TransitionListener 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- transitionlistener/__init__.py +21 -0
- transitionlistener/bubbledynamics.py +2250 -0
- transitionlistener/bubbledynamics_fixedstep.py +2929 -0
- transitionlistener/check_installation.py +309 -0
- transitionlistener/ckm.py +91 -0
- transitionlistener/colors.py +288 -0
- transitionlistener/config.py +185 -0
- transitionlistener/constants.py +64 -0
- transitionlistener/counterterms/__init__.py +230 -0
- transitionlistener/counterterms/cw.py +298 -0
- transitionlistener/counterterms/tensors.py +343 -0
- transitionlistener/counterterms/twohdm.py +520 -0
- transitionlistener/counterterms/twohdm_solver.py +374 -0
- transitionlistener/errors.py +315 -0
- transitionlistener/examples/bp_et.yaml +64 -0
- transitionlistener/examples/bp_lisa.yaml +70 -0
- transitionlistener/examples/bp_muares.yaml +69 -0
- transitionlistener/examples/bp_pta.yaml +64 -0
- transitionlistener/examples/example_dark_photon.yaml +73 -0
- transitionlistener/examples/example_grid.yaml +55 -0
- transitionlistener/examples/example_line.yaml +51 -0
- transitionlistener/examples/example_point.yaml +73 -0
- transitionlistener/examples/example_random_conformal.yaml +42 -0
- transitionlistener/examples/example_ultranest.yaml +39 -0
- transitionlistener/finiteT.py +468 -0
- transitionlistener/generic_potential.py +1521 -0
- transitionlistener/gridplots.py +592 -0
- transitionlistener/gwfopt.py +604 -0
- transitionlistener/helper_functions.py +843 -0
- transitionlistener/hydrodynamics.py +647 -0
- transitionlistener/interface/__init__.py +95 -0
- transitionlistener/interface/check.py +51 -0
- transitionlistener/interface/cli.py +211 -0
- transitionlistener/interface/config_loader.py +264 -0
- transitionlistener/interface/logging_utils.py +30 -0
- transitionlistener/interface/output_schema.py +296 -0
- transitionlistener/interface/pipeline.py +300 -0
- transitionlistener/interface/samplers.py +565 -0
- transitionlistener/interface/scans.py +468 -0
- transitionlistener/interface/single_point.py +581 -0
- transitionlistener/interface/state.py +61 -0
- transitionlistener/lineplots.py +597 -0
- transitionlistener/logo/TL-logo_large.png +0 -0
- transitionlistener/logo/TL-logo_small.png +0 -0
- transitionlistener/models/TL_2HDM.py +512 -0
- transitionlistener/models/TL_conformal_dark_u1.py +202 -0
- transitionlistener/models/TL_dark_U1.py +198 -0
- transitionlistener/models/TL_dark_U1_g_parameterization.py +198 -0
- transitionlistener/models/TL_dark_flipflop.py +274 -0
- transitionlistener/models/templatePotential.py +260 -0
- transitionlistener/nucleation.py +412 -0
- transitionlistener/observability.py +790 -0
- transitionlistener/particles.py +436 -0
- transitionlistener/pathDeformation.py +1462 -0
- transitionlistener/percolation_adaptive_gridbuilders.py +1385 -0
- transitionlistener/percolation_adaptive_rate.py +1049 -0
- transitionlistener/percolation_adaptivestepsize.py +1632 -0
- transitionlistener/percolation_fixedstepsize.py +618 -0
- transitionlistener/phases.py +1499 -0
- transitionlistener/plot_settings.py +121 -0
- transitionlistener/plots.py +1781 -0
- transitionlistener/runtime_options.py +278 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/bandwidths.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/density.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/freqs.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log.txt +16 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log10rhogrid.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log10rholabels.txt +30 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/pulsar_list.txt +1 -0
- transitionlistener/tab_data/HLV-O2-PLI.csv +82 -0
- transitionlistener/tab_data/HLV-O2-noise.csv +83 -0
- transitionlistener/tab_data/HLVK-design-PLI.csv +61 -0
- transitionlistener/tab_data/HLVK-design-noise.csv +83 -0
- transitionlistener/tab_data/NG15_PLI_hc.csv +31 -0
- transitionlistener/tab_data/NG15_noise_hc.csv +56 -0
- transitionlistener/tab_data/PLI-dataset.csv +327 -0
- transitionlistener/tab_data/energydensities.dat +1001 -0
- transitionlistener/tab_data/finiteT_b.dat.txt +1999 -0
- transitionlistener/tab_data/finiteT_f.dat.txt +1000 -0
- transitionlistener/tab_data/geff_SM.dat +10009 -0
- transitionlistener/tab_data/muAres_f_Hz_PLI_h2OmegaGW.csv +131 -0
- transitionlistener/tab_data/muAres_f_Hz_h2Omega_noise.csv +99 -0
- transitionlistener/tab_data/noise-dataset.csv +327 -0
- transitionlistener/tab_data/numberdensities.dat +1001 -0
- transitionlistener/tab_data/pressures.dat +1001 -0
- transitionlistener/thermodynamics.py +686 -0
- transitionlistener/transitionObservables.py +1199 -0
- transitionlistener/transitionObservables_fixedstep.py +976 -0
- transitionlistener/transitions.py +1000 -0
- transitionlistener/tunneling1D.py +910 -0
- transitionlistener-2.0.0.dist-info/METADATA +817 -0
- transitionlistener-2.0.0.dist-info/RECORD +95 -0
- transitionlistener-2.0.0.dist-info/WHEEL +4 -0
- transitionlistener-2.0.0.dist-info/entry_points.txt +2 -0
- transitionlistener-2.0.0.dist-info/licenses/LICENSE.txt +674 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Coleman–Weinberg gradient and Hessian utilities.
|
|
3
|
+
|
|
4
|
+
The functions defined here implement the analytic derivative formulas used in
|
|
5
|
+
BSMPT and ported into TransitionListener. They operate on the curvature
|
|
6
|
+
tensors evaluated at the vacuum configuration and provide gradient / Hessian
|
|
7
|
+
matrices expressed in the physical field basis (i.e. after rotating into the
|
|
8
|
+
mass eigenstates).
|
|
9
|
+
|
|
10
|
+
Part of TransitionListener v2.0
|
|
11
|
+
Documentation: https://tasillo.de/TransitionListener/
|
|
12
|
+
|
|
13
|
+
Authors:
|
|
14
|
+
Jonas Matuszak <jonas.matuszak@kit.edu>
|
|
15
|
+
Carlo Tasillo <carlo.tasillo@ific.uv.es>
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import math
|
|
21
|
+
from typing import Tuple
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
|
|
25
|
+
from transitionlistener.constants import CW_CONSTANTS
|
|
26
|
+
from .tensors import ensure_symmetric
|
|
27
|
+
|
|
28
|
+
EPS_LOOP = 1.0 / (16.0 * math.pi * math.pi)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _log_term(m2: float, scale_sq: float, counterterm: float) -> float:
|
|
32
|
+
r"""Return the Coleman-Weinberg logarithmic kernel
|
|
33
|
+
:math:`\log(m^2/\mu^2) - c + 1/2`, regularised at ``m^2 = 0``."""
|
|
34
|
+
if m2 == 0.0:
|
|
35
|
+
return -counterterm + 0.5
|
|
36
|
+
return math.log(m2 / scale_sq) - counterterm + 0.5
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _fbase(m2a: float, m2b: float, scale: float) -> float:
|
|
40
|
+
"""
|
|
41
|
+
Helper matching ``Class_Potential_Origin::fbase`` in BSMPT.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
if m2a == 0.0 and m2b == 0.0:
|
|
45
|
+
return 1.0
|
|
46
|
+
|
|
47
|
+
tol = 1e-5
|
|
48
|
+
log_a = math.log(m2a) - 2.0 * math.log(scale) if m2a != 0.0 else 0.0
|
|
49
|
+
if abs(m2a - m2b) > tol:
|
|
50
|
+
log_b = math.log(m2b) - 2.0 * math.log(scale) if m2b != 0.0 else 0.0
|
|
51
|
+
if m2a == 0.0:
|
|
52
|
+
return log_b
|
|
53
|
+
if m2b == 0.0:
|
|
54
|
+
return log_a
|
|
55
|
+
return (log_a * m2a - log_b * m2b) / (m2a - m2b)
|
|
56
|
+
return 1.0 + log_a
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def coleman_weinberg_derivatives(
|
|
60
|
+
h2: np.ndarray,
|
|
61
|
+
h3: np.ndarray,
|
|
62
|
+
h4: np.ndarray,
|
|
63
|
+
gauge_curv: np.ndarray,
|
|
64
|
+
quark_curv: np.ndarray,
|
|
65
|
+
lepton_curv: np.ndarray,
|
|
66
|
+
vev: np.ndarray,
|
|
67
|
+
*,
|
|
68
|
+
scale: float,
|
|
69
|
+
scalar_threshold: float = 1e-5,
|
|
70
|
+
fermion_threshold: float = 1e-10,
|
|
71
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
72
|
+
"""
|
|
73
|
+
Compute the Coleman–Weinberg gradient and Hessian in the physical basis.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
h2, h3, h4:
|
|
78
|
+
Higgs curvature tensors (quadratic, cubic, quartic) in the gauge basis.
|
|
79
|
+
gauge_curv:
|
|
80
|
+
Gauge curvature tensor :math:`G_{abij}`.
|
|
81
|
+
quark_curv, lepton_curv:
|
|
82
|
+
Fermionic Yukawa curvature tensors :math:`Y_{ij\\alpha}`.
|
|
83
|
+
vev:
|
|
84
|
+
Vacuum expectation value vector in the Higgs field basis (GeV).
|
|
85
|
+
scale:
|
|
86
|
+
Renormalisation scale :math:`\\mu` used in the Coleman–Weinberg logs.
|
|
87
|
+
scalar_threshold, fermion_threshold:
|
|
88
|
+
Numerical cut-offs below which eigenvalues are treated as zero.
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
(gradient, hessian):
|
|
93
|
+
Coleman–Weinberg first and second derivatives in the physical basis,
|
|
94
|
+
expressed in the same units as the input tensors. The overall
|
|
95
|
+
Coleman–Weinberg loop factor :math:`1/(16 \\pi^2)` is included.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
scale_sq = scale * scale
|
|
99
|
+
|
|
100
|
+
# ------------------------------------------------------------------
|
|
101
|
+
# Mass matrices and rotations
|
|
102
|
+
# ------------------------------------------------------------------
|
|
103
|
+
mass_h = h2.copy()
|
|
104
|
+
for k in range(vev.shape[0]):
|
|
105
|
+
mass_h += h3[:, :, k] * vev[k]
|
|
106
|
+
for k in range(vev.shape[0]):
|
|
107
|
+
for l in range(vev.shape[0]):
|
|
108
|
+
mass_h += 0.5 * h4[:, :, k, l] * vev[k] * vev[l]
|
|
109
|
+
|
|
110
|
+
mass_g = np.zeros((gauge_curv.shape[0], gauge_curv.shape[1]), dtype=float)
|
|
111
|
+
for a in range(gauge_curv.shape[0]):
|
|
112
|
+
for b in range(gauge_curv.shape[1]):
|
|
113
|
+
total = 0.0
|
|
114
|
+
for i in range(vev.shape[0]):
|
|
115
|
+
for j in range(vev.shape[0]):
|
|
116
|
+
total += gauge_curv[a, b, i, j] * vev[i] * vev[j]
|
|
117
|
+
mass_g[a, b] = 0.5 * total
|
|
118
|
+
|
|
119
|
+
MIJ_quark = np.tensordot(quark_curv, vev, axes=(2, 0))
|
|
120
|
+
MIJ_lepton = np.tensordot(lepton_curv, vev, axes=(2, 0))
|
|
121
|
+
|
|
122
|
+
mass_quark = MIJ_quark.conj().T @ MIJ_quark
|
|
123
|
+
mass_lepton = MIJ_lepton.conj().T @ MIJ_lepton
|
|
124
|
+
|
|
125
|
+
eigvals_h, eigvecs_h = np.linalg.eigh(mass_h)
|
|
126
|
+
HiggsRot = np.real_if_close(eigvecs_h.T, tol=1e8)
|
|
127
|
+
mass_h_sq = eigvals_h.copy()
|
|
128
|
+
mass_h_sq[np.abs(mass_h_sq) < scalar_threshold] = 0.0
|
|
129
|
+
|
|
130
|
+
eigvals_g, eigvecs_g = np.linalg.eigh(mass_g)
|
|
131
|
+
GaugeRot = np.real_if_close(eigvecs_g.T, tol=1e8)
|
|
132
|
+
mass_g_sq = eigvals_g.copy()
|
|
133
|
+
mass_g_sq[np.abs(mass_g_sq) < scalar_threshold] = 0.0
|
|
134
|
+
|
|
135
|
+
eigvals_q, eigvecs_q = np.linalg.eigh(mass_quark)
|
|
136
|
+
QuarkRot = np.real_if_close(eigvecs_q.T, tol=1e8)
|
|
137
|
+
mass_quark_sq = eigvals_q.real
|
|
138
|
+
mass_quark_sq[np.abs(mass_quark_sq) < fermion_threshold] = 0.0
|
|
139
|
+
|
|
140
|
+
eigvals_l, eigvecs_l = np.linalg.eigh(mass_lepton)
|
|
141
|
+
LepRot = np.real_if_close(eigvecs_l.T, tol=1e8)
|
|
142
|
+
mass_lepton_sq = eigvals_l.real
|
|
143
|
+
mass_lepton_sq[np.abs(mass_lepton_sq) < fermion_threshold] = 0.0
|
|
144
|
+
|
|
145
|
+
# ------------------------------------------------------------------
|
|
146
|
+
# Physical couplings
|
|
147
|
+
# ------------------------------------------------------------------
|
|
148
|
+
LambdaGauge3 = np.einsum("abij,j->abi", gauge_curv, vev, optimize=True)
|
|
149
|
+
LambdaHiggs3 = h3 + np.einsum("abij,j->abi", h4, vev, optimize=True)
|
|
150
|
+
|
|
151
|
+
LambdaQuark3 = (
|
|
152
|
+
np.einsum("ilk,lj->ijk", np.conjugate(quark_curv), MIJ_quark, optimize=True)
|
|
153
|
+
+ np.einsum("il,ljk->ijk", np.conjugate(MIJ_quark), quark_curv, optimize=True)
|
|
154
|
+
)
|
|
155
|
+
LambdaQuark4 = (
|
|
156
|
+
np.einsum("ilk,ljm->ijkm", np.conjugate(quark_curv), quark_curv, optimize=True)
|
|
157
|
+
+ np.einsum("ilm,ljk->ijkm", np.conjugate(quark_curv), quark_curv, optimize=True)
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
LambdaLepton3 = (
|
|
161
|
+
np.einsum("ilk,lj->ijk", np.conjugate(lepton_curv), MIJ_lepton, optimize=True)
|
|
162
|
+
+ np.einsum("il,ljk->ijk", np.conjugate(MIJ_lepton), lepton_curv, optimize=True)
|
|
163
|
+
)
|
|
164
|
+
LambdaLepton4 = (
|
|
165
|
+
np.einsum("ilk,ljm->ijkm", np.conjugate(lepton_curv), lepton_curv, optimize=True)
|
|
166
|
+
+ np.einsum("ilm,ljk->ijkm", np.conjugate(lepton_curv), lepton_curv, optimize=True)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
Couplings_Higgs_Triple = np.einsum(
|
|
170
|
+
"pi,qj,rk,ijk->pqr", HiggsRot, HiggsRot, HiggsRot, LambdaHiggs3, optimize=True
|
|
171
|
+
)
|
|
172
|
+
Couplings_Higgs_Quartic = np.einsum(
|
|
173
|
+
"pi,qj,rk,sl,ijkl->pqrs", HiggsRot, HiggsRot, HiggsRot, HiggsRot, h4, optimize=True
|
|
174
|
+
)
|
|
175
|
+
Couplings_Gauge_Higgs_21 = np.einsum(
|
|
176
|
+
"pa,qb,ri,abi->pqr", GaugeRot, GaugeRot, HiggsRot, LambdaGauge3, optimize=True
|
|
177
|
+
)
|
|
178
|
+
Couplings_Gauge_Higgs_22 = np.einsum(
|
|
179
|
+
"pa,qb,ri,sj,abij->pqrs", GaugeRot, GaugeRot, HiggsRot, HiggsRot, gauge_curv, optimize=True
|
|
180
|
+
)
|
|
181
|
+
Couplings_Quark_Higgs_21 = np.einsum(
|
|
182
|
+
"pa,qb,ri,abi->pqr", QuarkRot, QuarkRot, HiggsRot, LambdaQuark3, optimize=True
|
|
183
|
+
)
|
|
184
|
+
Couplings_Quark_Higgs_22 = np.einsum(
|
|
185
|
+
"pa,qb,ri,sj,abij->pqrs", QuarkRot, QuarkRot, HiggsRot, HiggsRot, LambdaQuark4, optimize=True
|
|
186
|
+
)
|
|
187
|
+
Couplings_Lepton_Higgs_21 = np.einsum(
|
|
188
|
+
"pa,qb,ri,abi->pqr", LepRot, LepRot, HiggsRot, LambdaLepton3, optimize=True
|
|
189
|
+
)
|
|
190
|
+
Couplings_Lepton_Higgs_22 = np.einsum(
|
|
191
|
+
"pa,qb,ri,sj,abij->pqrs", LepRot, LepRot, HiggsRot, HiggsRot, LambdaLepton4, optimize=True
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# ------------------------------------------------------------------
|
|
195
|
+
# Gradient
|
|
196
|
+
# ------------------------------------------------------------------
|
|
197
|
+
grad_mass_basis = np.zeros(vev.shape[0], dtype=float)
|
|
198
|
+
cw_gauge = CW_CONSTANTS["gauge"]
|
|
199
|
+
cw_scalar = CW_CONSTANTS["scalar"]
|
|
200
|
+
cw_fermion = CW_CONSTANTS["fermion"]
|
|
201
|
+
|
|
202
|
+
for i in range(vev.shape[0]):
|
|
203
|
+
tmp = 0.0
|
|
204
|
+
for a in range(mass_g_sq.shape[0]):
|
|
205
|
+
m2 = mass_g_sq[a]
|
|
206
|
+
if m2 != 0.0:
|
|
207
|
+
coup = Couplings_Gauge_Higgs_21[a, a, i]
|
|
208
|
+
tmp += 1.5 * float(coup) * m2 * _log_term(m2, scale_sq, cw_gauge)
|
|
209
|
+
for a in range(mass_h_sq.shape[0]):
|
|
210
|
+
m2 = mass_h_sq[a]
|
|
211
|
+
if m2 != 0.0:
|
|
212
|
+
coup = Couplings_Higgs_Triple[a, a, i]
|
|
213
|
+
tmp += 0.5 * float(coup) * m2 * _log_term(m2, scale_sq, cw_scalar)
|
|
214
|
+
for a in range(mass_quark_sq.shape[0]):
|
|
215
|
+
m2 = mass_quark_sq[a]
|
|
216
|
+
if m2 != 0.0:
|
|
217
|
+
coup = Couplings_Quark_Higgs_21[a, a, i].real
|
|
218
|
+
tmp -= 3.0 * coup * m2 * _log_term(m2, scale_sq, cw_fermion)
|
|
219
|
+
for a in range(mass_lepton_sq.shape[0]):
|
|
220
|
+
m2 = mass_lepton_sq[a]
|
|
221
|
+
if m2 != 0.0:
|
|
222
|
+
coup = Couplings_Lepton_Higgs_21[a, a, i].real
|
|
223
|
+
tmp -= 1.0 * coup * m2 * _log_term(m2, scale_sq, cw_fermion)
|
|
224
|
+
grad_mass_basis[i] = tmp
|
|
225
|
+
|
|
226
|
+
grad_phys = HiggsRot.T @ grad_mass_basis
|
|
227
|
+
grad_phys = np.real_if_close(grad_phys * EPS_LOOP, tol=1e8)
|
|
228
|
+
|
|
229
|
+
# ------------------------------------------------------------------
|
|
230
|
+
# Hessian
|
|
231
|
+
# ------------------------------------------------------------------
|
|
232
|
+
storage = np.zeros((vev.shape[0], vev.shape[0]), dtype=float)
|
|
233
|
+
for i in range(vev.shape[0]):
|
|
234
|
+
for j in range(vev.shape[0]):
|
|
235
|
+
total = 0.0
|
|
236
|
+
# Gauge sector
|
|
237
|
+
block = 0.0
|
|
238
|
+
for a in range(mass_g_sq.shape[0]):
|
|
239
|
+
for b in range(mass_g_sq.shape[0]):
|
|
240
|
+
coup1 = Couplings_Gauge_Higgs_21[a, b, i]
|
|
241
|
+
coup2 = Couplings_Gauge_Higgs_21[b, a, j]
|
|
242
|
+
br = _fbase(mass_g_sq[a], mass_g_sq[b], scale) - cw_gauge + 0.5
|
|
243
|
+
block += float(coup1) * float(coup2) * br
|
|
244
|
+
if mass_g_sq[a] != 0.0:
|
|
245
|
+
coup = Couplings_Gauge_Higgs_22[a, a, i, j]
|
|
246
|
+
block += float(coup) * mass_g_sq[a] * _log_term(mass_g_sq[a], scale_sq, cw_gauge)
|
|
247
|
+
total += 1.5 * block
|
|
248
|
+
|
|
249
|
+
# Scalar sector
|
|
250
|
+
block = 0.0
|
|
251
|
+
for a in range(mass_h_sq.shape[0]):
|
|
252
|
+
for b in range(mass_h_sq.shape[0]):
|
|
253
|
+
coup1 = Couplings_Higgs_Triple[a, b, i]
|
|
254
|
+
coup2 = Couplings_Higgs_Triple[b, a, j]
|
|
255
|
+
br = _fbase(mass_h_sq[a], mass_h_sq[b], scale) - cw_scalar + 0.5
|
|
256
|
+
block += float(coup1) * float(coup2) * br
|
|
257
|
+
if mass_h_sq[a] != 0.0:
|
|
258
|
+
coup = Couplings_Higgs_Quartic[a, a, i, j]
|
|
259
|
+
block += float(coup) * mass_h_sq[a] * _log_term(mass_h_sq[a], scale_sq, cw_scalar)
|
|
260
|
+
total += 0.5 * block
|
|
261
|
+
|
|
262
|
+
# Quark sector
|
|
263
|
+
block = 0.0
|
|
264
|
+
for a in range(mass_quark_sq.shape[0]):
|
|
265
|
+
for b in range(mass_quark_sq.shape[0]):
|
|
266
|
+
coup = (
|
|
267
|
+
Couplings_Quark_Higgs_21[a, b, i]
|
|
268
|
+
* Couplings_Quark_Higgs_21[b, a, j]
|
|
269
|
+
).real
|
|
270
|
+
br = _fbase(mass_quark_sq[a], mass_quark_sq[b], scale) - cw_fermion + 0.5
|
|
271
|
+
block += coup * br
|
|
272
|
+
if mass_quark_sq[a] != 0.0:
|
|
273
|
+
coup = Couplings_Quark_Higgs_22[a, a, i, j].real
|
|
274
|
+
block += coup * mass_quark_sq[a] * _log_term(mass_quark_sq[a], scale_sq, cw_fermion)
|
|
275
|
+
total -= 3.0 * block
|
|
276
|
+
|
|
277
|
+
# Lepton sector
|
|
278
|
+
block = 0.0
|
|
279
|
+
for a in range(mass_lepton_sq.shape[0]):
|
|
280
|
+
for b in range(mass_lepton_sq.shape[0]):
|
|
281
|
+
coup = (
|
|
282
|
+
Couplings_Lepton_Higgs_21[a, b, i]
|
|
283
|
+
* Couplings_Lepton_Higgs_21[b, a, j]
|
|
284
|
+
).real
|
|
285
|
+
br = _fbase(mass_lepton_sq[a], mass_lepton_sq[b], scale) - cw_fermion + 0.5
|
|
286
|
+
block += coup * br
|
|
287
|
+
if mass_lepton_sq[a] != 0.0:
|
|
288
|
+
coup = Couplings_Lepton_Higgs_22[a, a, i, j].real
|
|
289
|
+
block += coup * mass_lepton_sq[a] * _log_term(mass_lepton_sq[a], scale_sq, cw_fermion)
|
|
290
|
+
total -= block
|
|
291
|
+
|
|
292
|
+
storage[i, j] = total
|
|
293
|
+
|
|
294
|
+
storage = ensure_symmetric(storage)
|
|
295
|
+
hess_phys = HiggsRot.T @ storage @ HiggsRot
|
|
296
|
+
hess_phys = np.real_if_close(hess_phys * EPS_LOOP, tol=1e8)
|
|
297
|
+
|
|
298
|
+
return grad_phys, ensure_symmetric(hess_phys)
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Low-level helpers for symbolic curvature tensors.
|
|
3
|
+
|
|
4
|
+
The utilities provided here centralise the JSON I/O that we use to ship SymPy
|
|
5
|
+
expressions from the generator stage to the runtime evaluation. They also
|
|
6
|
+
offer a thin numerical wrapper around each expression so that models can keep
|
|
7
|
+
their own code short and declarative.
|
|
8
|
+
|
|
9
|
+
Part of TransitionListener v2.0
|
|
10
|
+
Documentation: https://tasillo.de/TransitionListener/
|
|
11
|
+
|
|
12
|
+
Authors:
|
|
13
|
+
Jonas Matuszak <jonas.matuszak@kit.edu>
|
|
14
|
+
Carlo Tasillo <carlo.tasillo@ific.uv.es>
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from functools import lru_cache
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import TYPE_CHECKING, Any, Mapping
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
from itertools import permutations
|
|
25
|
+
import numpy as np
|
|
26
|
+
|
|
27
|
+
from transitionlistener import errors
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
import sympy as sp
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _require_sympy():
|
|
34
|
+
"""Import SymPy on demand and raise a targeted error when it is unavailable."""
|
|
35
|
+
try:
|
|
36
|
+
import sympy as sp
|
|
37
|
+
except ModuleNotFoundError as exc:
|
|
38
|
+
raise ModuleNotFoundError(
|
|
39
|
+
"SymPy is required for symbolic counterterm tensor evaluation. "
|
|
40
|
+
"Install `sympy` to use SymPy-based counterterm models."
|
|
41
|
+
) from exc
|
|
42
|
+
return sp
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _is_index_key(key: str) -> bool:
|
|
46
|
+
"""Return whether ``key`` encodes a tensor index tuple such as ``"(0, 1)"``."""
|
|
47
|
+
key = key.strip()
|
|
48
|
+
return key.startswith("(") and key.endswith(")")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _parse_index_key(key: str) -> tuple[int, ...]:
|
|
52
|
+
"""Parse a stringified tensor-index tuple back into integers."""
|
|
53
|
+
stripped = key.strip()[1:-1]
|
|
54
|
+
if not stripped:
|
|
55
|
+
return tuple()
|
|
56
|
+
return tuple(int(part.strip()) for part in stripped.split(","))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _convert_tensor_dict(obj: Any) -> Any:
|
|
60
|
+
"""Recursively turn JSON-loaded tensor data back into SymPy expressions."""
|
|
61
|
+
sp = _require_sympy()
|
|
62
|
+
if isinstance(obj, dict):
|
|
63
|
+
converted = {}
|
|
64
|
+
for key, value in obj.items():
|
|
65
|
+
if _is_index_key(key):
|
|
66
|
+
converted[_parse_index_key(key)] = _convert_tensor_dict(value)
|
|
67
|
+
else:
|
|
68
|
+
converted[key] = _convert_tensor_dict(value)
|
|
69
|
+
return converted
|
|
70
|
+
if isinstance(obj, list):
|
|
71
|
+
return [_convert_tensor_dict(item) for item in obj]
|
|
72
|
+
if isinstance(obj, str):
|
|
73
|
+
return sp.sympify(obj, evaluate=False)
|
|
74
|
+
return obj
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _initialise_parameter_symbols(
|
|
78
|
+
metadata: dict[str, Any],
|
|
79
|
+
*,
|
|
80
|
+
complex_parameters: tuple[str, ...] = (),
|
|
81
|
+
) -> dict[str, sp.Symbol]:
|
|
82
|
+
"""Construct the SymPy symbols corresponding to the stored metadata labels."""
|
|
83
|
+
sp = _require_sympy()
|
|
84
|
+
parameter_aliases = metadata.get("parameter_symbol_names", {})
|
|
85
|
+
parameter_symbols: dict[str, sp.Symbol] = {}
|
|
86
|
+
for name in metadata.get("parameters", []):
|
|
87
|
+
alias = parameter_aliases.get(name, name)
|
|
88
|
+
alias_expr = sp.sympify(alias)
|
|
89
|
+
if isinstance(alias_expr, sp.Symbol):
|
|
90
|
+
base_name = alias_expr.name
|
|
91
|
+
else:
|
|
92
|
+
base_name = str(alias)
|
|
93
|
+
alias_expr = sp.Symbol(base_name)
|
|
94
|
+
is_complex = base_name in complex_parameters
|
|
95
|
+
symbol = sp.Symbol(base_name) if is_complex else sp.Symbol(base_name, real=True)
|
|
96
|
+
for tag in {name, base_name, str(alias)}:
|
|
97
|
+
parameter_symbols[tag] = symbol
|
|
98
|
+
parameter_symbols[alias_expr] = symbol
|
|
99
|
+
metadata["parameter_symbols"] = parameter_symbols
|
|
100
|
+
return parameter_symbols
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@lru_cache(maxsize=None)
|
|
104
|
+
def load_tensor_dataset(
|
|
105
|
+
tensor_file: Path,
|
|
106
|
+
*,
|
|
107
|
+
complex_parameters: tuple[str, ...] = (
|
|
108
|
+
"Vud",
|
|
109
|
+
"Vus",
|
|
110
|
+
"Vub",
|
|
111
|
+
"Vcd",
|
|
112
|
+
"Vcs",
|
|
113
|
+
"Vcb",
|
|
114
|
+
"Vtd",
|
|
115
|
+
"Vts",
|
|
116
|
+
"Vtb",
|
|
117
|
+
),
|
|
118
|
+
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
119
|
+
"""
|
|
120
|
+
Load a JSON tensor dataset and return SymPy expressions ready for evaluation.
|
|
121
|
+
|
|
122
|
+
Parameters
|
|
123
|
+
----------
|
|
124
|
+
tensor_file:
|
|
125
|
+
Absolute path to the JSON document produced by the counterterm generator.
|
|
126
|
+
complex_parameters:
|
|
127
|
+
Iterable with the subset of parameters that need complex-valued symbols.
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
(metadata, tensors)
|
|
132
|
+
Metadata dictionary (augmented with ``parameter_symbols``) and a nested
|
|
133
|
+
dictionary of SymPy expressions keyed by index tuples.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
if not tensor_file.exists():
|
|
137
|
+
raise errors.InitPotentialError(
|
|
138
|
+
f"Generated tensor file not found: {tensor_file}. "
|
|
139
|
+
"Run the counterterm generator to create it."
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
with tensor_file.open("r", encoding="utf-8") as fh:
|
|
143
|
+
raw = json.load(fh)
|
|
144
|
+
|
|
145
|
+
metadata = raw.get("metadata", {})
|
|
146
|
+
_initialise_parameter_symbols(metadata, complex_parameters=complex_parameters)
|
|
147
|
+
tensors = _convert_tensor_dict(raw.get("tensors", {}))
|
|
148
|
+
return metadata, tensors
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def build_substitutions(
|
|
152
|
+
metadata: Mapping[str, Any],
|
|
153
|
+
parameter_values: Mapping[str, complex],
|
|
154
|
+
) -> dict[sp.Symbol, complex]:
|
|
155
|
+
"""
|
|
156
|
+
Construct the substitution dictionary used during tensor evaluation.
|
|
157
|
+
|
|
158
|
+
Parameters
|
|
159
|
+
----------
|
|
160
|
+
metadata:
|
|
161
|
+
Metadata dictionary returned by :func:`load_tensor_dataset`.
|
|
162
|
+
parameter_values:
|
|
163
|
+
Mapping from parameter identifiers (as stored in the metadata) to the
|
|
164
|
+
numerical values provided by the model.
|
|
165
|
+
|
|
166
|
+
Returns
|
|
167
|
+
-------
|
|
168
|
+
dict
|
|
169
|
+
Mapping ``sympy.Symbol -> value`` ready to be passed to
|
|
170
|
+
:func:`evaluate_expression`.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
sp = _require_sympy()
|
|
174
|
+
subs: dict[sp.Symbol, complex] = {}
|
|
175
|
+
param_symbols: dict[str, sp.Symbol] = dict(metadata.get("parameter_symbols", {}))
|
|
176
|
+
aliases = metadata.get("parameter_symbol_names", {})
|
|
177
|
+
|
|
178
|
+
def assign(label: str, value: complex) -> None:
|
|
179
|
+
symbol = param_symbols.get(label)
|
|
180
|
+
if symbol is None:
|
|
181
|
+
symbol = sp.Symbol(label)
|
|
182
|
+
param_symbols[label] = symbol
|
|
183
|
+
subs[symbol] = value
|
|
184
|
+
# Also populate the alias to avoid round trips.
|
|
185
|
+
alias_label = aliases.get(label)
|
|
186
|
+
if alias_label is not None:
|
|
187
|
+
alias_symbol = param_symbols.get(alias_label)
|
|
188
|
+
if alias_symbol is None:
|
|
189
|
+
alias_symbol = sp.Symbol(alias_label)
|
|
190
|
+
param_symbols[alias_label] = alias_symbol
|
|
191
|
+
subs[alias_symbol] = value
|
|
192
|
+
|
|
193
|
+
for key, value in parameter_values.items():
|
|
194
|
+
assign(str(key), complex(value))
|
|
195
|
+
|
|
196
|
+
metadata["parameter_symbols"] = param_symbols # type: ignore[index]
|
|
197
|
+
return subs
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def evaluate_expression(
|
|
201
|
+
expr: sp.Expr,
|
|
202
|
+
substitutions: Mapping[sp.Symbol, complex],
|
|
203
|
+
*,
|
|
204
|
+
extra_substitutions: Mapping[str | sp.Symbol, complex] | None = None,
|
|
205
|
+
) -> complex | float:
|
|
206
|
+
"""
|
|
207
|
+
Evaluate a tensor entry to floating-point precision.
|
|
208
|
+
|
|
209
|
+
Parameters
|
|
210
|
+
----------
|
|
211
|
+
expr:
|
|
212
|
+
SymPy expression representing the tensor component.
|
|
213
|
+
substitutions:
|
|
214
|
+
Base substitution map generated by :func:`build_substitutions`.
|
|
215
|
+
extra_substitutions:
|
|
216
|
+
Optional field-dependent substitutions that should override the base map.
|
|
217
|
+
|
|
218
|
+
Returns
|
|
219
|
+
-------
|
|
220
|
+
complex
|
|
221
|
+
Numerical value of the expression. Purely real results are returned as
|
|
222
|
+
``float`` for convenience.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
sp = _require_sympy()
|
|
226
|
+
if expr is None:
|
|
227
|
+
return 0.0
|
|
228
|
+
|
|
229
|
+
subs = dict(substitutions)
|
|
230
|
+
if extra_substitutions:
|
|
231
|
+
for key, value in extra_substitutions.items():
|
|
232
|
+
symbol = key if isinstance(key, sp.Symbol) else sp.Symbol(str(key))
|
|
233
|
+
subs[symbol] = complex(value)
|
|
234
|
+
|
|
235
|
+
evaluated = sp.N(expr.subs(subs), 30)
|
|
236
|
+
free_symbols = getattr(evaluated, "free_symbols", set())
|
|
237
|
+
if free_symbols:
|
|
238
|
+
missing = ", ".join(str(sym) for sym in sorted(free_symbols, key=str))
|
|
239
|
+
raise ValueError(f"Unresolved symbols in tensor evaluation: {missing} (expression: {expr})")
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
value = complex(evaluated)
|
|
243
|
+
except TypeError:
|
|
244
|
+
real_part = float(sp.N(sp.re(evaluated), 30))
|
|
245
|
+
imag_part = float(sp.N(sp.im(evaluated), 30))
|
|
246
|
+
value = complex(real_part, imag_part)
|
|
247
|
+
|
|
248
|
+
if abs(value.imag) < 1e-12:
|
|
249
|
+
return float(value.real)
|
|
250
|
+
if abs(value.real) < 1e-12:
|
|
251
|
+
return complex(0.0, value.imag)
|
|
252
|
+
return value
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def ensure_symmetric(matrix):
|
|
256
|
+
"""
|
|
257
|
+
Symmetrise a square numerical matrix.
|
|
258
|
+
|
|
259
|
+
This helper is used to enforce the hermiticity of Coleman–Weinberg Hessians
|
|
260
|
+
after evaluating them numerically.
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
return 0.5 * (matrix + matrix.T)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _determine_dim(keys, positions):
|
|
267
|
+
"""Infer an array dimension from the largest index stored in ``keys``."""
|
|
268
|
+
max_index = 0
|
|
269
|
+
for key in keys:
|
|
270
|
+
for pos in positions:
|
|
271
|
+
max_index = max(max_index, key[pos])
|
|
272
|
+
return max_index + 1
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def evaluate_curvature_arrays(
|
|
276
|
+
tensors: Mapping[str, Any],
|
|
277
|
+
metadata: Mapping[str, Any],
|
|
278
|
+
parameter_values: Mapping[str, complex],
|
|
279
|
+
*,
|
|
280
|
+
yukawa_key: str,
|
|
281
|
+
) -> dict[str, np.ndarray]:
|
|
282
|
+
"""
|
|
283
|
+
Evaluate the curvature tensors stored in ``tensors`` using ``parameter_values``.
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
subs = build_substitutions(metadata, parameter_values)
|
|
287
|
+
n_h = len(metadata.get("field_basis", []))
|
|
288
|
+
if n_h == 0:
|
|
289
|
+
n_h = _determine_dim(tensors.get("Curvature_Higgs_L2", {}).keys(), (0, 1))
|
|
290
|
+
|
|
291
|
+
h2 = np.zeros((n_h, n_h), dtype=float)
|
|
292
|
+
for (i, j), expr in tensors.get("Curvature_Higgs_L2", {}).items():
|
|
293
|
+
value = float(evaluate_expression(expr, subs))
|
|
294
|
+
h2[i, j] = value
|
|
295
|
+
h2[j, i] = value
|
|
296
|
+
|
|
297
|
+
h3 = np.zeros((n_h, n_h, n_h), dtype=float)
|
|
298
|
+
for key, expr in tensors.get("Curvature_Higgs_L3", {}).items():
|
|
299
|
+
value = float(evaluate_expression(expr, subs))
|
|
300
|
+
for perm in set(permutations(key)):
|
|
301
|
+
h3[perm] = value
|
|
302
|
+
|
|
303
|
+
h4 = np.zeros((n_h, n_h, n_h, n_h), dtype=float)
|
|
304
|
+
for key, expr in tensors.get("Curvature_Higgs_L4", {}).items():
|
|
305
|
+
value = float(evaluate_expression(expr, subs))
|
|
306
|
+
for perm in set(permutations(key)):
|
|
307
|
+
h4[perm] = value
|
|
308
|
+
|
|
309
|
+
gauge_data = tensors.get("Curvature_Gauge_G2H2", {})
|
|
310
|
+
if gauge_data:
|
|
311
|
+
n_g = _determine_dim(gauge_data.keys(), (0, 1))
|
|
312
|
+
else:
|
|
313
|
+
n_g = 0
|
|
314
|
+
gauge = np.zeros((n_g, n_g, n_h, n_h), dtype=float)
|
|
315
|
+
for (a, b, i, j), expr in gauge_data.items():
|
|
316
|
+
value = float(evaluate_expression(expr, subs))
|
|
317
|
+
gauge[a, b, i, j] = value
|
|
318
|
+
gauge[a, b, j, i] = value
|
|
319
|
+
gauge[b, a, j, i] = value
|
|
320
|
+
gauge[b, a, i, j] = value
|
|
321
|
+
|
|
322
|
+
quark_data = tensors.get("Curvature_Quark_F2H1", {}).get(yukawa_key, {})
|
|
323
|
+
lepton_data = tensors.get("Curvature_Lepton_F2H1", {}).get(yukawa_key, {})
|
|
324
|
+
|
|
325
|
+
n_q = _determine_dim(quark_data.keys(), (0, 1)) if quark_data else 0
|
|
326
|
+
n_l = _determine_dim(lepton_data.keys(), (0, 1)) if lepton_data else 0
|
|
327
|
+
|
|
328
|
+
quark = np.zeros((n_q, n_q, n_h), dtype=complex)
|
|
329
|
+
for (i, j, k), expr in quark_data.items():
|
|
330
|
+
quark[i, j, k] = evaluate_expression(expr, subs)
|
|
331
|
+
|
|
332
|
+
lepton = np.zeros((n_l, n_l, n_h), dtype=complex)
|
|
333
|
+
for (i, j, k), expr in lepton_data.items():
|
|
334
|
+
lepton[i, j, k] = evaluate_expression(expr, subs)
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
"H2": h2,
|
|
338
|
+
"H3": h3,
|
|
339
|
+
"H4": h4,
|
|
340
|
+
"Gauge": gauge,
|
|
341
|
+
"Quark": quark,
|
|
342
|
+
"Lepton": lepton,
|
|
343
|
+
}
|