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,686 @@
|
|
|
1
|
+
"""Module containing routines to calculate thermodynamic quantities
|
|
2
|
+
like the numberdensity, energy density, pressure and entropy density.
|
|
3
|
+
|
|
4
|
+
Part of TransitionListener v2.0
|
|
5
|
+
Documentation: https://tasillo.de/TransitionListener/
|
|
6
|
+
|
|
7
|
+
Authors:
|
|
8
|
+
Jonas Matuszak <jonas.matuszak@kit.edu>
|
|
9
|
+
Carlo Tasillo <carlo.tasillo@ific.uv.es>
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from scipy import interpolate
|
|
15
|
+
from scipy import integrate
|
|
16
|
+
from scipy import special
|
|
17
|
+
from scipy.signal import savgol_filter
|
|
18
|
+
|
|
19
|
+
from .particles import BaseParticle
|
|
20
|
+
from . import console
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
myPath = os.path.abspath(os.path.dirname(__file__)) + "/"
|
|
24
|
+
|
|
25
|
+
# Interpolate numberdensities
|
|
26
|
+
def calcNumberdensityIntegrals():
|
|
27
|
+
"""Calculate geff for numberdensity"""
|
|
28
|
+
def integf(u,x): return u * np.sqrt(u**2 - x**2) / (np.exp(u) + 1)
|
|
29
|
+
def integb(u,x): return u * np.sqrt(u**2 - x**2) / (np.exp(u) - 1)
|
|
30
|
+
|
|
31
|
+
n = 1000
|
|
32
|
+
x_range = np.logspace(-7, np.log10(7e2), n)
|
|
33
|
+
Ib = np.zeros(n)
|
|
34
|
+
If = np.zeros(n)
|
|
35
|
+
for i, x in enumerate(x_range):
|
|
36
|
+
Ib[i] = integrate.quad(integb, x + 1e-50, np.inf, args=(x,))[0]
|
|
37
|
+
If[i] = integrate.quad(integf, x + 1e-50, np.inf, args=(x,))[0]
|
|
38
|
+
|
|
39
|
+
Ib = Ib / (2*special.zeta(3))
|
|
40
|
+
If = If / (2*special.zeta(3))
|
|
41
|
+
data = np.array([x_range, Ib, If]).T
|
|
42
|
+
|
|
43
|
+
np.savetxt(myPath + "tab_data/numberdensities.dat", data, header="# x, Ib, If")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def calcEnergydensityIntegrals():
|
|
47
|
+
"""Tabulate geff for the energydensity"""
|
|
48
|
+
def integf(u, x): return u**2 * np.sqrt(u**2 - x**2) / (np.exp(u) + 1)
|
|
49
|
+
def integb(u, x): return u**2 * np.sqrt(u**2 - x**2) / (np.exp(u) - 1)
|
|
50
|
+
|
|
51
|
+
n = 1000
|
|
52
|
+
x_range = np.logspace(-7, np.log10(7e2), n)
|
|
53
|
+
Ib = np.zeros(n)
|
|
54
|
+
If = np.zeros(n)
|
|
55
|
+
for i, x in enumerate(x_range):
|
|
56
|
+
Ib[i] = integrate.quad(integb, x + 1e-50, np.inf, args=(x,))[0]
|
|
57
|
+
If[i] = integrate.quad(integf, x + 1e-50, np.inf, args=(x,))[0]
|
|
58
|
+
|
|
59
|
+
Ib = Ib * 15/np.pi**4
|
|
60
|
+
If = If * 15/np.pi**4
|
|
61
|
+
data = np.array([x_range, Ib, If]).T
|
|
62
|
+
|
|
63
|
+
np.savetxt(myPath + "tab_data/energydensities.dat", data, header="# x, Ienergy_b, Ienergy_f")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def calcPressureIntegrals():
|
|
67
|
+
"""Pre-compute spline approximations of the pressure integrals."""
|
|
68
|
+
def integf(u,x): return np.sqrt(u**2 - x**2)**3 / (np.exp(u) + 1)
|
|
69
|
+
def integb(u,x): return np.sqrt(u**2 - x**2)**3 / (np.exp(u) - 1)
|
|
70
|
+
|
|
71
|
+
n = 1000
|
|
72
|
+
x_range = np.logspace(-7, np.log10(7e2), n)
|
|
73
|
+
Ib = np.zeros(n)
|
|
74
|
+
If = np.zeros(n)
|
|
75
|
+
for i,x in enumerate(x_range):
|
|
76
|
+
Ib[i] = integrate.quad(integb, x + 1e-50, np.inf, args=(x,))[0]
|
|
77
|
+
If[i] = integrate.quad(integf, x + 1e-50, np.inf, args=(x,))[0]
|
|
78
|
+
|
|
79
|
+
Ib = Ib * 15/np.pi**4
|
|
80
|
+
If = If * 15/np.pi**4
|
|
81
|
+
data = np.array([x_range, Ib, If]).T
|
|
82
|
+
|
|
83
|
+
np.savetxt(myPath + "tab_data/pressures.dat", data, header="# x, Ipressure_b, Ipressure_f")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
data_ndensity = np.loadtxt(myPath + "tab_data/numberdensities.dat")
|
|
88
|
+
data_edensity = np.loadtxt(myPath + "tab_data/energydensities.dat")
|
|
89
|
+
data_pressure = np.loadtxt(myPath + "tab_data/pressures.dat")
|
|
90
|
+
except:
|
|
91
|
+
calcNumberdensityIntegrals()
|
|
92
|
+
calcEnergydensityIntegrals()
|
|
93
|
+
calcPressureIntegrals()
|
|
94
|
+
data_ndensity = np.loadtxt(myPath + "tab_data/numberdensities.dat")
|
|
95
|
+
data_edensity = np.loadtxt(myPath + "tab_data/energydensities.dat")
|
|
96
|
+
data_pressure = np.loadtxt(myPath + "tab_data/pressures.dat")
|
|
97
|
+
|
|
98
|
+
data_geffSM = np.genfromtxt(myPath + "tab_data/geff_SM.dat")
|
|
99
|
+
Ie_geffSM = interpolate.CubicSpline(
|
|
100
|
+
np.log10(data_geffSM[:, 0]),
|
|
101
|
+
data_geffSM[:, 1],
|
|
102
|
+
bc_type="clamped"
|
|
103
|
+
)
|
|
104
|
+
# Add filter to smooth for derivative
|
|
105
|
+
Ie_geffSM_smooth = interpolate.CubicSpline(
|
|
106
|
+
np.log10(data_geffSM[:, 0]),
|
|
107
|
+
savgol_filter(data_geffSM[:, 1], 81, 3),
|
|
108
|
+
bc_type="clamped"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
Is_geffSM = interpolate.CubicSpline(
|
|
112
|
+
np.log10(data_geffSM[:, 0]),
|
|
113
|
+
data_geffSM[:, 3],
|
|
114
|
+
bc_type="clamped"
|
|
115
|
+
)
|
|
116
|
+
Is_geffSM_smooth = interpolate.CubicSpline(
|
|
117
|
+
np.log10(data_geffSM[:, 0]),
|
|
118
|
+
savgol_filter(data_geffSM[:, 3], 81, 3),
|
|
119
|
+
bc_type="clamped"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
Inb = interpolate.interp1d(data_ndensity[:,0], data_ndensity[:,1], kind="cubic")
|
|
124
|
+
Inf = interpolate.interp1d(data_ndensity[:,0], data_ndensity[:,2], kind="cubic")
|
|
125
|
+
Ieb = interpolate.interp1d(data_edensity[:,0], data_edensity[:,1], kind="cubic", fill_value=(1, 0), bounds_error=False)
|
|
126
|
+
Ief = interpolate.interp1d(
|
|
127
|
+
data_edensity[:, 0],
|
|
128
|
+
data_edensity[:, 2],
|
|
129
|
+
kind="cubic",
|
|
130
|
+
fill_value=(0.875, 0),
|
|
131
|
+
bounds_error=False,
|
|
132
|
+
)
|
|
133
|
+
Ipb = interpolate.interp1d(data_pressure[:,0], data_pressure[:,1], kind="cubic")
|
|
134
|
+
Ipf = interpolate.interp1d(data_pressure[:,0], data_pressure[:,2], kind="cubic")
|
|
135
|
+
|
|
136
|
+
# ==================================================
|
|
137
|
+
# Fit function for the SM effective DOFs
|
|
138
|
+
# K. Saikawa and S. Shirai (2018), arxiv:1803.01038
|
|
139
|
+
# ==================================================
|
|
140
|
+
|
|
141
|
+
def geff_rho_h(T: float):
|
|
142
|
+
r"""High-temperature fit for the SM energy-density degrees of freedom.
|
|
143
|
+
|
|
144
|
+
The fit approximates :math:`g_{\mathrm{eff},\rho}^{\mathrm{SM}}(T)` defined
|
|
145
|
+
by
|
|
146
|
+
|
|
147
|
+
.. math::
|
|
148
|
+
\rho_{\mathrm{SM}}(T)
|
|
149
|
+
= \frac{\pi^2}{30}\, g_{\mathrm{eff},\rho}^{\mathrm{SM}}(T)\, T^4.
|
|
150
|
+
|
|
151
|
+
It uses the rational approximation of Saikawa and Shirai
|
|
152
|
+
(arXiv:1803.01038) above the QCD crossover.
|
|
153
|
+
"""
|
|
154
|
+
a = np.array([1.0, 1.117240, 3.12672e-01,
|
|
155
|
+
-4.68049e-02, -2.65004e-02, -1.19760e-03,
|
|
156
|
+
1.82812e-04, 1.36436e-04, 8.55051e-05,
|
|
157
|
+
1.22840e-05, 3.82259e-07, -6.87035e-09])
|
|
158
|
+
b = np.array([1.43382e-02, 1.37559e-02, 2.92108e-03,
|
|
159
|
+
-5.38533e-04, -1.62496e-04, -2.87906e-05,
|
|
160
|
+
-3.84278e-06, 2.78776e-06, 7.40342e-07,
|
|
161
|
+
1.17210e-07, 3.72499e-09, -6.74107e-11])
|
|
162
|
+
|
|
163
|
+
t = np.log(T)
|
|
164
|
+
x = 1
|
|
165
|
+
temp1 = 0
|
|
166
|
+
temp2 = 0
|
|
167
|
+
for i in range(12):
|
|
168
|
+
temp1 += a[i] * x
|
|
169
|
+
temp2 += b[i] * x
|
|
170
|
+
x *= t
|
|
171
|
+
return temp1/temp2
|
|
172
|
+
|
|
173
|
+
def ratio(T: float):
|
|
174
|
+
r"""Return the fitted ratio :math:`g_{\mathrm{eff},\rho}/g_{\mathrm{eff},s}`.
|
|
175
|
+
|
|
176
|
+
This auxiliary fit is combined with :func:`geff_rho_h` to reconstruct the
|
|
177
|
+
entropy degrees of freedom according to
|
|
178
|
+
|
|
179
|
+
.. math::
|
|
180
|
+
g_{\mathrm{eff},s}(T)
|
|
181
|
+
= \frac{g_{\mathrm{eff},\rho}(T)}
|
|
182
|
+
{g_{\mathrm{eff},\rho}(T)/g_{\mathrm{eff},s}(T)}.
|
|
183
|
+
|
|
184
|
+
This function implements the ratio in the denominator.
|
|
185
|
+
"""
|
|
186
|
+
a = np.array([1., 6.07869e-01, -1.54485e-01,
|
|
187
|
+
-2.24034e-01, -2.82147e-02, 2.90620e-02,
|
|
188
|
+
6.86778e-03, -1.05e-03, -1.69104e-04,
|
|
189
|
+
1.06301e-05, 1.69528e-06, -9.33311e-08])
|
|
190
|
+
|
|
191
|
+
b = np.array([7.07388E+01, 9.18011E+01, 3.31892E+01,
|
|
192
|
+
-1.39779E+00, -1.52558E+00, -1.97857e-02,
|
|
193
|
+
-1.60146e-01, 8.22615e-05, 2.02651e-02,
|
|
194
|
+
-1.82134e-05, 7.83943e-05, 7.13518e-05])
|
|
195
|
+
|
|
196
|
+
t = np.log(T)
|
|
197
|
+
x = 1
|
|
198
|
+
temp1 = 0
|
|
199
|
+
temp2 = 0
|
|
200
|
+
for i in range(12):
|
|
201
|
+
temp1 += a[i] * x
|
|
202
|
+
temp2 += b[i] * x
|
|
203
|
+
x *= t
|
|
204
|
+
|
|
205
|
+
return temp1 / temp2 + 1
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def geff_s_h(T: float):
|
|
209
|
+
r"""High-temperature fit for the SM entropy degrees of freedom.
|
|
210
|
+
|
|
211
|
+
The quantity is defined by
|
|
212
|
+
|
|
213
|
+
.. math::
|
|
214
|
+
s_{\mathrm{SM}}(T)
|
|
215
|
+
= \frac{2\pi^2}{45}\, g_{\mathrm{eff},s}^{\mathrm{SM}}(T)\, T^3.
|
|
216
|
+
"""
|
|
217
|
+
return 1 / ratio(T) * geff_rho_h(T)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def fr(x: float):
|
|
221
|
+
"""Low-temperature fermionic energy-density kernel used in the tabulated SM fit."""
|
|
222
|
+
return np.exp(-1.04855 * x) * \
|
|
223
|
+
(1 + 1.03757 * x + 0.508630 * x * x + 0.0893988 * x * x * x)
|
|
224
|
+
|
|
225
|
+
def br(x: float):
|
|
226
|
+
"""Low-temperature bosonic energy-density kernel used in the tabulated SM fit."""
|
|
227
|
+
return np.exp(-1.03149 * x) * \
|
|
228
|
+
(1 + 1.03317 * x + 0.398264 * x * x + 0.0648056 * x * x * x)
|
|
229
|
+
|
|
230
|
+
def fs(x: float):
|
|
231
|
+
"""Low-temperature fermionic entropy-density kernel used in the tabulated SM fit."""
|
|
232
|
+
return np.exp(-1.04190 * x) * \
|
|
233
|
+
(1. + 1.03400 * x + 0.456426 * x * x + 0.0595248 * x * x * x)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def bs(x: float):
|
|
237
|
+
"""Low-temperature bosonic entropy-density kernel used in the tabulated SM fit."""
|
|
238
|
+
return np.exp(-1.03365 * x) * \
|
|
239
|
+
(1. + 1.03397 * x + 0.342548 * x * x + 0.0506182 * x * x * x)
|
|
240
|
+
|
|
241
|
+
def Sfit(x: float):
|
|
242
|
+
"""Auxiliary suppression factor appearing in the low-temperature SM fits."""
|
|
243
|
+
return 1. + 7. / 4. * np.exp(-1.0419 * x) * \
|
|
244
|
+
(1. + 1.034 * x + 0.456426 * x * x + 0.0595249 * x * x * x)
|
|
245
|
+
|
|
246
|
+
def geff_rho_l(T, p1 = 1, p2 = 1):
|
|
247
|
+
r"""Low-temperature fit for :math:`g_{\mathrm{eff},\rho}^{\mathrm{SM}}`.
|
|
248
|
+
|
|
249
|
+
Below the QCD crossover the fit is written as a sum of hadronic and leptonic
|
|
250
|
+
threshold functions and still satisfies
|
|
251
|
+
|
|
252
|
+
.. math::
|
|
253
|
+
\rho_{\mathrm{SM}}(T)
|
|
254
|
+
= \frac{\pi^2}{30}\, g_{\mathrm{eff},\rho}^{\mathrm{SM}}(T)\, T^4.
|
|
255
|
+
"""
|
|
256
|
+
me = 511e-6; mmu = 0.1056; mpi0 = 0.135; mpip = 0.140; m1 = 0.5
|
|
257
|
+
m2 = 0.77; m3 = 1.2; m4 = 2.0
|
|
258
|
+
return 2.030 + 1.353 * np.power(Sfit(me / T), 4. / 3.) + 3.495 * fr(me / T) + \
|
|
259
|
+
3.446 * fr(mmu / T) + 1.05 * br(mpi0 / T) + 2.08 * br(mpip / T) + \
|
|
260
|
+
4.165 * br(m1 / T) + 30.55 * br(m2 / T) + 89.4 * br(p2 * m3 / T) + \
|
|
261
|
+
8209 * br(p2 * m4 / T)
|
|
262
|
+
|
|
263
|
+
def geff_s_l(T: float, p1=1, p2=1):
|
|
264
|
+
r"""Low-temperature fit for :math:`g_{\mathrm{eff},s}^{\mathrm{SM}}`.
|
|
265
|
+
|
|
266
|
+
The fit is defined through
|
|
267
|
+
|
|
268
|
+
.. math::
|
|
269
|
+
s_{\mathrm{SM}}(T)
|
|
270
|
+
= \frac{2\pi^2}{45}\, g_{\mathrm{eff},s}^{\mathrm{SM}}(T)\, T^3.
|
|
271
|
+
"""
|
|
272
|
+
me = 511e-6; mmu = 0.1056; mpi0 = 0.135; mpip = 0.140; m1 = 0.5
|
|
273
|
+
m2 = 0.77; m3 = 1.2; m4 = 2.0
|
|
274
|
+
return 2.008 + 1.923 * np.power(Sfit(me / T), 1.) + 3.442 * fs(me / T) + \
|
|
275
|
+
3.468 * fs(mmu / T) + 1.034 * bs(mpi0 / T) + 2.068 * bs(mpip / T) + \
|
|
276
|
+
4.160 * bs(m1 / T) + 30.55 * bs(m2 / T) + 90 * bs(p2 * m3 / T) + \
|
|
277
|
+
6209 * br(p2 * m4 / T)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _e_geffSM_fit(T: float, CF: float):
|
|
281
|
+
"""Dispatch between the low- and high-temperature SM energy-density fits."""
|
|
282
|
+
if (T < 0.12):
|
|
283
|
+
return geff_rho_l(T)
|
|
284
|
+
return geff_rho_h(T)
|
|
285
|
+
|
|
286
|
+
e_geffSM_fit = np.vectorize(_e_geffSM_fit)
|
|
287
|
+
|
|
288
|
+
def _s_geffSM_fit(T: float, CF: float):
|
|
289
|
+
"""Dispatch between the low- and high-temperature SM entropy-density fits."""
|
|
290
|
+
if (T < 0.12):
|
|
291
|
+
return geff_s_l(T)
|
|
292
|
+
return geff_s_h(T)
|
|
293
|
+
|
|
294
|
+
s_geffSM_fit = np.vectorize(_s_geffSM_fit)
|
|
295
|
+
|
|
296
|
+
def p_geffSM_fit(TSM: float | np.ndarray, CF: float) -> float | np.ndarray:
|
|
297
|
+
"""
|
|
298
|
+
Effective pressure degress of freedom for the SM.
|
|
299
|
+
|
|
300
|
+
Parameters
|
|
301
|
+
----------
|
|
302
|
+
TSM : float or np.ndarray
|
|
303
|
+
Temperature in internal energy units.
|
|
304
|
+
CF : float
|
|
305
|
+
Conversion factor to put everything in GeV.
|
|
306
|
+
"""
|
|
307
|
+
TSM_arr = np.asarray(TSM, dtype=float)
|
|
308
|
+
TSM_GeV = TSM_arr * CF
|
|
309
|
+
if _sm_mass_cap_GeV is not None:
|
|
310
|
+
TSM_GeV = np.minimum(TSM_GeV, _sm_mass_cap_GeV)
|
|
311
|
+
s_val = np.asarray(s_geffSM_fit(TSM_GeV), dtype=float)
|
|
312
|
+
e_val = np.asarray(e_geffSM_fit(TSM_GeV), dtype=float)
|
|
313
|
+
p_base = 4 * s_val - 3 * e_val
|
|
314
|
+
|
|
315
|
+
return float(p_base) if np.asarray(p_base).shape == () else p_base
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
_sm_mass_cap_GeV: float | None = None
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def set_sm_temperature_cap(
|
|
322
|
+
CF: float,
|
|
323
|
+
mSq_bosons: tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray] | None = None,
|
|
324
|
+
boson_is_SM: np.ndarray | None = None,
|
|
325
|
+
mSq_fermions: tuple[np.ndarray, np.ndarray, np.ndarray] | None = None,
|
|
326
|
+
fermion_is_SM: np.ndarray | None = None,
|
|
327
|
+
verbose: bool = False,
|
|
328
|
+
) -> float | None:
|
|
329
|
+
"""Initialise the SM temperature cap from the spectrum at the true minimum.
|
|
330
|
+
|
|
331
|
+
Parameters
|
|
332
|
+
----------
|
|
333
|
+
CF : float
|
|
334
|
+
Conversion factor to GeV.
|
|
335
|
+
mSq_bosons, mSq_fermions : tuple, optional
|
|
336
|
+
Spectra evaluated at the true vacuum.
|
|
337
|
+
verbose : bool, optional
|
|
338
|
+
Print a warning containing the lightest SM mass when saturation is applied.
|
|
339
|
+
"""
|
|
340
|
+
global _sm_mass_cap_GeV
|
|
341
|
+
|
|
342
|
+
masses = []
|
|
343
|
+
selected_boson_m2 = np.array([], dtype=float)
|
|
344
|
+
selected_fermion_m2 = np.array([], dtype=float)
|
|
345
|
+
|
|
346
|
+
# Numerical massless floor:
|
|
347
|
+
# treat masses below one ULP of the characteristic SM m^2 scale as zero.
|
|
348
|
+
# The scale is computed in GeV^2 and mapped back to internal units with CF.
|
|
349
|
+
eps = np.finfo(float).eps
|
|
350
|
+
|
|
351
|
+
# Add only physical, non-BSM boson masses to the list
|
|
352
|
+
if mSq_bosons is not None:
|
|
353
|
+
m2b, _, _, physical = mSq_bosons
|
|
354
|
+
mask = (np.asarray(boson_is_SM, dtype=bool)) & np.asarray(physical, dtype=bool)
|
|
355
|
+
if np.any(mask):
|
|
356
|
+
selected = np.asarray(m2b, dtype=float)[..., mask]
|
|
357
|
+
selected_boson_m2 = np.abs(selected).ravel()
|
|
358
|
+
|
|
359
|
+
# Add only non-BSM fermion masses to the list
|
|
360
|
+
if mSq_fermions is not None:
|
|
361
|
+
m2f, _ = mSq_fermions
|
|
362
|
+
mask = np.asarray(fermion_is_SM, dtype=bool)
|
|
363
|
+
if np.any(mask):
|
|
364
|
+
selected = np.asarray(m2f, dtype=float)[..., mask]
|
|
365
|
+
selected_fermion_m2 = np.abs(selected).ravel()
|
|
366
|
+
|
|
367
|
+
selected_boson_m2 = selected_boson_m2[np.isfinite(selected_boson_m2)]
|
|
368
|
+
selected_fermion_m2 = selected_fermion_m2[np.isfinite(selected_fermion_m2)]
|
|
369
|
+
|
|
370
|
+
if selected_boson_m2.size:
|
|
371
|
+
m2_scale_b_internal = float(np.max(selected_boson_m2))
|
|
372
|
+
m2_scale_b_GeV2 = m2_scale_b_internal * CF * CF
|
|
373
|
+
m2_tol_b_internal = (eps * m2_scale_b_GeV2) / (CF * CF)
|
|
374
|
+
else:
|
|
375
|
+
m2_tol_b_internal = 0.0
|
|
376
|
+
|
|
377
|
+
if selected_fermion_m2.size:
|
|
378
|
+
m2_scale_f_internal = float(np.max(selected_fermion_m2))
|
|
379
|
+
m2_scale_f_GeV2 = m2_scale_f_internal * CF * CF
|
|
380
|
+
m2_tol_f_internal = (eps * m2_scale_f_GeV2) / (CF * CF)
|
|
381
|
+
else:
|
|
382
|
+
m2_tol_f_internal = 0.0
|
|
383
|
+
|
|
384
|
+
# Collect non-massless states using the numerical floor.
|
|
385
|
+
if mSq_bosons is not None:
|
|
386
|
+
m2b, _, _, physical = mSq_bosons
|
|
387
|
+
mask = (np.asarray(boson_is_SM, dtype=bool)) & np.asarray(physical, dtype=bool)
|
|
388
|
+
if np.any(mask):
|
|
389
|
+
selected = np.asarray(m2b, dtype=float)[..., mask]
|
|
390
|
+
masses.extend(np.sqrt(selected[selected > m2_tol_b_internal]).ravel())
|
|
391
|
+
|
|
392
|
+
if mSq_fermions is not None:
|
|
393
|
+
m2f, _ = mSq_fermions
|
|
394
|
+
mask = np.asarray(fermion_is_SM, dtype=bool)
|
|
395
|
+
if np.any(mask):
|
|
396
|
+
selected = np.asarray(m2f, dtype=float)[..., mask]
|
|
397
|
+
masses.extend(np.sqrt(selected[selected > m2_tol_f_internal]).ravel())
|
|
398
|
+
|
|
399
|
+
if masses:
|
|
400
|
+
lightest = float(np.min(masses))
|
|
401
|
+
previous_cap = _sm_mass_cap_GeV
|
|
402
|
+
_sm_mass_cap_GeV = 0.5 * lightest * CF
|
|
403
|
+
if verbose and previous_cap is None:
|
|
404
|
+
msg = (f"The lightest (non-massless) SM particle in the model "
|
|
405
|
+
f"file has a mass of {lightest * CF:.3g} GeV. The tabulated SM "
|
|
406
|
+
f"effective degrees of freedom will hence be capped at "
|
|
407
|
+
f"{_sm_mass_cap_GeV:.3g} GeV. The model will compute the "
|
|
408
|
+
f"effective degrees of freedom dynamically beyond that point.")
|
|
409
|
+
console.print(
|
|
410
|
+
f"[yellow]Warning: {msg}[/yellow]"
|
|
411
|
+
)
|
|
412
|
+
else:
|
|
413
|
+
_sm_mass_cap_GeV = None
|
|
414
|
+
|
|
415
|
+
return _sm_mass_cap_GeV
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def e_geffSM(
|
|
419
|
+
TSM: float | np.ndarray,
|
|
420
|
+
CF: float,
|
|
421
|
+
mode: str = "smooth"
|
|
422
|
+
) -> float | np.ndarray:
|
|
423
|
+
"""
|
|
424
|
+
Effective energy degrees of freedom of the SM
|
|
425
|
+
at a given temperature.
|
|
426
|
+
|
|
427
|
+
Parameters
|
|
428
|
+
----------
|
|
429
|
+
TSM : float or np.ndarray
|
|
430
|
+
Temperature in internal energy units.
|
|
431
|
+
CF : float
|
|
432
|
+
Conversion factor to put everything in GeV.
|
|
433
|
+
mode : str, optional
|
|
434
|
+
"smooth", "fit" or "data"
|
|
435
|
+
"""
|
|
436
|
+
TSM_arr = np.asarray(TSM, dtype=float)
|
|
437
|
+
capped_T = TSM_arr * CF
|
|
438
|
+
if _sm_mass_cap_GeV is not None:
|
|
439
|
+
capped_T = np.minimum(capped_T, _sm_mass_cap_GeV)
|
|
440
|
+
if mode == "smooth":
|
|
441
|
+
result = np.asarray(Ie_geffSM_smooth(np.log10(capped_T)), dtype=float)
|
|
442
|
+
elif mode == "data":
|
|
443
|
+
result = np.asarray(Ie_geffSM(np.log10(capped_T)), dtype=float)
|
|
444
|
+
elif mode == "fit":
|
|
445
|
+
result = e_geffSM_fit(TSM, CF)
|
|
446
|
+
|
|
447
|
+
return float(result) if result.shape == () else result
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def s_geffSM(TSM: float | np.ndarray, CF: float, mode: str = "smooth"
|
|
451
|
+
) -> float | np.ndarray:
|
|
452
|
+
"""
|
|
453
|
+
Effective entropy degrees of freedom of the SM
|
|
454
|
+
at a given temperature.
|
|
455
|
+
|
|
456
|
+
Parameters
|
|
457
|
+
----------
|
|
458
|
+
TSM : float or np.ndarray
|
|
459
|
+
Temperature in internal energy units.
|
|
460
|
+
CF : float
|
|
461
|
+
Conversion factor to put everything in GeV.
|
|
462
|
+
mode : str, optional
|
|
463
|
+
"fit", "smooth" or "data".
|
|
464
|
+
"""
|
|
465
|
+
TSM_arr = np.asarray(TSM, dtype=float)
|
|
466
|
+
capped_T = TSM_arr * CF
|
|
467
|
+
if _sm_mass_cap_GeV is not None:
|
|
468
|
+
capped_T = np.minimum(capped_T, _sm_mass_cap_GeV)
|
|
469
|
+
if mode == "smooth":
|
|
470
|
+
result = np.asarray(Is_geffSM_smooth(np.log10(capped_T)), dtype=float)
|
|
471
|
+
elif mode == "data":
|
|
472
|
+
result = np.asarray(Is_geffSM(np.log10(capped_T)), dtype=float)
|
|
473
|
+
elif mode == "fit":
|
|
474
|
+
result = s_geffSM_fit(TSM, CF)
|
|
475
|
+
|
|
476
|
+
return float(result) if result.shape == () else result
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def p_geffSM(TSM: float | np.ndarray, CF: float, mode: str = "smooth"
|
|
480
|
+
) -> float | np.ndarray:
|
|
481
|
+
"""
|
|
482
|
+
Effective pressure degress of freedom for the SM.
|
|
483
|
+
|
|
484
|
+
Parameters
|
|
485
|
+
----------
|
|
486
|
+
TSM : float or np.ndarray
|
|
487
|
+
Temperature in internal energy units.
|
|
488
|
+
CF : float
|
|
489
|
+
Conversion factor to put everything in GeV.
|
|
490
|
+
mode : str, optional
|
|
491
|
+
Either "fit", "smooth" or "data".
|
|
492
|
+
"""
|
|
493
|
+
TSM_arr = np.asarray(TSM, dtype=float)
|
|
494
|
+
TSM_GeV = TSM_arr * CF
|
|
495
|
+
if _sm_mass_cap_GeV is not None:
|
|
496
|
+
TSM_GeV = np.minimum(TSM_GeV, _sm_mass_cap_GeV)
|
|
497
|
+
if mode == "smooth":
|
|
498
|
+
s_val = np.asarray(Is_geffSM_smooth(np.log10(TSM_GeV)), dtype=float)
|
|
499
|
+
e_val = np.asarray(Ie_geffSM_smooth(np.log10(TSM_GeV)), dtype=float)
|
|
500
|
+
elif mode == "data":
|
|
501
|
+
s_val = np.asarray(Is_geffSM(np.log10(TSM_GeV)), dtype=float)
|
|
502
|
+
e_val = np.asarray(Ie_geffSM(np.log10(TSM_GeV)), dtype=float)
|
|
503
|
+
elif mode == "fit":
|
|
504
|
+
s_val = s_geffSM_fit(TSM, CF)
|
|
505
|
+
e_val = e_geffSM_fit(TSM, CF)
|
|
506
|
+
p_base = 4 * s_val - 3 * e_val
|
|
507
|
+
|
|
508
|
+
return float(p_base) if np.asarray(p_base).shape == () else p_base
|
|
509
|
+
|
|
510
|
+
def nDensity(m, T, g, ptype):
|
|
511
|
+
"""Numberdensity from tabulated values."""
|
|
512
|
+
pref = special.zeta(3)/np.pi**2 * g * T**3
|
|
513
|
+
if T == 0.0:
|
|
514
|
+
return 0.0
|
|
515
|
+
x = m/T
|
|
516
|
+
res = 0.0
|
|
517
|
+
if ptype == "b":
|
|
518
|
+
if x < Inb.x[0]:
|
|
519
|
+
res = pref
|
|
520
|
+
elif x < Inb.x[-1]:
|
|
521
|
+
res = pref * Inb(x)
|
|
522
|
+
elif ptype == "f":
|
|
523
|
+
if x < Inb.x[0]:
|
|
524
|
+
res = (3/4) * pref
|
|
525
|
+
elif x < Inb.x[-1]:
|
|
526
|
+
res = pref * Inf(x)
|
|
527
|
+
return res
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def e_geff(particles: BaseParticle | list[BaseParticle], X: np.ndarray, T: float | np.ndarray):
|
|
531
|
+
"""
|
|
532
|
+
Calculate the particles effective degrees of freedom.
|
|
533
|
+
"""
|
|
534
|
+
T += 1e-100
|
|
535
|
+
|
|
536
|
+
if not isinstance(particles, list):
|
|
537
|
+
particles = [particles]
|
|
538
|
+
|
|
539
|
+
geff = np.zeros_like(X)
|
|
540
|
+
|
|
541
|
+
for p in particles:
|
|
542
|
+
masses = p.evaluate_prefactor(X)
|
|
543
|
+
if masses is None:
|
|
544
|
+
raise AttributeError(
|
|
545
|
+
"Thermodynamic routines require particles with thermal mass prefactors"
|
|
546
|
+
)
|
|
547
|
+
m = np.sqrt(np.asarray(masses))
|
|
548
|
+
x = m / T
|
|
549
|
+
res = np.zeros_like(X)
|
|
550
|
+
if p.statistic == "fermion":
|
|
551
|
+
res = p.dof * np.where(x < Ief.x[0], 7 / 8, res)
|
|
552
|
+
res = p.dof * np.where((x >= Ief.x[0]) & (x < Ief.x[-1]), Ief(x), res)
|
|
553
|
+
elif p.statistic == "boson":
|
|
554
|
+
res = p.dof * np.where(x < Ieb.x[0], 1.0, res)
|
|
555
|
+
res = p.dof * np.where((x >= Ieb.x[0]) & (x < Ieb.x[-1]), Ieb(x), res)
|
|
556
|
+
geff += res
|
|
557
|
+
return geff
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def p_geff(particles: BaseParticle | list[BaseParticle], X: np.ndarray, T: float | np.ndarray):
|
|
561
|
+
"""
|
|
562
|
+
Calculate the particles pressure effective degrees of freedom.
|
|
563
|
+
"""
|
|
564
|
+
T += 1e-100
|
|
565
|
+
|
|
566
|
+
if not isinstance(particles, list):
|
|
567
|
+
particles = [particles]
|
|
568
|
+
|
|
569
|
+
geff = np.zeros_like(X)
|
|
570
|
+
|
|
571
|
+
for p in particles:
|
|
572
|
+
masses = p.evaluate_prefactor(X)
|
|
573
|
+
if masses is None:
|
|
574
|
+
raise AttributeError(
|
|
575
|
+
"Thermodynamic routines require particles with thermal mass prefactors"
|
|
576
|
+
)
|
|
577
|
+
m = np.sqrt(np.asarray(masses))
|
|
578
|
+
x = m / T
|
|
579
|
+
res = np.zeros_like(X)
|
|
580
|
+
if p.statistic == "fermion":
|
|
581
|
+
res = p.dof * np.where(x < Ipf.x[0], 7 / 8, res)
|
|
582
|
+
res = p.dof * np.where((x >= Ipf.x[0]) & (x < Ipf.x[-1]), Ipf(x), res)
|
|
583
|
+
elif p.statistic == "boson":
|
|
584
|
+
res = p.dof * np.where(x < Ipb.x[0], 1.0, res)
|
|
585
|
+
res = p.dof * np.where((x >= Ipb.x[0]) & (x < Ipb.x[-1]), Ipb(x), res)
|
|
586
|
+
geff += res
|
|
587
|
+
return geff
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def e_geff(m, T, g, ptype):
|
|
591
|
+
"""Energy degrees of freedom, supports scalar or vector inputs for m and T."""
|
|
592
|
+
m = np.asarray(m) # Ensure m is a numpy array
|
|
593
|
+
T = np.asarray(T) # Ensure T is a numpy array
|
|
594
|
+
T = T + 1e-100
|
|
595
|
+
x = np.where(T != 0, m / T, np.inf) # Avoid division by zero
|
|
596
|
+
res = np.zeros_like(x)
|
|
597
|
+
|
|
598
|
+
if ptype == "b":
|
|
599
|
+
res = np.where(x < Ieb.x[0], 1.0, res)
|
|
600
|
+
res = np.where((x >= Ieb.x[0]) & (x < Ieb.x[-1]), Ieb(x), res)
|
|
601
|
+
elif ptype == "f":
|
|
602
|
+
res = np.where(x < Ief.x[0], 7 / 8, res)
|
|
603
|
+
res = np.where((x >= Ief.x[0]) & (x < Ief.x[-1]), Ief(x), res)
|
|
604
|
+
return res * g
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def p_geff(m, T, g, ptype):
|
|
608
|
+
"""Pressure degrees of freedom, supports scalar or vector inputs for m and T."""
|
|
609
|
+
if T == 0.0:
|
|
610
|
+
return 0.0
|
|
611
|
+
x = m/T
|
|
612
|
+
res = 0.0
|
|
613
|
+
if ptype == "b":
|
|
614
|
+
if x < Ipb.x[0]:
|
|
615
|
+
res = 1.0
|
|
616
|
+
elif x < Ipb.x[-1]:
|
|
617
|
+
res = Ipb(x)
|
|
618
|
+
elif ptype == "f":
|
|
619
|
+
if x < Ipf.x[0]:
|
|
620
|
+
res = (7/8)
|
|
621
|
+
elif x < Ipf.x[-1]:
|
|
622
|
+
res = Ipf(x)
|
|
623
|
+
return res * g
|
|
624
|
+
|
|
625
|
+
def s_geff(m, T, g, ptype):
|
|
626
|
+
"""Entropy degrees of freedom, supports scalar or vector inputs for m and T."""
|
|
627
|
+
return (3*e_geff(m, T, g, ptype) + p_geff(m, T, g, ptype))/4
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def s_geffDS(mSq_bosons, mSq_fermions, T):
|
|
631
|
+
"""Entropy degrees of freedom of the dark sector"""
|
|
632
|
+
m2b, gb, c, physical = mSq_bosons
|
|
633
|
+
m2f, gf = mSq_fermions
|
|
634
|
+
|
|
635
|
+
geff = 0.0
|
|
636
|
+
for i in range(len(m2b)):
|
|
637
|
+
if physical[i]:
|
|
638
|
+
if m2b[i] > 0:
|
|
639
|
+
geff += s_geff(np.sqrt(m2b[i]), T, gb[i], 'b')
|
|
640
|
+
else:
|
|
641
|
+
# In general, tachyonic modes should not be included
|
|
642
|
+
# in the effective degrees of freedom, but we include them
|
|
643
|
+
# here, assuming that in the plasma they
|
|
644
|
+
# are not tachyonic anymore. One example is the SM Higgs
|
|
645
|
+
# field, which is tachyonic in the symmetric phase,
|
|
646
|
+
# but still contributes to the effective degrees of freedom
|
|
647
|
+
# with 4 degrees of freedom before the phase transition.
|
|
648
|
+
# Truly tachyonic modes, which also
|
|
649
|
+
# don't receive large enough plasma masses in order to have
|
|
650
|
+
# m^2 > 0, should be excluded in the sum.
|
|
651
|
+
# This should be checked in the model / be implemented through the
|
|
652
|
+
# physical flag.
|
|
653
|
+
geff += gb[i]
|
|
654
|
+
for i in range(len(m2f)):
|
|
655
|
+
geff += s_geff(np.sqrt(m2f[i]), T, gf[i], 'f')
|
|
656
|
+
return geff
|
|
657
|
+
|
|
658
|
+
def e_geffDS(mSq_bosons, mSq_fermions, T):
|
|
659
|
+
"""Energy degrees of freedom of the dark sector, supports vectorized T."""
|
|
660
|
+
m2b, gb, c, physical = mSq_bosons
|
|
661
|
+
m2f, gf = mSq_fermions
|
|
662
|
+
|
|
663
|
+
T = np.asarray(T) # Ensure T is a numpy array
|
|
664
|
+
geff = np.zeros_like(T, dtype=float)
|
|
665
|
+
|
|
666
|
+
for i in range(len(m2b)):
|
|
667
|
+
if physical[i]:
|
|
668
|
+
if m2b[i] > 0:
|
|
669
|
+
geff += e_geff(np.sqrt(m2b[i]), T, gb[i], 'b')
|
|
670
|
+
else:
|
|
671
|
+
# In general, tachyonic modes should not be included
|
|
672
|
+
# in the effective degrees of freedom, but we include them
|
|
673
|
+
# here, assuming that in the plasma they
|
|
674
|
+
# are not tachyonic anymore. One example is the SM Higgs
|
|
675
|
+
# field, which is tachyonic in the symmetric phase,
|
|
676
|
+
# but still contributes to the effective degrees of freedom
|
|
677
|
+
# with 4 degrees of freedom before the phase transition.
|
|
678
|
+
# Truly tachyonic modes, which also
|
|
679
|
+
# don't receive large enough plasma masses in order to have
|
|
680
|
+
# m^2 > 0, should be excluded in the sum.
|
|
681
|
+
# This should be checked in the model / be implemented through the
|
|
682
|
+
# physical flag.
|
|
683
|
+
geff += gb[i] # modes with negative mass are inlcuded
|
|
684
|
+
for i in range(len(m2f)):
|
|
685
|
+
geff += e_geff(np.sqrt(m2f[i]), T, gf[i], 'f')
|
|
686
|
+
return geff
|