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,2929 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module is used to calculate the bubble dynamics in the dark sector.
|
|
3
|
+
It contains functions to calculate the energy density, effective degrees of freedom,
|
|
4
|
+
the Hubble parameter, the bubble nucleation rate, and from this the nucleation
|
|
5
|
+
and percolation temperature.
|
|
6
|
+
|
|
7
|
+
Part of TransitionListener v2.0
|
|
8
|
+
Documentation: https://tasillo.de/TransitionListener/
|
|
9
|
+
|
|
10
|
+
Authors:
|
|
11
|
+
Jonas Matuszak <jonas.matuszak@kit.edu>
|
|
12
|
+
Carlo Tasillo <carlo.tasillo@ific.uv.es>
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from scipy import optimize
|
|
18
|
+
from scipy import interpolate
|
|
19
|
+
from scipy import integrate
|
|
20
|
+
|
|
21
|
+
from transitionlistener import thermodynamics as td
|
|
22
|
+
from transitionlistener import constants as cn
|
|
23
|
+
from transitionlistener.helper_functions import derivative
|
|
24
|
+
from transitionlistener import errors
|
|
25
|
+
from transitionlistener.pathDeformation import bounceAction
|
|
26
|
+
from transitionlistener.finiteT import Jb_spline as Jb
|
|
27
|
+
from transitionlistener.finiteT import Jf_spline as Jf
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def g_eff_DS(T_DS: float, pot, phase) -> float:
|
|
31
|
+
"""Calculate the effective energy degrees of freedom in the dark sector.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
T : float
|
|
36
|
+
Temperature at which to evaluate g_eff
|
|
37
|
+
pot : generic_potential
|
|
38
|
+
The effetive potential object
|
|
39
|
+
phase : PhaseInfo
|
|
40
|
+
The phase of the system, either symmetric or broken.
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
----------
|
|
44
|
+
float
|
|
45
|
+
Effective degrees of freedom in the dark sector."""
|
|
46
|
+
try:
|
|
47
|
+
vevT = phase.valAt(T_DS)
|
|
48
|
+
except BaseException:
|
|
49
|
+
print("Warning: TBRO is too low for interpolation of vev, using T = 0 value")
|
|
50
|
+
# Temperature is to low for interpolation of vev, use T = 0 value
|
|
51
|
+
vevT = pot.X0
|
|
52
|
+
bosons = pot.boson_massSq(vevT, 0)*(~pot.mass_spectrum.is_SM_bosons)
|
|
53
|
+
fermions = pot.fermion_massSq(vevT)*(~pot.mass_spectrum.is_SM_fermions)
|
|
54
|
+
geff = td.e_geffDS(bosons, fermions, T_DS)
|
|
55
|
+
return geff
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def h_eff_DS(T_DS: float, pot, phase) -> float:
|
|
59
|
+
"""Calculate the effective entropy degrees of freedom in the dark sector.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
T : float
|
|
64
|
+
Temperature at which to evaluate g_eff
|
|
65
|
+
pot : generic_potential
|
|
66
|
+
The effetive potential object
|
|
67
|
+
phase : PhaseInfo
|
|
68
|
+
The phase of the system, either symmetric or broken.
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
----------
|
|
72
|
+
geff : float
|
|
73
|
+
Effective degrees of freedom in the dark sector."""
|
|
74
|
+
try:
|
|
75
|
+
vevT = phase.valAt(T_DS)
|
|
76
|
+
except BaseException:
|
|
77
|
+
print("Warning: TBRO is too low for interpolation of vev, using T = 0 value")
|
|
78
|
+
# Temperature is to low for interpolation of vev, use T = 0 value
|
|
79
|
+
vevT = pot.X0
|
|
80
|
+
|
|
81
|
+
# Set SM masses to zero:
|
|
82
|
+
bosons = pot.boson_massSq(vevT, 0)*(~pot.mass_spectrum.is_SM_bosons)
|
|
83
|
+
fermions = pot.fermion_massSq(vevT)*(~pot.mass_spectrum.is_SM_fermions)
|
|
84
|
+
geff = td.s_geffDS(bosons, fermions, T_DS)
|
|
85
|
+
return geff
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def energyDensity(pot, phase, T: float | np.ndarray, include_decoupled=True) -> float | np.ndarray:
|
|
89
|
+
r"""This function calls the implementation in the effective potential.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
pot : generic_potential
|
|
94
|
+
Effective potential
|
|
95
|
+
phase : PhaseInfo
|
|
96
|
+
The phase information
|
|
97
|
+
T : float|np.ndarray
|
|
98
|
+
The temperature at which to compute the energy density
|
|
99
|
+
include_decoupled : bool, optional
|
|
100
|
+
If true, also account for the energy density in a decoupled sector
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
float|np.ndarray :
|
|
105
|
+
The energy density at `T`."""
|
|
106
|
+
X = phase.valAt(T)
|
|
107
|
+
return pot.energyDensity(X, T, include_decoupled=include_decoupled)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def Gamma(T: float | np.ndarray, S: float | np.ndarray) -> np.ndarray:
|
|
111
|
+
"""Calculate the bubble nucleation rate.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
T : float | np.ndarray
|
|
116
|
+
Symmetric phase temperature
|
|
117
|
+
S : float | np.ndarray
|
|
118
|
+
The action at temperature T
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
----------
|
|
122
|
+
Gamma : np.ndarray
|
|
123
|
+
The bubble nucleation rate."""
|
|
124
|
+
S = np.atleast_1d(S)
|
|
125
|
+
T = np.atleast_1d(T)
|
|
126
|
+
result = np.zeros_like(T, dtype=float)
|
|
127
|
+
|
|
128
|
+
mask_zero = S == 0
|
|
129
|
+
mask_inf = np.isinf(S)
|
|
130
|
+
mask_valid = ~(mask_zero | mask_inf)
|
|
131
|
+
|
|
132
|
+
result[mask_zero] = np.inf
|
|
133
|
+
result[mask_inf] = np.nan
|
|
134
|
+
# Ignore RuntimeWarnings in sqrt and exp (e.g., for invalid or overflow values)
|
|
135
|
+
with np.errstate(invalid="ignore", over="ignore"):
|
|
136
|
+
result[mask_valid] = (
|
|
137
|
+
T[mask_valid] ** 4
|
|
138
|
+
* np.sqrt(S[mask_valid] / (2 * np.pi * T[mask_valid])) ** 3
|
|
139
|
+
* np.exp(-S[mask_valid] / T[mask_valid])
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return result
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def logGamma(T: float | np.ndarray, S: float | np.ndarray) -> np.ndarray:
|
|
146
|
+
"""Calculate the log10 of the bubble nucleation rate.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
T : float | np.ndarray
|
|
151
|
+
Symmetric phase temperature
|
|
152
|
+
S : float | np.ndarray
|
|
153
|
+
The action at temperature T
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
----------
|
|
157
|
+
Gamma : np.ndarray
|
|
158
|
+
The bubble nucleation rate."""
|
|
159
|
+
S = np.atleast_1d(S)
|
|
160
|
+
T = np.atleast_1d(T)
|
|
161
|
+
result = np.zeros_like(T, dtype=float)
|
|
162
|
+
|
|
163
|
+
mask_zero = S == 0
|
|
164
|
+
mask_inf = np.isinf(S)
|
|
165
|
+
mask_valid = ~(mask_zero | mask_inf)
|
|
166
|
+
|
|
167
|
+
result[mask_zero] = np.inf
|
|
168
|
+
result[mask_inf] = np.nan
|
|
169
|
+
# Ignore RuntimeWarnings in sqrt and exp (e.g., for invalid or overflow values)
|
|
170
|
+
with np.errstate(invalid="ignore", over="ignore"):
|
|
171
|
+
result[mask_valid] = (
|
|
172
|
+
4 * np.log(T[mask_valid])
|
|
173
|
+
+ 3 / 2 * np.log(S[mask_valid] / (2 * np.pi * T[mask_valid]))
|
|
174
|
+
- S[mask_valid] / T[mask_valid]
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return result
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def HubbleParameter(rho: float | np.ndarray, CF: float) -> float:
|
|
181
|
+
"""Hubble parameter.
|
|
182
|
+
|
|
183
|
+
Parameters
|
|
184
|
+
----------
|
|
185
|
+
rho : float
|
|
186
|
+
The energy density of the unverse.
|
|
187
|
+
CF : float
|
|
188
|
+
Conversion factor to convert internal units to GeV.
|
|
189
|
+
Returns
|
|
190
|
+
-------
|
|
191
|
+
float :
|
|
192
|
+
The Hubble parameter."""
|
|
193
|
+
return np.sqrt(8 * np.pi / 3 * rho) / (cn.Mpl_GeV / CF)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def scalefactorRatio(pot, T1, T2, X) -> float:
|
|
197
|
+
"""Compute the scalefactor ratio a(T1)/a(T2)
|
|
198
|
+
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
|
|
205
|
+
"""
|
|
206
|
+
if T1 == T2:
|
|
207
|
+
return 1
|
|
208
|
+
n = 100
|
|
209
|
+
Trange = np.linspace(T1, T2, n)
|
|
210
|
+
dVdT = np.array([pot.dVdT(X, T, dT=0.1) for T in Trange])
|
|
211
|
+
d2VdT2 = np.array([pot.d2VdT2(X, T) for T in Trange])
|
|
212
|
+
|
|
213
|
+
intgr = integrate.trapezoid(d2VdT2/(3*dVdT), x=Trange)
|
|
214
|
+
return np.exp(intgr)
|
|
215
|
+
|
|
216
|
+
def calcAction(pot, T: float, start_phase, end_phase, outdict: dict, verbose: bool = False,
|
|
217
|
+
phitol: float = 1e-6) -> float:
|
|
218
|
+
"""Calculate the action at temperature `T`
|
|
219
|
+
|
|
220
|
+
Parameters
|
|
221
|
+
----------
|
|
222
|
+
pot : generic_potential
|
|
223
|
+
The effective potential object
|
|
224
|
+
T : float
|
|
225
|
+
The temperature
|
|
226
|
+
start_phase : PhaseInfo
|
|
227
|
+
The information about the high temperature phase
|
|
228
|
+
end_phase : PhaseInfo
|
|
229
|
+
The information about the low temperature phase
|
|
230
|
+
outdict : dict
|
|
231
|
+
The dictionary storing the action evaluations with the key `T`.
|
|
232
|
+
verbose : bool, optional
|
|
233
|
+
Set the output level
|
|
234
|
+
phitol : float, optional
|
|
235
|
+
Set the accuracy of the minimisation of the potential minima.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
float :
|
|
240
|
+
The action at temperature `T`."""
|
|
241
|
+
|
|
242
|
+
x0 = findLocalMinimum(start_phase.valAt(T), T, pot.gradV, pot.d2V)
|
|
243
|
+
x1 = findLocalMinimum(end_phase.valAt(T), T, pot.gradV, pot.d2V)
|
|
244
|
+
tdict = dict(low_vev=x1, high_vev=x0)
|
|
245
|
+
|
|
246
|
+
if T in outdict:
|
|
247
|
+
return outdict[T]["action"]
|
|
248
|
+
|
|
249
|
+
outdict = bounceAction(
|
|
250
|
+
T, pot.Vtot, pot.gradV, outdict, tdict, verbose, pot.conversionFactor,
|
|
251
|
+
**pot.config.tracingConf.tunneling_params)
|
|
252
|
+
return outdict[T]["action"]
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def approxPercIntegral(
|
|
256
|
+
Tperc: float,
|
|
257
|
+
logG: float,
|
|
258
|
+
Tnuc: float,
|
|
259
|
+
beta: float,
|
|
260
|
+
alphasq: float,
|
|
261
|
+
pot,
|
|
262
|
+
phase,
|
|
263
|
+
verbose: bool = False,
|
|
264
|
+
) -> float:
|
|
265
|
+
r"""Calculate the percolation integral up to the gaussian
|
|
266
|
+
approximation.
|
|
267
|
+
|
|
268
|
+
.. math::
|
|
269
|
+
I = \int_{-\infty}^{t_p} d t (t_p - t) \Gamma_p
|
|
270
|
+
\exp(\beta (t - t_p) - a^2/2(t - t_p)^2)
|
|
271
|
+
|
|
272
|
+
Parameters
|
|
273
|
+
----------
|
|
274
|
+
Tperc : float,
|
|
275
|
+
The percolation temperature
|
|
276
|
+
logG : float
|
|
277
|
+
:math:`\log(\Gamma_p)`, the log of the nucleation rate
|
|
278
|
+
Tnuc : float
|
|
279
|
+
The nucleation temperature
|
|
280
|
+
beta : float
|
|
281
|
+
First derivative of the action w.r.t. time: :math:`d/dt (S_3/T)`
|
|
282
|
+
alphasq : float
|
|
283
|
+
Second derivative of the action w.r.t. time :math:`d^2/dt^2 (S_3/T)`
|
|
284
|
+
pot : "generic_potential"
|
|
285
|
+
The effective potential
|
|
286
|
+
phase: PhaseInfo
|
|
287
|
+
The current phase
|
|
288
|
+
verbose: bool, optional
|
|
289
|
+
Set the output level
|
|
290
|
+
Returns
|
|
291
|
+
-------
|
|
292
|
+
float:
|
|
293
|
+
The percolation interal.
|
|
294
|
+
"""
|
|
295
|
+
assert Tperc <= Tnuc
|
|
296
|
+
|
|
297
|
+
# T to temperature:
|
|
298
|
+
Trange = np.linspace(Tperc, Tnuc, num=200)
|
|
299
|
+
rho_range = np.zeros_like(Trange)
|
|
300
|
+
for i, T in enumerate(Trange):
|
|
301
|
+
rho_range[i] = energyDensity(pot, phase, T)
|
|
302
|
+
integr = 1 / (Trange * HubbleParameter(rho_range, pot.conversionFactor))
|
|
303
|
+
trange = integrate.cumulative_trapezoid(integr, x=Trange, initial=0)
|
|
304
|
+
trange = trange - trange[-1] # set tperc = 0
|
|
305
|
+
|
|
306
|
+
def fG(t):
|
|
307
|
+
res = (-t) ** 3 * np.exp(logG + beta * t - 1 / 2 * alphasq * t**2)
|
|
308
|
+
return res
|
|
309
|
+
|
|
310
|
+
Iperc = integrate.trapezoid(fG(trange), x=trange)
|
|
311
|
+
return Iperc
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def percIntegral(T: np.ndarray, H: np.ndarray, S: np.ndarray, vw=1.0) -> float:
|
|
315
|
+
"""Perform the percolation integral beteen Tstart and Tend.
|
|
316
|
+
|
|
317
|
+
Parameters
|
|
318
|
+
----------
|
|
319
|
+
T : np.ndarray
|
|
320
|
+
Temperature of the dark sector symmetric phase. It must start with the
|
|
321
|
+
nucleation temperature and go down to the percolation temp.
|
|
322
|
+
H : np.ndarray
|
|
323
|
+
Hubble rate evaluated at T
|
|
324
|
+
S : np.ndarray
|
|
325
|
+
S3 Euclidian bounce action evaluated at T
|
|
326
|
+
vw : float
|
|
327
|
+
|
|
328
|
+
Returns
|
|
329
|
+
-------
|
|
330
|
+
float
|
|
331
|
+
The integral evaluated at the last temperature ``T[-1]``.
|
|
332
|
+
"""
|
|
333
|
+
# See eq. (4.57) in 2305.02357
|
|
334
|
+
# The ordering of the array is important
|
|
335
|
+
if len(T) > 1:
|
|
336
|
+
if not T[0] >= T[1]:
|
|
337
|
+
raise errors.PercolationError("T is not decreasing in the percolation integral.")
|
|
338
|
+
|
|
339
|
+
vol_int = np.array([integrate.trapezoid(1 / H[i:], x=T[i:]) for i in range(len(T))])
|
|
340
|
+
with np.errstate(invalid="ignore"):
|
|
341
|
+
integrant = Gamma(T, S) / T**4 / H * vol_int**3
|
|
342
|
+
y = integrate.trapezoid(np.nan_to_num(integrant), x=T)
|
|
343
|
+
return 4 * np.pi / 3 * vw**3 * y
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def percIntegralwExp(T: np.ndarray, H: np.ndarray, S: np.ndarray,
|
|
347
|
+
scaleF_ratio: np.ndarray,
|
|
348
|
+
soundSpeedSq: np.ndarray,
|
|
349
|
+
vw=1.0) -> float:
|
|
350
|
+
"""Perform the percolation integral beteen Tstart and Tend.
|
|
351
|
+
Take the expansion of the universe into account.
|
|
352
|
+
|
|
353
|
+
Parameters
|
|
354
|
+
----------
|
|
355
|
+
T : np.ndarray
|
|
356
|
+
Temperature of the dark sector symmetric phase. It must start with the
|
|
357
|
+
nucleation temperature and go down to the percolation temp.
|
|
358
|
+
H : np.ndarray
|
|
359
|
+
Hubble rate evaluated at T
|
|
360
|
+
S : np.ndarray
|
|
361
|
+
S3 Euclidian bounce action evaluated at T
|
|
362
|
+
vw : float
|
|
363
|
+
|
|
364
|
+
Returns
|
|
365
|
+
-------
|
|
366
|
+
float
|
|
367
|
+
The integral evaluated at the last temperature ``T[-1]``.
|
|
368
|
+
"""
|
|
369
|
+
# See eq. (4.57) in 2305.02357
|
|
370
|
+
# The ordering of the array is important
|
|
371
|
+
if len(T) > 1:
|
|
372
|
+
if not T[0] >= T[1]:
|
|
373
|
+
raise errors.PercolationError("T is not decreasing in the percolation integral.")
|
|
374
|
+
|
|
375
|
+
vol_int = np.zeros_like(T)
|
|
376
|
+
for i in range(len(T)):
|
|
377
|
+
vol_int[i] = integrate.trapezoid(1/(3*H[i:]*T[i:]*soundSpeedSq[i:]*scaleF_ratio[i:]),
|
|
378
|
+
x=T[i:])
|
|
379
|
+
with np.errstate(invalid="ignore"):
|
|
380
|
+
integrant = Gamma(T, S) * scaleF_ratio**3 / (3*H*T*soundSpeedSq) * vol_int**3
|
|
381
|
+
y = integrate.trapezoid(np.nan_to_num(integrant), x=T)
|
|
382
|
+
return 4 * np.pi / 3 * vw**3 * y
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def approxNucleationCriterion(T_DS: float, S: float, pot, phase_sym, phase_bro) -> float:
|
|
386
|
+
r"""
|
|
387
|
+
Calculate the nucleation criterion for a given temperature and action.
|
|
388
|
+
This function computes the nucleation criterion based on the following equation:
|
|
389
|
+
|
|
390
|
+
.. math::
|
|
391
|
+
|
|
392
|
+
\frac{\Gamma}{H^4} = 1
|
|
393
|
+
|
|
394
|
+
where :math:`\Gamma` denotes the bubble nucleation rate and :math:`H`
|
|
395
|
+
is the Hubble parameter during radiation domination.
|
|
396
|
+
|
|
397
|
+
Parameters
|
|
398
|
+
----------
|
|
399
|
+
T_DS : float
|
|
400
|
+
Temperature of the DS.
|
|
401
|
+
S : float
|
|
402
|
+
Action at temperature TDS
|
|
403
|
+
pot : generic_potential
|
|
404
|
+
The effective potential object
|
|
405
|
+
phase_sym : PhaseInfo
|
|
406
|
+
The information about the high temperature phase
|
|
407
|
+
phase_bro : PhaseInfo
|
|
408
|
+
The information about the low temperature phase
|
|
409
|
+
|
|
410
|
+
Returns
|
|
411
|
+
-------
|
|
412
|
+
float
|
|
413
|
+
Result of the nucleation criterion, 0 if fulfilled.
|
|
414
|
+
"""
|
|
415
|
+
T_DS += 1e-100
|
|
416
|
+
|
|
417
|
+
if S == -np.inf:
|
|
418
|
+
return np.inf
|
|
419
|
+
elif S == np.inf:
|
|
420
|
+
return -np.inf
|
|
421
|
+
# This approximation identifies the PT-sector temperature with T_DS and
|
|
422
|
+
# keeps any hidden-sector temperature ratio temperature independent.
|
|
423
|
+
T = T_DS
|
|
424
|
+
|
|
425
|
+
rho = energyDensity(pot, phase_sym, T)
|
|
426
|
+
H = HubbleParameter(rho, pot.conversionFactor)
|
|
427
|
+
logG = logGamma(T_DS, S)[0] # Returnvalue is an array
|
|
428
|
+
crit = logG - 4 * np.log(H)
|
|
429
|
+
# crit is > 0 if more than one bubble is nucleated until T
|
|
430
|
+
# crit is < 0 if less than one bubble is nucleated until T
|
|
431
|
+
|
|
432
|
+
return crit
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def ApproxPercCritSecondOrder(
|
|
436
|
+
T: float,
|
|
437
|
+
Tnuc: float,
|
|
438
|
+
outdict: dict,
|
|
439
|
+
pot,
|
|
440
|
+
phase_sym,
|
|
441
|
+
phase_bro,
|
|
442
|
+
Tmax: float,
|
|
443
|
+
Tmin: float,
|
|
444
|
+
vw: float,
|
|
445
|
+
verbose: bool = False,
|
|
446
|
+
) -> float:
|
|
447
|
+
r"""Calculate a an approximation of the percolation temperature.
|
|
448
|
+
This uses the saddlepoint approximation of
|
|
449
|
+
|
|
450
|
+
.. math::
|
|
451
|
+
\Gamma = \Gamma_p e^{\beta (t - t_p) - \alpha^2 / 2 (t - t_p)^2}
|
|
452
|
+
|
|
453
|
+
Important: The criterion returns a value < 0 if the temperature is too high
|
|
454
|
+
and a value > 0 if the temperature is too small. It returns 0 if the criterion
|
|
455
|
+
is met.
|
|
456
|
+
|
|
457
|
+
If the action becomes inf at a temperature, this has to be because
|
|
458
|
+
the S/T function is u-shaped and must occur at the Tmin end, since
|
|
459
|
+
at Tnuc the action is finite. Therefore also return np.inf for
|
|
460
|
+
S/T = inf.
|
|
461
|
+
|
|
462
|
+
Parameters
|
|
463
|
+
----------
|
|
464
|
+
T : float
|
|
465
|
+
The temperature.
|
|
466
|
+
Tnuc : float
|
|
467
|
+
The nucleation temperature
|
|
468
|
+
outdict : dict
|
|
469
|
+
Dictionary storing the action evaluation
|
|
470
|
+
pot : generic_potential
|
|
471
|
+
The effective potential object
|
|
472
|
+
phase_sym : PhaseInfo
|
|
473
|
+
The high temperature phase information
|
|
474
|
+
phase_bro : PhaseInfo
|
|
475
|
+
The low temperature phase information
|
|
476
|
+
Tmax : float
|
|
477
|
+
The maximal temperature, where to compute the nucleation criterion (usually Tnuc)
|
|
478
|
+
Tmin : float
|
|
479
|
+
The minimal temperature where to compute the nucleation criterion, usually
|
|
480
|
+
the minimal temp. of coexistence of the phases.
|
|
481
|
+
vw : float
|
|
482
|
+
The bubble wall velocity.
|
|
483
|
+
verbose : bool, optional
|
|
484
|
+
Set the output level
|
|
485
|
+
|
|
486
|
+
Returns
|
|
487
|
+
-------
|
|
488
|
+
float
|
|
489
|
+
Zero if the criterion is met for ``T``; negative if ``T`` is too large, positive if it is too small."""
|
|
490
|
+
fperc = 0.34 # - log(P = 0.7) = 0.34
|
|
491
|
+
dT = np.maximum(T * 1e-3, 1e-3) # Try this for numerical stability
|
|
492
|
+
Tr = np.zeros(3)
|
|
493
|
+
Sr = np.zeros(3)
|
|
494
|
+
logGr = np.zeros(3)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
Sr[0] = calcAction(pot, T + dT, phase_sym, phase_bro, outdict)
|
|
498
|
+
Sr[1] = calcAction(pot, T, phase_sym, phase_bro, outdict)
|
|
499
|
+
Sr[2] = calcAction(pot, T - dT, phase_sym, phase_bro, outdict)
|
|
500
|
+
|
|
501
|
+
# If S/T is inf then the temperature must be too small
|
|
502
|
+
if np.isinf(Sr[1] / T):
|
|
503
|
+
return np.inf
|
|
504
|
+
|
|
505
|
+
if np.isinf(Sr[0] / T):
|
|
506
|
+
# It could be that the temperature interval was chosen too large
|
|
507
|
+
while np.isinf(Sr[0]) and dT / T > 1e-10:
|
|
508
|
+
dT *= 0.1
|
|
509
|
+
Sr[0] = calcAction(pot, T + dT, phase_sym, phase_bro, outdict)
|
|
510
|
+
if np.isinf(Sr[0] / T):
|
|
511
|
+
# If we still have an inf action, then the temperature is too small
|
|
512
|
+
return np.inf
|
|
513
|
+
|
|
514
|
+
Tr[0] = T + dT
|
|
515
|
+
Tr[1] = T
|
|
516
|
+
Tr[2] = T - dT
|
|
517
|
+
logGr[0] = logGamma(Tr[0], Sr[0])
|
|
518
|
+
logGr[1] = logGamma(Tr[1], Sr[1])
|
|
519
|
+
logGr[2] = logGamma(Tr[2], Sr[2])
|
|
520
|
+
rho = energyDensity(pot, phase_sym, T)
|
|
521
|
+
H = HubbleParameter(rho, pot.conversionFactor)
|
|
522
|
+
betaH = -T * (logGr[0] - logGr[1]) / dT
|
|
523
|
+
|
|
524
|
+
dSTdT = (Sr[0] / Tr[0] - Sr[1] / Tr[1]) / dT
|
|
525
|
+
d2STdT2 = (Sr[0] / Tr[0] - 2 * Sr[1] / Tr[1] + Sr[2] / Tr[2]) / dT**2
|
|
526
|
+
|
|
527
|
+
dHdT = (HubbleParameter(energyDensity(pot, phase_sym, T + dT), pot.conversionFactor) - H) / dT
|
|
528
|
+
alphaH2 = -(T**2) * d2STdT2 - (T**2 * dHdT / H + T) * dSTdT
|
|
529
|
+
|
|
530
|
+
beta = betaH * H
|
|
531
|
+
asq = -alphaH2 * H**2
|
|
532
|
+
Iperc = 4 * np.pi / 3 * vw**3
|
|
533
|
+
Iperc *= approxPercIntegral(T, logGr[1], Tnuc, beta, asq, pot, phase_sym, verbose)
|
|
534
|
+
crit = np.log(fperc / (Iperc + 1e-300))
|
|
535
|
+
|
|
536
|
+
return crit
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def ApproxPercolCriterion(
|
|
540
|
+
T: float, outdict: dict, pot, phase_sym, phase_bro, Tmax: float, Tmin: float, vw: float, verbose: bool = False
|
|
541
|
+
) -> float:
|
|
542
|
+
r"""Calculate a rough approximation of the percolation temperature.
|
|
543
|
+
This uses the saddlepoint approximation of
|
|
544
|
+
|
|
545
|
+
.. math::
|
|
546
|
+
|
|
547
|
+
\Gamma = \Gamma_0 \exp\bigl[-\beta (t - t_0)\bigr]
|
|
548
|
+
|
|
549
|
+
Important: The criterion returns a value < 0 if the temperature is too high
|
|
550
|
+
and a value > 0 if the temperature is too small. It returns 0 if the criterion
|
|
551
|
+
is met.
|
|
552
|
+
|
|
553
|
+
If the action becomes inf at a temperature, this has to be because
|
|
554
|
+
the S/T function is u-shaped and must occur at the Tmin end, since
|
|
555
|
+
at Tnuc the action is finite. Therefore also return np.inf for
|
|
556
|
+
S/T = inf.
|
|
557
|
+
|
|
558
|
+
Parameters
|
|
559
|
+
----------
|
|
560
|
+
T : float
|
|
561
|
+
The temperature.
|
|
562
|
+
outdict : dict
|
|
563
|
+
Dictionary storing the action evaluation
|
|
564
|
+
pot : generic_potential
|
|
565
|
+
The effective potential object
|
|
566
|
+
phase_sym : PhaseInfo
|
|
567
|
+
The high temperature phase information
|
|
568
|
+
phase_bro : PhaseInfo
|
|
569
|
+
The low temperature phase information
|
|
570
|
+
Tmax : float
|
|
571
|
+
The maximal temperature, where to compute the nucleation criterion (usually Tnuc)
|
|
572
|
+
Tmin : float
|
|
573
|
+
The minimal temperature where to compute the nucleation criterion, usually
|
|
574
|
+
the minimal temp. of coexistence of the phases.
|
|
575
|
+
vw : float
|
|
576
|
+
The bubble wall velocity.
|
|
577
|
+
verbose : bool, optional
|
|
578
|
+
Set the output level
|
|
579
|
+
|
|
580
|
+
Returns
|
|
581
|
+
-------
|
|
582
|
+
float
|
|
583
|
+
Zero if the criterion is met for ``T``; negative if ``T`` is too large, positive if it is too small.
|
|
584
|
+
"""
|
|
585
|
+
# Check if T is in the range of the potential
|
|
586
|
+
if T >= Tmax:
|
|
587
|
+
return -np.inf
|
|
588
|
+
elif T <= Tmin:
|
|
589
|
+
return np.inf
|
|
590
|
+
|
|
591
|
+
# Calculate beta/H
|
|
592
|
+
fperc = 0.34 # - log(P = 0.7) = 0.34
|
|
593
|
+
dT = np.maximum(T * 1e-2, 1e-3) # Try this for numerical stability
|
|
594
|
+
if T + dT > Tmax:
|
|
595
|
+
dT = (Tmax - T) / 100.0
|
|
596
|
+
Tr = np.zeros(2)
|
|
597
|
+
Sr = np.zeros(2)
|
|
598
|
+
Gr = np.zeros(2)
|
|
599
|
+
Sr[0] = calcAction(pot, T + dT, phase_sym, phase_bro, outdict)
|
|
600
|
+
Sr[1] = calcAction(pot, T, phase_sym, phase_bro, outdict)
|
|
601
|
+
|
|
602
|
+
# If S/T is inf then the temperature must be too small
|
|
603
|
+
if np.isinf(Sr[1] / T):
|
|
604
|
+
return np.inf
|
|
605
|
+
|
|
606
|
+
if np.isinf(Sr[0] / T):
|
|
607
|
+
# It could be that the temperature interval was chosen too large
|
|
608
|
+
while np.isinf(Sr[0]) and dT / T > 1e-10:
|
|
609
|
+
dT *= 0.1
|
|
610
|
+
Sr[0] = calcAction(pot, T + dT, phase_sym, phase_bro, outdict)
|
|
611
|
+
if np.isinf(Sr[0] / T):
|
|
612
|
+
# If we still have an inf action, then the temperature is too small
|
|
613
|
+
return np.inf
|
|
614
|
+
|
|
615
|
+
Tr[0] = T + dT
|
|
616
|
+
Tr[1] = T
|
|
617
|
+
Gr[0] = Gamma(Tr[0], Sr[0])
|
|
618
|
+
Gr[1] = Gamma(Tr[1], Sr[1])
|
|
619
|
+
rho = energyDensity(pot, phase_sym, T)
|
|
620
|
+
H = HubbleParameter(rho, pot.conversionFactor)
|
|
621
|
+
betaH = -T * (np.log(Gr[0]) - np.log(Gr[1])) / dT
|
|
622
|
+
|
|
623
|
+
# if betaH becomes negative, then the temperature
|
|
624
|
+
# is too small. Return positive infinity
|
|
625
|
+
if betaH < 0:
|
|
626
|
+
return np.inf
|
|
627
|
+
|
|
628
|
+
crit = np.power(Gr[1], 1 / 4.0) / H / betaH / np.power(fperc / (8 * np.pi * vw**3), 1 / 4.0)
|
|
629
|
+
if verbose:
|
|
630
|
+
print(
|
|
631
|
+
f"T = {T * pot.conversionFactor} GeV, S3 = {Sr[1] * pot.conversionFactor} GeV,"
|
|
632
|
+
f"S3/T = {Sr[1] / T}, Gamma = {Gr[1] * pot.conversionFactor**4} GeV^4, betaH = {betaH:2.5g}"
|
|
633
|
+
)
|
|
634
|
+
print("crit = ", crit)
|
|
635
|
+
return crit - 1
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def ApproxPercolCriterion2(
|
|
639
|
+
T: float, outdict: dict, pot, phase_sym, phase_bro, Tmax: float, Tmin: float, vw: float, verbose: bool = False
|
|
640
|
+
) -> float:
|
|
641
|
+
"""Alternative method to estimate the percolation criterion. This uses a
|
|
642
|
+
smaller dT for the derivatives and computes betaH from the action not the
|
|
643
|
+
nucleation rate.
|
|
644
|
+
|
|
645
|
+
Parameters
|
|
646
|
+
----------
|
|
647
|
+
T : float
|
|
648
|
+
The temperature.
|
|
649
|
+
outdict : dict
|
|
650
|
+
Dictionary storing the action evaluation
|
|
651
|
+
pot : generic_potential
|
|
652
|
+
The effective potential object
|
|
653
|
+
phase_sym : PhaseInfo
|
|
654
|
+
The high temperature phase information
|
|
655
|
+
phase_bro : PhaseInfo
|
|
656
|
+
The low temperature phase information
|
|
657
|
+
Tmax : float
|
|
658
|
+
The maximal temperature, where to compute the nucleation criterion (usually Tnuc)
|
|
659
|
+
Tmin : float
|
|
660
|
+
The minimal temperature where to compute the nucleation criterion, usually
|
|
661
|
+
the minimal temp. of coexistence of the phases.
|
|
662
|
+
vw : float
|
|
663
|
+
The bubble wall velocity.
|
|
664
|
+
verbose : bool, optional
|
|
665
|
+
Set the output level
|
|
666
|
+
|
|
667
|
+
Returns
|
|
668
|
+
----------
|
|
669
|
+
float :
|
|
670
|
+
0 if the criterion is met for T, < 0 if T is to large, > 0 if T is to small."""
|
|
671
|
+
try:
|
|
672
|
+
T = T[0] # need this when the function is run from optimize.newton, because outdict needs a hashable key
|
|
673
|
+
except Exception:
|
|
674
|
+
pass
|
|
675
|
+
|
|
676
|
+
if T >= Tmax:
|
|
677
|
+
return -np.inf
|
|
678
|
+
if T <= Tmin:
|
|
679
|
+
return np.inf
|
|
680
|
+
|
|
681
|
+
dT = T * 1e-3
|
|
682
|
+
if T + dT > Tmax:
|
|
683
|
+
dT = (Tmax - T) / 100.0
|
|
684
|
+
|
|
685
|
+
if T + dT >= Tmax:
|
|
686
|
+
return -np.inf
|
|
687
|
+
if T + dT <= Tmin:
|
|
688
|
+
return np.inf
|
|
689
|
+
|
|
690
|
+
fperc = 0.34 # - log(P = 0.7) = 0.34
|
|
691
|
+
SdT = calcAction(pot, T + dT, phase_sym, phase_bro, outdict)
|
|
692
|
+
S = calcAction(pot, T, phase_sym, phase_bro, outdict)
|
|
693
|
+
if np.isinf(SdT) or np.isinf(S):
|
|
694
|
+
return np.inf
|
|
695
|
+
|
|
696
|
+
if S == 0 or SdT == 0:
|
|
697
|
+
return -np.inf
|
|
698
|
+
|
|
699
|
+
betaH = (SdT - S) / dT - S / T
|
|
700
|
+
rho = energyDensity(pot, phase_sym, T)
|
|
701
|
+
H = HubbleParameter(rho, pot.conversionFactor)
|
|
702
|
+
G = Gamma(T, S)
|
|
703
|
+
if not np.isfinite(betaH) or abs(betaH) < 1e-30:
|
|
704
|
+
if verbose:
|
|
705
|
+
print("Warning: betaH is not finite or zero in ApproxPercolCriterion2. betaH =", betaH)
|
|
706
|
+
return np.inf
|
|
707
|
+
if not np.isfinite(H) or H <= 0:
|
|
708
|
+
if verbose:
|
|
709
|
+
print("Warning: H is not finite or zero in ApproxPercolCriterion2. H =", H)
|
|
710
|
+
return np.inf
|
|
711
|
+
if not np.isfinite(vw) or vw <= 0:
|
|
712
|
+
if verbose:
|
|
713
|
+
print("Warning: vw is not finite or zero in ApproxPercolCriterion2. vw =", vw)
|
|
714
|
+
return np.inf
|
|
715
|
+
crit = np.power(G, 1 / 4.0) / H / betaH / np.power(fperc / (8 * np.pi * vw**3), 1 / 4.0)
|
|
716
|
+
return float(np.squeeze(crit)) - 1
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def calcApproxPercolation(
|
|
720
|
+
outdict: dict,
|
|
721
|
+
pot,
|
|
722
|
+
Tnuc: float,
|
|
723
|
+
phase_sym,
|
|
724
|
+
phase_bro,
|
|
725
|
+
vw: float,
|
|
726
|
+
verbose: bool = True,
|
|
727
|
+
tmin: float | None = None,
|
|
728
|
+
tmax: float | None = None,
|
|
729
|
+
) -> float:
|
|
730
|
+
"""Estimate the percolation temperature assuming equal SM/DS temperatures.
|
|
731
|
+
|
|
732
|
+
Parameters
|
|
733
|
+
----------
|
|
734
|
+
outdict : dict
|
|
735
|
+
Dictionary storing the action evaluation.
|
|
736
|
+
pot : generic_potential
|
|
737
|
+
The effective potential object.
|
|
738
|
+
Tnuc : float
|
|
739
|
+
The nucleation temperature.
|
|
740
|
+
phase_sym : PhaseInfo
|
|
741
|
+
High-temperature phase information.
|
|
742
|
+
phase_bro : PhaseInfo
|
|
743
|
+
Low-temperature phase information.
|
|
744
|
+
vw : float
|
|
745
|
+
Bubble wall velocity.
|
|
746
|
+
verbose : bool, optional
|
|
747
|
+
Whether to emit diagnostic messages.
|
|
748
|
+
|
|
749
|
+
Returns
|
|
750
|
+
-------
|
|
751
|
+
float
|
|
752
|
+
The approximate percolation temperature.
|
|
753
|
+
"""
|
|
754
|
+
if tmin is None or tmax is None:
|
|
755
|
+
tmin, tmax = _phase_overlap_interval(pot, phase_sym, phase_bro, Tnuc=Tnuc)
|
|
756
|
+
|
|
757
|
+
dT = np.maximum(Tnuc * 1e-3, 1e-3) # Try this for numerical stability
|
|
758
|
+
Tmin = max(float(tmin) + 2 * dT, float(tmin))
|
|
759
|
+
# Shift Tmax to make derivative possible, take 2*dT to be sure.
|
|
760
|
+
# Keep the search inside the phase-overlap interval.
|
|
761
|
+
Tmax = min(Tnuc - 2 * dT, float(tmax) - 2 * dT)
|
|
762
|
+
if Tmax <= Tmin:
|
|
763
|
+
raise errors.PercolationApproximation1Error(
|
|
764
|
+
"No valid temperature bracket for percolation approximation inside "
|
|
765
|
+
f"the phase-overlap interval [{tmin:.8g}, {tmax:.8g}] at Tnuc={Tnuc:.8g}."
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
try:
|
|
769
|
+
# The calculation at the Tmin failes some times for u-shaped actions
|
|
770
|
+
args = (outdict, pot, phase_sym, phase_bro, Tmax, Tmin, vw, verbose)
|
|
771
|
+
# rtol is large, dont waste time on precision here
|
|
772
|
+
Tperc = optimize.brentq(ApproxPercolCriterion2, Tmax, Tmin, rtol=1e-3, args=args)
|
|
773
|
+
|
|
774
|
+
except ValueError as err:
|
|
775
|
+
if verbose:
|
|
776
|
+
print("Brentq failed in percolation approximation 1: ", err)
|
|
777
|
+
# This error is likely due to the range of the search not being large enough
|
|
778
|
+
# to find the root. We can try to increase the range and see if it works.
|
|
779
|
+
try:
|
|
780
|
+
Tperc = optimize.brentq(
|
|
781
|
+
ApproxPercolCriterion,
|
|
782
|
+
float(tmax),
|
|
783
|
+
Tmin,
|
|
784
|
+
rtol=1e-3,
|
|
785
|
+
args=(outdict, pot, phase_sym, phase_bro, float(tmax), Tmin, vw, verbose),
|
|
786
|
+
)
|
|
787
|
+
except ValueError as err:
|
|
788
|
+
if verbose:
|
|
789
|
+
print("Brentq failed in percolation approximation 1, even after increasing temperature range: ", err)
|
|
790
|
+
# This error may indicate that the error was not due to the range of the search.
|
|
791
|
+
# Try with another method now:
|
|
792
|
+
try:
|
|
793
|
+
Tperc = optimize.newton(
|
|
794
|
+
ApproxPercolCriterion2,
|
|
795
|
+
Tnuc,
|
|
796
|
+
args=(outdict, pot, phase_sym, phase_bro, float(tmax), float(tmin), vw, verbose),
|
|
797
|
+
)
|
|
798
|
+
if verbose:
|
|
799
|
+
print("Newton method used in percolation approximation 1")
|
|
800
|
+
if Tperc < float(tmin) or Tperc > float(tmax):
|
|
801
|
+
raise ValueError("Newton method failed to find a root in the range")
|
|
802
|
+
except Exception as newton_err:
|
|
803
|
+
if verbose:
|
|
804
|
+
print("Newton method failed in percolation approximation 1: ", newton_err)
|
|
805
|
+
raise errors.PercolationApproximation1Error(newton_err)
|
|
806
|
+
return np.squeeze(Tperc)
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
def Tb_criterion(TBRO: float, TSYM: float, phase_sym, phase_broken, pot) -> float:
|
|
810
|
+
"""Criterion to find the broken phase temperature, it
|
|
811
|
+
uses energy conservation.
|
|
812
|
+
Reheat to SM + DS in the broken phase. Assume instant heating.
|
|
813
|
+
|
|
814
|
+
Parameters
|
|
815
|
+
----------
|
|
816
|
+
TBRO : float
|
|
817
|
+
Broken phase temperature to solve for
|
|
818
|
+
TSYM : float
|
|
819
|
+
Temperature in the symmetric phase
|
|
820
|
+
phase_sym : PhaseInfo
|
|
821
|
+
Field value in the broken phase as a function of temperature
|
|
822
|
+
phase_broken : PhaseInfo
|
|
823
|
+
Field value in the broken phase as a function of temperature
|
|
824
|
+
pot : generic_potential
|
|
825
|
+
The effective potential object
|
|
826
|
+
|
|
827
|
+
Returns
|
|
828
|
+
-------
|
|
829
|
+
float
|
|
830
|
+
Criterion: 0 if ``TBRO`` is the correct broken phase temperature.
|
|
831
|
+
"""
|
|
832
|
+
|
|
833
|
+
eSYM = energyDensity(pot, phase_sym, TSYM, include_decoupled=False)
|
|
834
|
+
eBRO = energyDensity(pot, phase_broken, TBRO, include_decoupled=False)
|
|
835
|
+
|
|
836
|
+
return eBRO / eSYM - 1
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
def energy_criterion_BRO(TBRO: float, eSYM: float, eBRO_start: float, P: float, dP: float, phase_broken, pot) -> float:
|
|
840
|
+
"""Calculate the temperature in the broken phase that results
|
|
841
|
+
from the reheating by converting dP of the false vacuum into true vacuum.
|
|
842
|
+
|
|
843
|
+
Parameters
|
|
844
|
+
----------
|
|
845
|
+
TBRO : float
|
|
846
|
+
Temperature to solve for.
|
|
847
|
+
eSYM : float
|
|
848
|
+
Energy density in the symmetric phase
|
|
849
|
+
eBRO_start : float
|
|
850
|
+
Initial energy density in broken phase
|
|
851
|
+
P : float
|
|
852
|
+
True vacuum fraction
|
|
853
|
+
dP : float
|
|
854
|
+
Change in the true vacuum fraction
|
|
855
|
+
phase_broken : PhaseInfo
|
|
856
|
+
Information about the end phase.
|
|
857
|
+
pot : generic_potential
|
|
858
|
+
|
|
859
|
+
Returns
|
|
860
|
+
-------
|
|
861
|
+
float
|
|
862
|
+
Zero when the criterion is fulfilled.
|
|
863
|
+
"""
|
|
864
|
+
eBRO = energyDensity(pot, phase_broken, TBRO, include_decoupled=False)
|
|
865
|
+
crit = eBRO * P - (eBRO_start * (P - dP) + dP * eSYM)
|
|
866
|
+
return crit
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
def entropy_criterion_SYM_BRO(Tb: float, TBRO_ref: float, TSYM: float, TSYM_ref: float, phase_broken, pot) -> float:
|
|
870
|
+
"""Calculate the temperature of the dark sector broken phase in terms
|
|
871
|
+
of the symmetric phase temperature. Only valid when entropy is
|
|
872
|
+
conserved between reference temperature and TSYM.
|
|
873
|
+
|
|
874
|
+
Parameters
|
|
875
|
+
----------
|
|
876
|
+
Tb : float
|
|
877
|
+
Temperature in the broken phase.
|
|
878
|
+
TBRO_ref : float
|
|
879
|
+
Reference temperature of DS from which on entropy is conserved.
|
|
880
|
+
TSYM : float
|
|
881
|
+
SYM temperature for which we want to know TDS(TSM).
|
|
882
|
+
TSYM_ref : float
|
|
883
|
+
Reference temperature of SYM from which on entropy is conserved.
|
|
884
|
+
phase_broken : PhaseInfo
|
|
885
|
+
Phase information about the end (broken) phase.
|
|
886
|
+
pot : generic_potential
|
|
887
|
+
|
|
888
|
+
Returns
|
|
889
|
+
-------
|
|
890
|
+
float
|
|
891
|
+
Zero when the condition is met.
|
|
892
|
+
"""
|
|
893
|
+
heff_ds_pt = h_eff_DS(TBRO_ref, pot, phase_broken)
|
|
894
|
+
heff_SM_pt = td.s_geffSM(TBRO_ref, pot.conversionFactor)
|
|
895
|
+
heff_ds = h_eff_DS(Tb, pot, phase_broken)
|
|
896
|
+
heff_SM = td.s_geffSM(Tb, pot.conversionFactor)
|
|
897
|
+
crit = (heff_ds + heff_SM) * Tb**3
|
|
898
|
+
crit -= (heff_ds_pt + heff_SM_pt) * TBRO_ref**3 * TSYM**3 / TSYM_ref**3
|
|
899
|
+
return crit
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
@dataclass
|
|
903
|
+
class PercolationSettings:
|
|
904
|
+
"""Container for frequently used percolation configuration parameters."""
|
|
905
|
+
|
|
906
|
+
f_perc: float
|
|
907
|
+
f_start: float
|
|
908
|
+
f_final: float
|
|
909
|
+
weight: float
|
|
910
|
+
maxit: int
|
|
911
|
+
rel_increment: float
|
|
912
|
+
max_boundary_n: int
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
@dataclass
|
|
916
|
+
class PercolationGrid:
|
|
917
|
+
"""Shared inputs needed throughout the percolation workflow."""
|
|
918
|
+
|
|
919
|
+
TSYM: np.ndarray
|
|
920
|
+
TpercApprox: float
|
|
921
|
+
tmin: float
|
|
922
|
+
tmax: float
|
|
923
|
+
TBROmin: float
|
|
924
|
+
TBROmax: float
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
@dataclass
|
|
928
|
+
class PercolationState:
|
|
929
|
+
"""Mutable arrays updated during the percolation iterations."""
|
|
930
|
+
|
|
931
|
+
TSYM: np.ndarray
|
|
932
|
+
Sr: np.ndarray
|
|
933
|
+
Hr: np.ndarray
|
|
934
|
+
Pr: np.ndarray
|
|
935
|
+
Pr_exp: np.ndarray
|
|
936
|
+
scalef_ratio: np.ndarray
|
|
937
|
+
soundSpSq: np.ndarray
|
|
938
|
+
Tb: np.ndarray
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
def _build_percolation_settings(pot, nAction: int) -> PercolationSettings:
|
|
942
|
+
"""Read commonly used configuration parameters once."""
|
|
943
|
+
conf = pot.config.percolationConf
|
|
944
|
+
max_boundary_n = int(conf.max_boundary_ratio * nAction)
|
|
945
|
+
return PercolationSettings(
|
|
946
|
+
f_perc=conf.f_perc,
|
|
947
|
+
f_start=conf.f_start,
|
|
948
|
+
f_final=conf.f_final,
|
|
949
|
+
weight=conf.weight,
|
|
950
|
+
maxit=conf.maxit,
|
|
951
|
+
rel_increment=conf.rel_increment,
|
|
952
|
+
max_boundary_n=max_boundary_n,
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
def _phase_overlap_interval(
|
|
957
|
+
pot,
|
|
958
|
+
phase_symmetric,
|
|
959
|
+
phase_broken,
|
|
960
|
+
Tnuc: float | None = None,
|
|
961
|
+
) -> tuple[float, float]:
|
|
962
|
+
"""Return the common traced temperature interval of both phases.
|
|
963
|
+
|
|
964
|
+
The percolation and observable pipeline assumes all thermodynamic
|
|
965
|
+
quantities are evaluated inside this overlap. If this is violated, we
|
|
966
|
+
surface a hard error instead of silently extrapolating phase splines.
|
|
967
|
+
"""
|
|
968
|
+
tmin = max(float(phase_symmetric.Tmin), float(phase_broken.Tmin))
|
|
969
|
+
tmax = min(float(phase_symmetric.Tmax), float(phase_broken.Tmax))
|
|
970
|
+
if not np.isfinite(tmin) or not np.isfinite(tmax) or tmin >= tmax:
|
|
971
|
+
raise errors.PercolationError(
|
|
972
|
+
"No overlapping temperature interval between traced phases: "
|
|
973
|
+
f"Tsym in [{phase_symmetric.Tmin:.8g}, {phase_symmetric.Tmax:.8g}], "
|
|
974
|
+
f"Tbro in [{phase_broken.Tmin:.8g}, {phase_broken.Tmax:.8g}]."
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
if Tnuc is not None:
|
|
978
|
+
tracing_conf = getattr(getattr(pot, "config", None), "tracingConf", None)
|
|
979
|
+
nuc_tol = float(getattr(tracing_conf, "nucleation_Ttol", 1e-8))
|
|
980
|
+
scale = max(abs(tmin), abs(tmax), abs(float(Tnuc)), 1.0)
|
|
981
|
+
temp_tol = max(nuc_tol, 1e-10 * scale)
|
|
982
|
+
if Tnuc < tmin - temp_tol or Tnuc > tmax + temp_tol:
|
|
983
|
+
raise errors.PercolationError(
|
|
984
|
+
"Nucleation temperature is outside the traced phase-overlap interval: "
|
|
985
|
+
f"Tnuc={Tnuc:.8g}, overlap=[{tmin:.8g}, {tmax:.8g}], tol={temp_tol:.3g}. "
|
|
986
|
+
"This indicates inconsistent phase tracing / tunnelling data."
|
|
987
|
+
)
|
|
988
|
+
|
|
989
|
+
return tmin, tmax
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def _initial_temperature_grid(
|
|
993
|
+
outdict: dict,
|
|
994
|
+
pot,
|
|
995
|
+
Tnuc: float,
|
|
996
|
+
phase_symmetric,
|
|
997
|
+
phase_broken,
|
|
998
|
+
vw: float,
|
|
999
|
+
nAction: int,
|
|
1000
|
+
settings: PercolationSettings,
|
|
1001
|
+
overlap_tmin: float,
|
|
1002
|
+
overlap_tmax: float,
|
|
1003
|
+
verbose: bool,
|
|
1004
|
+
) -> PercolationGrid:
|
|
1005
|
+
"""Step 1: build the temperature grid around an approximate percolation point."""
|
|
1006
|
+
if verbose:
|
|
1007
|
+
print("\nPercolation step 1: calculating Tperc using the saddlepoint approximation")
|
|
1008
|
+
try:
|
|
1009
|
+
TpercApprox = calcApproxPercolation(
|
|
1010
|
+
outdict,
|
|
1011
|
+
pot,
|
|
1012
|
+
Tnuc,
|
|
1013
|
+
phase_symmetric,
|
|
1014
|
+
phase_broken,
|
|
1015
|
+
vw,
|
|
1016
|
+
verbose,
|
|
1017
|
+
tmin=overlap_tmin,
|
|
1018
|
+
tmax=overlap_tmax,
|
|
1019
|
+
)
|
|
1020
|
+
except errors.PercolationApproximation1Error as err:
|
|
1021
|
+
if verbose:
|
|
1022
|
+
print("Percolation approximation 1 failed: ", err, ". We will continue with TpercApprox = Tnuc.")
|
|
1023
|
+
TpercApprox = Tnuc
|
|
1024
|
+
if verbose:
|
|
1025
|
+
print(f"Approximate percolation temperature: {TpercApprox * pot.conversionFactor:2.5g} GeV")
|
|
1026
|
+
|
|
1027
|
+
dT = (Tnuc - TpercApprox) / int(nAction * settings.weight)
|
|
1028
|
+
tmin = overlap_tmin
|
|
1029
|
+
tmax = overlap_tmax
|
|
1030
|
+
|
|
1031
|
+
if TpercApprox < Tnuc:
|
|
1032
|
+
upper = np.maximum(TpercApprox - int(nAction * (1 - settings.weight)) * dT, tmin)
|
|
1033
|
+
TSYM = np.linspace(Tnuc, upper, nAction)
|
|
1034
|
+
else:
|
|
1035
|
+
if verbose:
|
|
1036
|
+
print("Warning: TpercApprox > Tnuc, use interval around TpercApprox")
|
|
1037
|
+
# here we set couple_hydrodynamics to false, since we are only interested in alpha_tot anyways
|
|
1038
|
+
_, alpha_perc_approx, _, _, _, _ = calcAlphas(TpercApprox, pot, phase_symmetric, phase_broken,
|
|
1039
|
+
coupled_hydrodynamics=False, verbose=verbose)
|
|
1040
|
+
|
|
1041
|
+
# The following is a heuristic to set the temperature range. The values are
|
|
1042
|
+
# chose somewhat arbitrarily, but they should be reasonable for most cases.
|
|
1043
|
+
# This step is not critical, but it should be chosen such that
|
|
1044
|
+
# a percolation temperature is found within a reasonable range.
|
|
1045
|
+
if alpha_perc_approx < 1:
|
|
1046
|
+
Tmin = 0.999 * TpercApprox
|
|
1047
|
+
Tmax = 1.001 * TpercApprox
|
|
1048
|
+
elif alpha_perc_approx < 10:
|
|
1049
|
+
Tmin = 0.9 * TpercApprox
|
|
1050
|
+
Tmax = 1.1 * TpercApprox
|
|
1051
|
+
elif alpha_perc_approx < 100:
|
|
1052
|
+
Tmin = 0.8 * TpercApprox
|
|
1053
|
+
Tmax = 1.2 * TpercApprox
|
|
1054
|
+
else:
|
|
1055
|
+
Tmin = 0.5 * TpercApprox
|
|
1056
|
+
Tmax = 1.5 * TpercApprox
|
|
1057
|
+
Tmax = min(Tmax, tmax)
|
|
1058
|
+
Tmin = max(Tmin, tmin)
|
|
1059
|
+
TSYM = np.linspace(Tmax, Tmin, nAction)
|
|
1060
|
+
|
|
1061
|
+
return PercolationGrid(
|
|
1062
|
+
TSYM=TSYM,
|
|
1063
|
+
TpercApprox=TpercApprox,
|
|
1064
|
+
tmin=tmin,
|
|
1065
|
+
tmax=tmax,
|
|
1066
|
+
TBROmin=phase_broken.Tmin,
|
|
1067
|
+
TBROmax=phase_broken.Tmax,
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
def _initial_percolation_scan(
|
|
1072
|
+
grid: PercolationGrid,
|
|
1073
|
+
settings: PercolationSettings,
|
|
1074
|
+
outdict: dict,
|
|
1075
|
+
pot,
|
|
1076
|
+
phase_symmetric,
|
|
1077
|
+
phase_broken,
|
|
1078
|
+
CF: float,
|
|
1079
|
+
vw: float,
|
|
1080
|
+
verbose: bool,
|
|
1081
|
+
) -> PercolationState:
|
|
1082
|
+
"""Step 2: compute P(T) with a P-independent Hubble rate.
|
|
1083
|
+
|
|
1084
|
+
Calculate the action and Hubble parameter at each temperature
|
|
1085
|
+
to find a first guess for the true vacuum fraction P and use the latter
|
|
1086
|
+
to calculate the percolation temperature."""
|
|
1087
|
+
TSYM = grid.TSYM
|
|
1088
|
+
Sr = np.zeros_like(TSYM)
|
|
1089
|
+
Hr = np.zeros_like(TSYM)
|
|
1090
|
+
Pr = np.zeros_like(TSYM)
|
|
1091
|
+
Tb = np.zeros_like(TSYM)
|
|
1092
|
+
scalef_ratio = np.zeros_like(TSYM)
|
|
1093
|
+
soundSpSq = np.zeros_like(TSYM)
|
|
1094
|
+
Pr_exp = np.zeros_like(TSYM)
|
|
1095
|
+
TBROmin = grid.TBROmin
|
|
1096
|
+
TBROmax = grid.TBROmax
|
|
1097
|
+
|
|
1098
|
+
iterator = 1
|
|
1099
|
+
iteration_condition = True
|
|
1100
|
+
if verbose:
|
|
1101
|
+
print("\nPercolation step 2: calculating Tperc assuming P = 0 in the Hubble rate")
|
|
1102
|
+
|
|
1103
|
+
while iteration_condition:
|
|
1104
|
+
Sr.fill(0)
|
|
1105
|
+
Hr.fill(0)
|
|
1106
|
+
Pr.fill(0)
|
|
1107
|
+
Tb.fill(0)
|
|
1108
|
+
Pr_exp.fill(0)
|
|
1109
|
+
soundSpSq.fill(0)
|
|
1110
|
+
scalef_ratio.fill(0)
|
|
1111
|
+
is_Pr_converged = False
|
|
1112
|
+
|
|
1113
|
+
for i, T in enumerate(TSYM):
|
|
1114
|
+
if not is_Pr_converged:
|
|
1115
|
+
Sr[i] = calcAction(pot, T, phase_symmetric, phase_broken, outdict)
|
|
1116
|
+
rho = energyDensity(pot, phase_symmetric, T)
|
|
1117
|
+
scalef_ratio[i] = scalefactorRatio(pot, TSYM[0], T, phase_symmetric.valAt(T))
|
|
1118
|
+
soundSpSq[i] = calcSoundSpeedSq(pot, phase_symmetric.valAt(T), T)
|
|
1119
|
+
Hr[i] = HubbleParameter(rho, CF)
|
|
1120
|
+
Pr[i] = 1 - np.exp(-percIntegral(TSYM[0:i+1], Hr[0:i+1], Sr[0:i+1], vw=vw))
|
|
1121
|
+
Pr_exp[i] = 1 - np.exp(-percIntegralwExp(TSYM[0:i+1], Hr[0:i+1],
|
|
1122
|
+
Sr[0:i+1], scalef_ratio[0:i+1],
|
|
1123
|
+
soundSpSq[0:i+1], vw=vw))
|
|
1124
|
+
else:
|
|
1125
|
+
Sr[i] = np.nan
|
|
1126
|
+
rho = energyDensity(pot, phase_symmetric, T)
|
|
1127
|
+
Hr[i] = HubbleParameter(rho, CF)
|
|
1128
|
+
scalef_ratio[i] = scalefactorRatio(pot, TSYM[0], T, phase_symmetric.valAt(T))
|
|
1129
|
+
soundSpSq[i] = calcSoundSpeedSq(pot, phase_symmetric.valAt(T), T)
|
|
1130
|
+
Pr[i] = Pr[i - 1]
|
|
1131
|
+
Pr_exp[i] = Pr_exp[i - 1]
|
|
1132
|
+
|
|
1133
|
+
if verbose:
|
|
1134
|
+
print(
|
|
1135
|
+
f"Step {i+1}/{len(TSYM)}, T = {TSYM[i] * CF:2.8g} GeV, "
|
|
1136
|
+
f"P_true = {Pr[i]:2.5g}, S_3 = {Sr[i] * CF:2.5g} GeV, "
|
|
1137
|
+
f"S_3/T = {Sr[i] / T:2.5g}"
|
|
1138
|
+
)
|
|
1139
|
+
|
|
1140
|
+
if i >= 1 and Pr[i] > settings.f_final and Pr[i - 1] > settings.f_final:
|
|
1141
|
+
if verbose and not is_Pr_converged:
|
|
1142
|
+
print(
|
|
1143
|
+
"WARNING: P > ",
|
|
1144
|
+
settings.f_final,
|
|
1145
|
+
" at T = ",
|
|
1146
|
+
TSYM[i] * CF,
|
|
1147
|
+
" GeV detected. Stop computation of action from here on "
|
|
1148
|
+
"and only fill in the values for the Hubble rate.",
|
|
1149
|
+
)
|
|
1150
|
+
is_Pr_converged = True
|
|
1151
|
+
|
|
1152
|
+
boundary_n = min(settings.max_boundary_n, len(Pr) - 1)
|
|
1153
|
+
if Pr[0] <= settings.f_start and Pr[-1] >= settings.f_final:
|
|
1154
|
+
# This is good already! P_true goes from < f_start to > f_final.
|
|
1155
|
+
# The temperature range could maybe be improved:
|
|
1156
|
+
|
|
1157
|
+
# Check if Pr includes a jump from 0 to 1
|
|
1158
|
+
jump_indices = np.where((Pr[:-1] == 0) & (Pr[1:] == 1))[0]
|
|
1159
|
+
has_jump_0_to_1 = jump_indices.size > 0
|
|
1160
|
+
if has_jump_0_to_1:
|
|
1161
|
+
# If there is a jump from 0 to 1, adjust the temperature range to cover only this jump.
|
|
1162
|
+
max_index = jump_indices[0]
|
|
1163
|
+
min_index = jump_indices[0] + 1
|
|
1164
|
+
tsym_jump_max = TSYM[max_index]
|
|
1165
|
+
tsym_jump_min = TSYM[min_index]
|
|
1166
|
+
if verbose:
|
|
1167
|
+
print(
|
|
1168
|
+
"Warning: Pr includes a 0 followed by 1, i.e. a "
|
|
1169
|
+
"discontinuous jump in true vacuum fraction."
|
|
1170
|
+
)
|
|
1171
|
+
print(
|
|
1172
|
+
f"Jump occurs between the indices {max_index} and {min_index}, "
|
|
1173
|
+
f"TSYM values: {tsym_jump_min} and {tsym_jump_max}. "
|
|
1174
|
+
"Set these as new boundaries."
|
|
1175
|
+
)
|
|
1176
|
+
if tsym_jump_max / tsym_jump_min - 1 > 1e-10:
|
|
1177
|
+
TSYM = np.linspace(tsym_jump_max, tsym_jump_min, len(TSYM))
|
|
1178
|
+
grid.TSYM = TSYM
|
|
1179
|
+
Sr = np.zeros_like(TSYM)
|
|
1180
|
+
Hr = np.zeros_like(TSYM)
|
|
1181
|
+
Pr = np.zeros_like(TSYM)
|
|
1182
|
+
Tb = np.zeros_like(TSYM)
|
|
1183
|
+
iterator += 1
|
|
1184
|
+
iteration_condition = iterator < settings.maxit
|
|
1185
|
+
else:
|
|
1186
|
+
if verbose:
|
|
1187
|
+
print(
|
|
1188
|
+
"WARNING: Even though there is a jump in P_true, the "
|
|
1189
|
+
"temperature range is already very small. We will not "
|
|
1190
|
+
"adjust it further and call the percolation temperature "
|
|
1191
|
+
"calculation converged."
|
|
1192
|
+
)
|
|
1193
|
+
# The difference in the temperatures is already tiny,
|
|
1194
|
+
# so further shrinking the range would only make the code unstable.
|
|
1195
|
+
iteration_condition = False
|
|
1196
|
+
|
|
1197
|
+
elif Pr[boundary_n] == 0 or Pr[-boundary_n] == 1:
|
|
1198
|
+
# There are too many points in the temperature range where P_true = 0 or P_true = 1.
|
|
1199
|
+
if verbose and iterator != settings.maxit:
|
|
1200
|
+
print(
|
|
1201
|
+
"Warning: the temperature range for the percolation "
|
|
1202
|
+
"temperature computation was chosen too wide in iteration ",
|
|
1203
|
+
iterator,
|
|
1204
|
+
". Decrease it!",
|
|
1205
|
+
)
|
|
1206
|
+
|
|
1207
|
+
if Pr[boundary_n] == 0:
|
|
1208
|
+
# redo step 2 with a lower highest temperature
|
|
1209
|
+
Tmax_new = TSYM[np.where(Pr < settings.f_start)[0][-1]]
|
|
1210
|
+
else:
|
|
1211
|
+
Tmax_new = TSYM[0]
|
|
1212
|
+
|
|
1213
|
+
if Pr[-boundary_n] == 1:
|
|
1214
|
+
# redo step 2 with a higher lowest temperature
|
|
1215
|
+
Tmin_new = TSYM[np.where(Pr > settings.f_final)[0][0]]
|
|
1216
|
+
else:
|
|
1217
|
+
Tmin_new = TSYM[-1]
|
|
1218
|
+
|
|
1219
|
+
if Tmax_new < Tmin_new:
|
|
1220
|
+
raise errors.PercolationError(
|
|
1221
|
+
"The temperature range for the percolation temperature "
|
|
1222
|
+
"computation was messed up. This should not happen. Please report this as a bug."
|
|
1223
|
+
)
|
|
1224
|
+
TSYM = np.linspace(Tmax_new, Tmin_new, len(TSYM))
|
|
1225
|
+
grid.TSYM = TSYM
|
|
1226
|
+
Sr = np.zeros_like(TSYM)
|
|
1227
|
+
Hr = np.zeros_like(TSYM)
|
|
1228
|
+
Pr = np.zeros_like(TSYM)
|
|
1229
|
+
Tb = np.zeros_like(TSYM)
|
|
1230
|
+
iterator += 1
|
|
1231
|
+
iteration_condition = iterator < settings.maxit
|
|
1232
|
+
else:
|
|
1233
|
+
# The temperature range is good!
|
|
1234
|
+
iteration_condition = False
|
|
1235
|
+
else:
|
|
1236
|
+
# The temperature range is not good, we need to adjust it.
|
|
1237
|
+
if verbose and iterator != settings.maxit:
|
|
1238
|
+
print(
|
|
1239
|
+
"Warning: the temperature range for the percolation "
|
|
1240
|
+
"temperature computation was chosen too narrow in iteration ",
|
|
1241
|
+
iterator,
|
|
1242
|
+
". Increase it!",
|
|
1243
|
+
)
|
|
1244
|
+
if verbose and iterator == settings.maxit:
|
|
1245
|
+
print(
|
|
1246
|
+
"Warning: the temperature range for the percolation "
|
|
1247
|
+
"temperature computation was chosen too narrow. This is "
|
|
1248
|
+
"the last iteration, so we will use the current "
|
|
1249
|
+
"temperature range anyway."
|
|
1250
|
+
)
|
|
1251
|
+
if Pr[0] > settings.f_start:
|
|
1252
|
+
Tmax_new = TSYM[0] + (grid.tmax - TSYM[0]) * settings.rel_increment
|
|
1253
|
+
else:
|
|
1254
|
+
Tmax_new = TSYM[0]
|
|
1255
|
+
|
|
1256
|
+
if Pr[-1] < settings.f_final:
|
|
1257
|
+
# redo step 2 with a lower lowest temperature
|
|
1258
|
+
if iterator >= settings.maxit - 2:
|
|
1259
|
+
# If we are at the last iteration, we use the tmin value
|
|
1260
|
+
# to be sure that we cover the whole possible temperature
|
|
1261
|
+
# range before concluding that percolation is not possible.
|
|
1262
|
+
Tmin_new = grid.tmin
|
|
1263
|
+
else:
|
|
1264
|
+
Tmin_new = TSYM[-1] - (TSYM[-1] - grid.tmin) * settings.rel_increment
|
|
1265
|
+
else:
|
|
1266
|
+
Tmin_new = TSYM[-1]
|
|
1267
|
+
|
|
1268
|
+
# The temperature must be decreasing in this array
|
|
1269
|
+
TSYM = np.linspace(Tmax_new, Tmin_new, len(TSYM))
|
|
1270
|
+
grid.TSYM = TSYM
|
|
1271
|
+
Sr = np.zeros_like(TSYM)
|
|
1272
|
+
Hr = np.zeros_like(TSYM)
|
|
1273
|
+
Pr = np.zeros_like(TSYM)
|
|
1274
|
+
Tb = np.zeros_like(TSYM)
|
|
1275
|
+
iterator += 1
|
|
1276
|
+
iteration_condition = iterator < settings.maxit
|
|
1277
|
+
|
|
1278
|
+
return PercolationState(TSYM=grid.TSYM, Sr=Sr, Hr=Hr, Pr=Pr, Tb=Tb,
|
|
1279
|
+
Pr_exp=Pr_exp, soundSpSq=soundSpSq, scalef_ratio=scalef_ratio)
|
|
1280
|
+
|
|
1281
|
+
|
|
1282
|
+
def _solve_for_initial_Tperc(
|
|
1283
|
+
state: PercolationState,
|
|
1284
|
+
settings: PercolationSettings,
|
|
1285
|
+
pot,
|
|
1286
|
+
verbose: bool,
|
|
1287
|
+
) -> float:
|
|
1288
|
+
"""Solve for the percolation temperature after step 2."""
|
|
1289
|
+
Pint = interpolate.interp1d(state.TSYM, state.Pr)
|
|
1290
|
+
try:
|
|
1291
|
+
Tperc_prev = optimize.brentq(lambda T: Pint(T) - settings.f_perc, state.TSYM[0], state.TSYM[-1])
|
|
1292
|
+
except ValueError as err:
|
|
1293
|
+
msg = err.args[0]
|
|
1294
|
+
if msg.startswith("f(a) and f(b) must have different signs"):
|
|
1295
|
+
if Pint(state.TSYM[-1]) < settings.f_perc:
|
|
1296
|
+
raise errors.TooMuchSupercoolingError(
|
|
1297
|
+
"The percolation temperature could not be found because the true vacuum "
|
|
1298
|
+
"fraction only reaches "
|
|
1299
|
+
f"{Pint(state.TSYM[-1])} < {settings.f_perc} at Tmin = "
|
|
1300
|
+
f"{state.TSYM[-1] * pot.conversionFactor} GeV."
|
|
1301
|
+
)
|
|
1302
|
+
raise errors.PercolationError(err)
|
|
1303
|
+
except Exception as err:
|
|
1304
|
+
msg = (
|
|
1305
|
+
"Error in calculating Tperc_prev after the second approximation step "
|
|
1306
|
+
"with P = 0 in the Hubble rate. This might be because the nucleation "
|
|
1307
|
+
"criterion could be fulfilled but not the percolation one due to "
|
|
1308
|
+
f"strong supercooling: {err}"
|
|
1309
|
+
)
|
|
1310
|
+
if verbose:
|
|
1311
|
+
print(msg)
|
|
1312
|
+
raise errors.PercolationError(err)
|
|
1313
|
+
if verbose:
|
|
1314
|
+
print(f"Tperc_prev = {Tperc_prev * pot.conversionFactor:2.8g} GeV")
|
|
1315
|
+
return Tperc_prev
|
|
1316
|
+
|
|
1317
|
+
|
|
1318
|
+
def _refine_percolation_temperature(
|
|
1319
|
+
state: PercolationState,
|
|
1320
|
+
grid: PercolationGrid,
|
|
1321
|
+
settings: PercolationSettings,
|
|
1322
|
+
outdict: dict,
|
|
1323
|
+
pot,
|
|
1324
|
+
phase_symmetric,
|
|
1325
|
+
phase_broken,
|
|
1326
|
+
CF: float,
|
|
1327
|
+
vw: float,
|
|
1328
|
+
Tperc_prev: float,
|
|
1329
|
+
rtol: float,
|
|
1330
|
+
verbose: bool,
|
|
1331
|
+
) -> tuple[float, PercolationState]:
|
|
1332
|
+
"""Step 3: iterate with the P-dependent Hubble rate until convergence.
|
|
1333
|
+
|
|
1334
|
+
Refine the percolation temperature by solving the
|
|
1335
|
+
percolation integral with the Hubble rate and the true vacuum fraction
|
|
1336
|
+
calculated in the previous step. The function iteratively
|
|
1337
|
+
improves the true vacuum fraction and the Hubble rate until
|
|
1338
|
+
convergence is reached."""
|
|
1339
|
+
TBROmin = grid.TBROmin
|
|
1340
|
+
TBROmax = grid.TBROmax
|
|
1341
|
+
TSYM = state.TSYM
|
|
1342
|
+
iterator = 1
|
|
1343
|
+
iteration_condition = True
|
|
1344
|
+
|
|
1345
|
+
if verbose:
|
|
1346
|
+
print("\nPercolation step 3: calculating Tperc with P-dependent Hubble rate")
|
|
1347
|
+
|
|
1348
|
+
while iteration_condition:
|
|
1349
|
+
if verbose:
|
|
1350
|
+
print(f"Iteration {iterator}, T_perc = {Tperc_prev * CF:2.8g} GeV")
|
|
1351
|
+
|
|
1352
|
+
Pprev = state.Pr.copy()
|
|
1353
|
+
state.Hr.fill(0)
|
|
1354
|
+
state.Pr.fill(0)
|
|
1355
|
+
state.Pr_exp.fill(0)
|
|
1356
|
+
state.scalef_ratio.fill(0)
|
|
1357
|
+
state.soundSpSq.fill(0)
|
|
1358
|
+
state.Tb.fill(0)
|
|
1359
|
+
state.Sr.fill(0)
|
|
1360
|
+
is_Pr_converged = False
|
|
1361
|
+
|
|
1362
|
+
for i, T in enumerate(TSYM):
|
|
1363
|
+
if not is_Pr_converged:
|
|
1364
|
+
eSYM = energyDensity(pot, phase_symmetric, T)
|
|
1365
|
+
if i == 0: # High temperature bin
|
|
1366
|
+
# Pprev must be zero here
|
|
1367
|
+
state.Hr[i] = HubbleParameter(eSYM, CF)
|
|
1368
|
+
state.Sr[i] = calcAction(pot, T, phase_symmetric, phase_broken, outdict)
|
|
1369
|
+
state.scalef_ratio[i] = 1
|
|
1370
|
+
state.soundSpSq[i] = calcSoundSpeedSq(pot, phase_symmetric.valAt(T), T)
|
|
1371
|
+
try:
|
|
1372
|
+
TBRO = optimize.brentq(
|
|
1373
|
+
Tb_criterion,
|
|
1374
|
+
TBROmin,
|
|
1375
|
+
TBROmax,
|
|
1376
|
+
args=(T, phase_symmetric, phase_broken, pot),
|
|
1377
|
+
)
|
|
1378
|
+
state.Tb[i] = TBRO
|
|
1379
|
+
except ValueError as err:
|
|
1380
|
+
# Sometimes, brentq fails to find a root here. This is usually due to
|
|
1381
|
+
# an inaccuracy in phase tracing, which can be solved by increasing
|
|
1382
|
+
# the diftol parameter in the config file.
|
|
1383
|
+
raise errors.PercolationError(
|
|
1384
|
+
"Error in Tb_criterion: "
|
|
1385
|
+
f"{err}. Please check the input parameters again by hand. "
|
|
1386
|
+
"It might be that the phase tracing is not accurate enough, "
|
|
1387
|
+
"try increasing the diftol parameter in the config file."
|
|
1388
|
+
)
|
|
1389
|
+
eBRO = 0
|
|
1390
|
+
else:
|
|
1391
|
+
# Use entropy conservation in the symmetric phase between two time steps
|
|
1392
|
+
# to compute the broken phase energy density in step i based on the
|
|
1393
|
+
# broken phase energy density in step i-1.
|
|
1394
|
+
TBRO = optimize.brentq(
|
|
1395
|
+
entropy_criterion_SYM_BRO, TBROmin, TBROmax,
|
|
1396
|
+
args=(state.Tb[i - 1], T, TSYM[i - 1], phase_broken, pot)
|
|
1397
|
+
)
|
|
1398
|
+
eBRO = energyDensity(pot, phase_broken, TBRO)
|
|
1399
|
+
|
|
1400
|
+
P = Pprev[i]
|
|
1401
|
+
dP = Pprev[i] - Pprev[i - 1]
|
|
1402
|
+
energy_release = eBRO * (P - dP) + dP * eSYM
|
|
1403
|
+
relative_energy_release = energy_release / eBRO
|
|
1404
|
+
if relative_energy_release < 1e-50:
|
|
1405
|
+
# If the energy release between two steps is too small, we don't need to
|
|
1406
|
+
# calculate the broken phase temperature. The following computation in that
|
|
1407
|
+
# case would just try to find an arbitrary argument T in front of a factor zero
|
|
1408
|
+
# being the energy release. It is thus safer to just set TBRO to the value obtained
|
|
1409
|
+
# in the previous step in the entropy conservation.
|
|
1410
|
+
if verbose:
|
|
1411
|
+
print("Energy release is too small to use energy criterion, using T_bro from entropy step")
|
|
1412
|
+
state.Tb[i] = TBRO
|
|
1413
|
+
else:
|
|
1414
|
+
# Use the energy conservation to find the broken phase temperature
|
|
1415
|
+
# based on the energy release between two steps.
|
|
1416
|
+
try:
|
|
1417
|
+
TBRO = optimize.brentq(
|
|
1418
|
+
energy_criterion_BRO,
|
|
1419
|
+
TBROmin,
|
|
1420
|
+
TBROmax,
|
|
1421
|
+
args=(eSYM, eBRO, Pprev[i], Pprev[i] - Pprev[i - 1], phase_broken, pot),
|
|
1422
|
+
)
|
|
1423
|
+
state.Tb[i] = TBRO
|
|
1424
|
+
except ValueError as err:
|
|
1425
|
+
# Sometimes, the energy criterion fails to find a root,
|
|
1426
|
+
# because the energy release is too small.
|
|
1427
|
+
# We try to limit this case by checking the relative
|
|
1428
|
+
# energy release beforehand, but sometimes it still happens.
|
|
1429
|
+
# In that case, we just use the value from the previous step
|
|
1430
|
+
# and print a warning. This is not critical, as the energy
|
|
1431
|
+
# release is small anyway.
|
|
1432
|
+
if verbose:
|
|
1433
|
+
print(
|
|
1434
|
+
"Warning: energy_criterion failed. This is usually "
|
|
1435
|
+
"because the relative energy release "
|
|
1436
|
+
f"{relative_energy_release} is too small. We set it "
|
|
1437
|
+
"to 0 and continue: ",
|
|
1438
|
+
err,
|
|
1439
|
+
)
|
|
1440
|
+
state.Tb[i] = state.Tb[i - 1]
|
|
1441
|
+
TBRO = state.Tb[i]
|
|
1442
|
+
except Exception as err:
|
|
1443
|
+
raise errors.PercolationError(
|
|
1444
|
+
"Error in energy_criterion_BRO: "
|
|
1445
|
+
f"{err}. Please check this input parameters again by hand."
|
|
1446
|
+
)
|
|
1447
|
+
|
|
1448
|
+
eBRO = energyDensity(pot, phase_broken, TBRO)
|
|
1449
|
+
|
|
1450
|
+
if state.Tb[i] < TSYM[i]:
|
|
1451
|
+
raise ValueError(
|
|
1452
|
+
"There was a problem with the T_bro calculation, "
|
|
1453
|
+
"T_bro < T_sym. This is (usually) not allowed by the "
|
|
1454
|
+
"second law of thermodynamics."
|
|
1455
|
+
)
|
|
1456
|
+
|
|
1457
|
+
state.Sr[i] = calcAction(pot, T, phase_symmetric, phase_broken, outdict)
|
|
1458
|
+
state.Hr[i] = HubbleParameter(Pprev[i] * eBRO + (1 - Pprev[i]) * eSYM, CF)
|
|
1459
|
+
state.soundSpSq[i] = calcSoundSpeedSq(pot, phase_symmetric.valAt(T), T)
|
|
1460
|
+
state.scalef_ratio[i] = scalefactorRatio(pot, TSYM[0], T,
|
|
1461
|
+
phase_symmetric.valAt(T))
|
|
1462
|
+
state.Pr[i] = 1 - np.exp(-percIntegral(TSYM[0:i+1], state.Hr[0:i+1],
|
|
1463
|
+
state.Sr[0:i+1], vw=vw))
|
|
1464
|
+
state.Pr_exp[i] = 1 - np.exp(-percIntegralwExp(TSYM[0:i+1], state.Hr[0:i+1],
|
|
1465
|
+
state.Sr[0:i+1],
|
|
1466
|
+
state.scalef_ratio[0:i+1],
|
|
1467
|
+
state.soundSpSq[0:i+1],
|
|
1468
|
+
vw=vw))
|
|
1469
|
+
else:
|
|
1470
|
+
state.Sr[i] = np.nan
|
|
1471
|
+
rho = energyDensity(pot, phase_symmetric, T)
|
|
1472
|
+
state.soundSpSq[i] = calcSoundSpeedSq(pot, phase_symmetric.valAt(T), T)
|
|
1473
|
+
state.scalef_ratio[i] = scalefactorRatio(pot, TSYM[0], T,
|
|
1474
|
+
phase_symmetric.valAt(T))
|
|
1475
|
+
state.Hr[i] = HubbleParameter(rho, CF)
|
|
1476
|
+
state.Pr[i] = state.Pr[i-1]
|
|
1477
|
+
state.Pr_exp[i] = state.Pr_exp[i-1]
|
|
1478
|
+
|
|
1479
|
+
if verbose:
|
|
1480
|
+
print(
|
|
1481
|
+
f"Step {i+1}/{len(TSYM)}, T = {TSYM[i] * CF:2.8g} GeV, "
|
|
1482
|
+
f"P_true = {state.Pr[i]:2.5g}, S_3 = {state.Sr[i] * CF:2.5g} GeV, "
|
|
1483
|
+
f"S_3/T = {state.Sr[i] / T:2.5g}"
|
|
1484
|
+
)
|
|
1485
|
+
|
|
1486
|
+
if i >= 1 and state.Pr[i] > settings.f_final and state.Pr[i - 1] > settings.f_final:
|
|
1487
|
+
if verbose and not is_Pr_converged:
|
|
1488
|
+
print(
|
|
1489
|
+
"WARNING: P > ",
|
|
1490
|
+
settings.f_final,
|
|
1491
|
+
" at T = ",
|
|
1492
|
+
TSYM[i] * CF,
|
|
1493
|
+
" GeV detected. Stop computation of action from here on "
|
|
1494
|
+
"and only fill in the values for the Hubble rate.",
|
|
1495
|
+
)
|
|
1496
|
+
is_Pr_converged = True
|
|
1497
|
+
|
|
1498
|
+
Pint = interpolate.interp1d(TSYM, state.Pr)
|
|
1499
|
+
Pexpint = interpolate.interp1d(TSYM, state.Pr_exp)
|
|
1500
|
+
try:
|
|
1501
|
+
Tperc = optimize.brentq(lambda T: Pint(T) - settings.f_perc, TSYM[0], TSYM[-1])
|
|
1502
|
+
except ValueError as err:
|
|
1503
|
+
msg = err.args[0]
|
|
1504
|
+
if msg.startswith("f(a) and f(b) must have different signs"):
|
|
1505
|
+
if Pint(TSYM[-1]) < settings.f_perc:
|
|
1506
|
+
raise errors.TooMuchSupercoolingError(
|
|
1507
|
+
"The percolation temperature could not be found because the "
|
|
1508
|
+
"true vacuum fraction only reaches "
|
|
1509
|
+
f"{Pint(TSYM[-1])} < {settings.f_perc} at Tmin = "
|
|
1510
|
+
f"{TSYM[-1] * CF} GeV."
|
|
1511
|
+
)
|
|
1512
|
+
raise errors.PercolationError(err)
|
|
1513
|
+
|
|
1514
|
+
except Exception as err:
|
|
1515
|
+
msg = (
|
|
1516
|
+
"Warning: could not compute Tperc after the third approximation step "
|
|
1517
|
+
"with variable P in Hubble rate: "
|
|
1518
|
+
f"{err}. Set the percolation temperature to the one from the previous step."
|
|
1519
|
+
)
|
|
1520
|
+
if verbose:
|
|
1521
|
+
print(msg)
|
|
1522
|
+
Tperc = Tperc_prev
|
|
1523
|
+
|
|
1524
|
+
if np.abs(Tperc - Tperc_prev) / Tperc < rtol:
|
|
1525
|
+
# A percolation temperature was found!
|
|
1526
|
+
if state.Pr[0] <= settings.f_start and state.Pr[-1] >= settings.f_final:
|
|
1527
|
+
# This is good already! P_true goes from < f_start to > f_final.
|
|
1528
|
+
# The temperature range could maybe be improved:
|
|
1529
|
+
|
|
1530
|
+
# Check if Pr includes a jump from 0 to 1
|
|
1531
|
+
jump_indices = np.where((state.Pr[:-1] == 0) & (state.Pr[1:] == 1))[0]
|
|
1532
|
+
has_jump_0_to_1 = jump_indices.size > 0
|
|
1533
|
+
boundary_n = min(settings.max_boundary_n, len(state.Pr) - 1)
|
|
1534
|
+
if has_jump_0_to_1:
|
|
1535
|
+
# If there is a jump from 0 to 1, we need to adjust the temperature range to cover only this jump.
|
|
1536
|
+
max_index = jump_indices[0]
|
|
1537
|
+
min_index = jump_indices[0] + 1
|
|
1538
|
+
tsym_jump_max = TSYM[max_index]
|
|
1539
|
+
tsym_jump_min = TSYM[min_index]
|
|
1540
|
+
if verbose:
|
|
1541
|
+
print(
|
|
1542
|
+
"Warning: Pr includes a 0 followed by 1, i.e. a "
|
|
1543
|
+
"discontinuous jump in true vacuum fraction."
|
|
1544
|
+
)
|
|
1545
|
+
print(
|
|
1546
|
+
f"Jump occurs between the indices {max_index} and {min_index}, "
|
|
1547
|
+
f"TSYM values: {tsym_jump_min} and {tsym_jump_max}. "
|
|
1548
|
+
"Set these as new boundaries."
|
|
1549
|
+
)
|
|
1550
|
+
if tsym_jump_max / tsym_jump_min - 1 > 1e-10:
|
|
1551
|
+
TSYM = np.linspace(tsym_jump_max, tsym_jump_min, len(TSYM))
|
|
1552
|
+
state.TSYM = TSYM
|
|
1553
|
+
state.Sr = np.zeros_like(TSYM)
|
|
1554
|
+
state.Hr = np.zeros_like(TSYM)
|
|
1555
|
+
state.Pr = np.zeros_like(TSYM)
|
|
1556
|
+
state.Tb = np.zeros_like(TSYM)
|
|
1557
|
+
Tperc_prev = Tperc
|
|
1558
|
+
iterator += 1
|
|
1559
|
+
iteration_condition = iterator < settings.maxit
|
|
1560
|
+
else:
|
|
1561
|
+
if verbose:
|
|
1562
|
+
print(
|
|
1563
|
+
"WARNING: Even though there is a jump in P_true, the "
|
|
1564
|
+
"temperature range is already very small. We will not "
|
|
1565
|
+
"adjust it further and call the percolation temperature "
|
|
1566
|
+
"calculation converged."
|
|
1567
|
+
)
|
|
1568
|
+
print(
|
|
1569
|
+
"\nConverged after "
|
|
1570
|
+
f"{iterator} iterations to Tperc = "
|
|
1571
|
+
f"{Tperc * CF:2.8g} GeV"
|
|
1572
|
+
)
|
|
1573
|
+
# The temperature difference is already tiny,
|
|
1574
|
+
# so further shrinking the range would only make the code unstable.
|
|
1575
|
+
iteration_condition = False
|
|
1576
|
+
elif state.Pr[boundary_n] == 0 or state.Pr[-boundary_n] == 1:
|
|
1577
|
+
# There are too many points in the temperature range where P_true = 0 or P_true = 1.
|
|
1578
|
+
if verbose and iterator != settings.maxit:
|
|
1579
|
+
print(
|
|
1580
|
+
"Warning: the temperature range for the percolation "
|
|
1581
|
+
"temperature computation was chosen too wide in iteration ",
|
|
1582
|
+
iterator,
|
|
1583
|
+
". Decrease it!",
|
|
1584
|
+
)
|
|
1585
|
+
if state.Pr[boundary_n] == 0:
|
|
1586
|
+
# redo step 2 with a lower highest temperature
|
|
1587
|
+
Tmax_new = TSYM[np.where(state.Pr < settings.f_start)[0][-1]]
|
|
1588
|
+
else:
|
|
1589
|
+
Tmax_new = TSYM[0]
|
|
1590
|
+
|
|
1591
|
+
if state.Pr[-boundary_n] == 1:
|
|
1592
|
+
# redo step 2 with a higher lowest temperature
|
|
1593
|
+
Tmin_new = TSYM[np.where(state.Pr > settings.f_final)[0][0]]
|
|
1594
|
+
else:
|
|
1595
|
+
Tmin_new = TSYM[-1]
|
|
1596
|
+
|
|
1597
|
+
if Tmax_new < Tmin_new:
|
|
1598
|
+
raise errors.PercolationError(
|
|
1599
|
+
"The temperature range for the percolation temperature "
|
|
1600
|
+
"computation was messed up. It is not clear how this could "
|
|
1601
|
+
"have happened."
|
|
1602
|
+
)
|
|
1603
|
+
TSYM = np.linspace(Tmax_new, Tmin_new, len(TSYM))
|
|
1604
|
+
state.TSYM = TSYM
|
|
1605
|
+
state.Sr = np.zeros_like(TSYM)
|
|
1606
|
+
state.Hr = np.zeros_like(TSYM)
|
|
1607
|
+
state.Pr = np.zeros_like(TSYM)
|
|
1608
|
+
state.Tb = np.zeros_like(TSYM)
|
|
1609
|
+
state.Pr_exp = np.zeros_like(TSYM)
|
|
1610
|
+
state.soundSpSq = np.zeros_like(TSYM)
|
|
1611
|
+
state.scalef_ratio = np.zeros_like(TSYM)
|
|
1612
|
+
Tperc_prev = Tperc
|
|
1613
|
+
iterator += 1
|
|
1614
|
+
iteration_condition = iterator < settings.maxit
|
|
1615
|
+
else:
|
|
1616
|
+
# The temperature range is good!
|
|
1617
|
+
if verbose:
|
|
1618
|
+
print(
|
|
1619
|
+
"\nConverged after "
|
|
1620
|
+
f"{iterator} iterations to Tperc = "
|
|
1621
|
+
f"{Tperc * CF:2.8g} GeV"
|
|
1622
|
+
)
|
|
1623
|
+
iteration_condition = False # This breaks the while loop
|
|
1624
|
+
else:
|
|
1625
|
+
# The temperature range is not large enough to find a percolation temperature, we need to increase it
|
|
1626
|
+
if verbose:
|
|
1627
|
+
print(
|
|
1628
|
+
"Warning: even though a percolation temperature was previously "
|
|
1629
|
+
f"found (Tperc = {Tperc * CF:2.8g} GeV), it is not clear "
|
|
1630
|
+
"whether the phase transition can finalize. The temperature "
|
|
1631
|
+
"range will therefore be increased."
|
|
1632
|
+
)
|
|
1633
|
+
print("This was iteration", iterator, "of the third step of the percolation calculation.")
|
|
1634
|
+
if iterator == settings.maxit - 1:
|
|
1635
|
+
print(
|
|
1636
|
+
"This was the penultimate iteration, the temperature range "
|
|
1637
|
+
"will be set to the maximum possible range."
|
|
1638
|
+
)
|
|
1639
|
+
elif iterator == settings.maxit:
|
|
1640
|
+
print("This was the last iteration. We stop here and return an error.")
|
|
1641
|
+
else:
|
|
1642
|
+
print("We will increase the temperature range and try again.")
|
|
1643
|
+
|
|
1644
|
+
if state.Pr[0] > settings.f_start:
|
|
1645
|
+
Tmax_new = TSYM[0] + (grid.tmax - TSYM[0]) * settings.rel_increment
|
|
1646
|
+
else:
|
|
1647
|
+
Tmax_new = TSYM[0]
|
|
1648
|
+
|
|
1649
|
+
if state.Pr[-1] < settings.f_final:
|
|
1650
|
+
if iterator == settings.maxit - 1:
|
|
1651
|
+
# If we are at the last iteration, we set the lower temperature boundary
|
|
1652
|
+
# all the way down to the minimal possible temperature allowed by the
|
|
1653
|
+
# potential. This is to ensure that we do not miss the temperature at which
|
|
1654
|
+
# the transition completes.
|
|
1655
|
+
Tmin_new = grid.tmin
|
|
1656
|
+
elif iterator == settings.maxit:
|
|
1657
|
+
raise errors.PercolationError(
|
|
1658
|
+
"The true vacuum fraction at Tmin = "
|
|
1659
|
+
f"{grid.tmin * CF:2.5g} GeV computed in step 3 "
|
|
1660
|
+
"of the percolation computation is too low for the "
|
|
1661
|
+
"transition to be considered complete: "
|
|
1662
|
+
f"P(Tmin) = {state.Pr[-1]:2.5g}."
|
|
1663
|
+
)
|
|
1664
|
+
else:
|
|
1665
|
+
Tmin_new = TSYM[-1] - (TSYM[-1] - grid.tmin) * settings.rel_increment
|
|
1666
|
+
else:
|
|
1667
|
+
Tmin_new = TSYM[-1]
|
|
1668
|
+
|
|
1669
|
+
TSYM = np.linspace(Tmax_new, Tmin_new, len(TSYM))
|
|
1670
|
+
state.TSYM = TSYM
|
|
1671
|
+
state.Sr = np.zeros_like(TSYM)
|
|
1672
|
+
state.Hr = np.zeros_like(TSYM)
|
|
1673
|
+
state.Pr = np.zeros_like(TSYM)
|
|
1674
|
+
state.Tb = np.zeros_like(TSYM)
|
|
1675
|
+
Tperc_prev = Tperc
|
|
1676
|
+
iterator += 1
|
|
1677
|
+
iteration_condition = iterator <= settings.maxit
|
|
1678
|
+
else:
|
|
1679
|
+
if verbose:
|
|
1680
|
+
print(
|
|
1681
|
+
"Percolation temperature T_perc = ",
|
|
1682
|
+
Tperc,
|
|
1683
|
+
"was found, but it is not close enough to the previous one (",
|
|
1684
|
+
Tperc_prev,
|
|
1685
|
+
") at iteration ",
|
|
1686
|
+
iterator,
|
|
1687
|
+
". Iterate one more time with the P(t) from the previous step and see if T_perc converges.",
|
|
1688
|
+
)
|
|
1689
|
+
Tperc_prev = Tperc
|
|
1690
|
+
iterator += 1
|
|
1691
|
+
iteration_condition = iterator <= settings.maxit
|
|
1692
|
+
|
|
1693
|
+
return Tperc, state
|
|
1694
|
+
|
|
1695
|
+
|
|
1696
|
+
def calcPercAndEvolve(outdict: dict, Tnuc: float, phase_symmetric,
|
|
1697
|
+
phase_broken, pot, vw=1.0, rtol=1e-4, nAction=50,
|
|
1698
|
+
verbose=False):
|
|
1699
|
+
"""
|
|
1700
|
+
Calculate the percolation temperature iteratively. The function
|
|
1701
|
+
assumes that the dark sector and the SM are in thermal equilibrium
|
|
1702
|
+
during the phase transition. First, the function calculates the
|
|
1703
|
+
approximate percolation temperature using the function
|
|
1704
|
+
calcApproxPercolation assuming P = 0 in the computation of the
|
|
1705
|
+
Hubble parameter and using the saddlepoint approximation, i.e.
|
|
1706
|
+
assuming a quickly growing bubble nucleation rate. Then, it
|
|
1707
|
+
computes the percolation temperature by actually solving the
|
|
1708
|
+
percolation integral, assuming that P = 0 at all times, such that
|
|
1709
|
+
the computation of the Hubble rate is simplified. Next, the
|
|
1710
|
+
function refines the percolation temperature by solving the
|
|
1711
|
+
percolation integral with the Hubble rate with the previously
|
|
1712
|
+
calculated P. The function returns the percolation temperature,
|
|
1713
|
+
if it converged, the symmetric phase temperature, the Hubble
|
|
1714
|
+
parameter, the true vacuum fraction, the broken phase temperature,
|
|
1715
|
+
and the action.
|
|
1716
|
+
|
|
1717
|
+
Parameters
|
|
1718
|
+
----------
|
|
1719
|
+
outdict : dict
|
|
1720
|
+
Dict storing the tunneling information.
|
|
1721
|
+
Tnuc : float
|
|
1722
|
+
Nucleation temperature of the dark sector
|
|
1723
|
+
phase_symmetric : PhaseInfo
|
|
1724
|
+
Phi of the broken phase as function of TDS
|
|
1725
|
+
phase_broken : PhaseInfo
|
|
1726
|
+
Phi of the broken phase as function of TDS
|
|
1727
|
+
pot : generic_potential
|
|
1728
|
+
The effective potential object
|
|
1729
|
+
vw : float, optional
|
|
1730
|
+
The bubble wall velocity
|
|
1731
|
+
rtol : float, optional
|
|
1732
|
+
Desired relative error in the percolation temperature
|
|
1733
|
+
nAction : int, optional
|
|
1734
|
+
Number of action evaluations
|
|
1735
|
+
verbose : bool, optional
|
|
1736
|
+
Set the output level.
|
|
1737
|
+
|
|
1738
|
+
Returns
|
|
1739
|
+
----------
|
|
1740
|
+
Tperc : float
|
|
1741
|
+
The percolation temperature in the DS.
|
|
1742
|
+
TSYM : ndarray
|
|
1743
|
+
The symmetric phase temperature from Tnuc til Tperc
|
|
1744
|
+
Hr : ndarray
|
|
1745
|
+
The Hubble parameter from Tnuc til Tperc
|
|
1746
|
+
Pr : ndarray
|
|
1747
|
+
The true vacuum fraction from Tnuc til Tperc
|
|
1748
|
+
Tb : ndarray
|
|
1749
|
+
The broken phase temperature (also SM temperature)
|
|
1750
|
+
Sr : ndarray
|
|
1751
|
+
The action from Tnuc til Tperc"""
|
|
1752
|
+
|
|
1753
|
+
CF = pot.conversionFactor
|
|
1754
|
+
settings = _build_percolation_settings(pot, nAction)
|
|
1755
|
+
overlap_tmin, overlap_tmax = _phase_overlap_interval(
|
|
1756
|
+
pot,
|
|
1757
|
+
phase_symmetric,
|
|
1758
|
+
phase_broken,
|
|
1759
|
+
Tnuc=Tnuc,
|
|
1760
|
+
)
|
|
1761
|
+
grid = _initial_temperature_grid(
|
|
1762
|
+
outdict,
|
|
1763
|
+
pot,
|
|
1764
|
+
Tnuc,
|
|
1765
|
+
phase_symmetric,
|
|
1766
|
+
phase_broken,
|
|
1767
|
+
vw,
|
|
1768
|
+
nAction,
|
|
1769
|
+
settings,
|
|
1770
|
+
overlap_tmin,
|
|
1771
|
+
overlap_tmax,
|
|
1772
|
+
verbose,
|
|
1773
|
+
)
|
|
1774
|
+
state = _initial_percolation_scan(
|
|
1775
|
+
grid,
|
|
1776
|
+
settings,
|
|
1777
|
+
outdict,
|
|
1778
|
+
pot,
|
|
1779
|
+
phase_symmetric,
|
|
1780
|
+
phase_broken,
|
|
1781
|
+
CF,
|
|
1782
|
+
vw,
|
|
1783
|
+
verbose,
|
|
1784
|
+
)
|
|
1785
|
+
Tperc_prev = _solve_for_initial_Tperc(state, settings, pot, verbose)
|
|
1786
|
+
Tperc, state = _refine_percolation_temperature(
|
|
1787
|
+
state,
|
|
1788
|
+
grid,
|
|
1789
|
+
settings,
|
|
1790
|
+
outdict,
|
|
1791
|
+
pot,
|
|
1792
|
+
phase_symmetric,
|
|
1793
|
+
phase_broken,
|
|
1794
|
+
CF,
|
|
1795
|
+
vw,
|
|
1796
|
+
Tperc_prev,
|
|
1797
|
+
rtol,
|
|
1798
|
+
verbose,
|
|
1799
|
+
)
|
|
1800
|
+
return Tperc, state.TSYM, state.Hr, state.Pr, state.Tb, state.Sr, \
|
|
1801
|
+
state.Pr_exp, state.scalef_ratio, state.soundSpSq
|
|
1802
|
+
|
|
1803
|
+
|
|
1804
|
+
def calc_betaH_S3(T: float, Sint: interpolate.interp1d, outdict: dict, pot, phase_sym, phase_bro, verbose=False) -> float:
|
|
1805
|
+
"""Calculate the phase transition speed from the action derivative.
|
|
1806
|
+
|
|
1807
|
+
Parameters
|
|
1808
|
+
----------
|
|
1809
|
+
T : float
|
|
1810
|
+
The temperature at which to evaluate beta/H
|
|
1811
|
+
Sint: interpolate.interp1d
|
|
1812
|
+
Interpolation function of the action
|
|
1813
|
+
outdict : dict
|
|
1814
|
+
Dictionary storing the action evaluations, key is `T`
|
|
1815
|
+
pot : generic_potential
|
|
1816
|
+
The effective potential object
|
|
1817
|
+
phase_sym : PhaseInfo
|
|
1818
|
+
Information about the high temperature (symmetric) phase
|
|
1819
|
+
phase_bro : PhaseInfo
|
|
1820
|
+
Information about the low temperature (broken) phaes
|
|
1821
|
+
|
|
1822
|
+
Returns
|
|
1823
|
+
----------
|
|
1824
|
+
float :
|
|
1825
|
+
The transition speed beta/H."""
|
|
1826
|
+
dT = T * 1e-5
|
|
1827
|
+
tmin = max(float(phase_sym.Tmin), float(phase_bro.Tmin))
|
|
1828
|
+
tmax = min(float(phase_sym.Tmax), float(phase_bro.Tmax))
|
|
1829
|
+
if tmin >= tmax or T <= tmin or T >= tmax:
|
|
1830
|
+
if verbose:
|
|
1831
|
+
print(
|
|
1832
|
+
"Warning: cannot evaluate betaH outside phase overlap. "
|
|
1833
|
+
f"T={T:.8g}, overlap=[{tmin:.8g}, {tmax:.8g}]"
|
|
1834
|
+
)
|
|
1835
|
+
return np.nan
|
|
1836
|
+
# Check if the derivative can actually be calculated
|
|
1837
|
+
# in the range of interest. If not, make the points
|
|
1838
|
+
# support for the derivative closer to each other.
|
|
1839
|
+
if T - 2 * dT < tmin or T + 2 * dT > tmax:
|
|
1840
|
+
dT = np.minimum((T - tmin) / 100, (tmax - T) / 100)
|
|
1841
|
+
|
|
1842
|
+
if len(Sint.x) >= 10 and Sint.x[0] < T - 2 * dT and Sint.x[-1] > T + 2 * dT:
|
|
1843
|
+
dSdT = derivative(Sint, T, dT)
|
|
1844
|
+
betaH = dSdT - Sint(T) / T
|
|
1845
|
+
else:
|
|
1846
|
+
# not enough evaluations of S for accuracy
|
|
1847
|
+
Tr = []
|
|
1848
|
+
Sr = []
|
|
1849
|
+
for i in range(-2, 3, 1):
|
|
1850
|
+
S = calcAction(pot, T - dT * i, phase_sym, phase_bro, outdict)
|
|
1851
|
+
Tr.append(T - dT * i)
|
|
1852
|
+
Sr.append(S)
|
|
1853
|
+
T_ar = np.array(Tr[::-1]) # We need increasing T for the spline interpolation
|
|
1854
|
+
S_ar = np.array(Sr[::-1])
|
|
1855
|
+
if np.all(np.isinf(S_ar)):
|
|
1856
|
+
if verbose:
|
|
1857
|
+
print("WARNING: All elements of S_ar are infinite. Unable to proceed with calculation on beta/H.")
|
|
1858
|
+
print(
|
|
1859
|
+
"Most likely the transition is so weak that the action is infinite. "
|
|
1860
|
+
"Correspondingly, beta/H is also set to inf."
|
|
1861
|
+
)
|
|
1862
|
+
return np.inf
|
|
1863
|
+
tckS = interpolate.splrep(T_ar, S_ar, s=0)
|
|
1864
|
+
S = interpolate.splev(T, tckS, der=0)
|
|
1865
|
+
dSdT = interpolate.splev(T, tckS, der=1)
|
|
1866
|
+
betaH = dSdT - S / T
|
|
1867
|
+
return betaH
|
|
1868
|
+
|
|
1869
|
+
|
|
1870
|
+
def calcSoundSpeedSq(pot, X, T) -> float:
|
|
1871
|
+
"""Compute the sound speed squared.
|
|
1872
|
+
|
|
1873
|
+
Note: Decoupled degrees of freedom do not enter here.
|
|
1874
|
+
|
|
1875
|
+
Parameters
|
|
1876
|
+
----------
|
|
1877
|
+
|
|
1878
|
+
Returns
|
|
1879
|
+
-------
|
|
1880
|
+
|
|
1881
|
+
"""
|
|
1882
|
+
dT = T*1e-3
|
|
1883
|
+
dVdT = pot.dVdT(X, T, dT=dT, include_decoupled=False)
|
|
1884
|
+
d2VdT2 = pot.d2VdT2(X, T, dT=dT, include_decoupled=False)
|
|
1885
|
+
csSq = dVdT/(T*d2VdT2)
|
|
1886
|
+
return csSq
|
|
1887
|
+
|
|
1888
|
+
|
|
1889
|
+
def calcAlphas(T: float, pot, high_phase, low_phase, verbose=False) -> tuple[float]:
|
|
1890
|
+
"""Calculate the total transition strenght of the PT.
|
|
1891
|
+
Use several definitions.
|
|
1892
|
+
|
|
1893
|
+
The last 3 alphas are normalised to the radiation energy density
|
|
1894
|
+
of only the relevant sector for the bubble expansion. I.e.
|
|
1895
|
+
when `pot.kin_coupled_e/p_geff = 0`, they are normalised to
|
|
1896
|
+
radiation energy density in the PT sector only.
|
|
1897
|
+
|
|
1898
|
+
Parameters
|
|
1899
|
+
----------
|
|
1900
|
+
T : float
|
|
1901
|
+
Temperature at which to evaluate alpha
|
|
1902
|
+
pot : generic_potential
|
|
1903
|
+
Effective potential
|
|
1904
|
+
high_phase : phaseInfo
|
|
1905
|
+
Phase info of the initial phase
|
|
1906
|
+
low_phase : phaseInfo
|
|
1907
|
+
Phase info of the end phase
|
|
1908
|
+
|
|
1909
|
+
Returns
|
|
1910
|
+
----------
|
|
1911
|
+
tuple : float
|
|
1912
|
+
Tuple of floats, the 3 definitions of alpha and the alphas used for the
|
|
1913
|
+
calculation the efficency factors kappa."""
|
|
1914
|
+
|
|
1915
|
+
high_phi = high_phase.valAt(T) # Start phase phi values
|
|
1916
|
+
low_phi = low_phase.valAt(T) # End phase phi values
|
|
1917
|
+
DeltaV = np.abs(pot.Vtot(high_phi, T) - pot.Vtot(low_phi, T))
|
|
1918
|
+
|
|
1919
|
+
# Derivative of the potential with respect to T
|
|
1920
|
+
dT = T * 1e-5
|
|
1921
|
+
dDeltaV_p = np.abs(pot.Vtot(high_phi, T + dT / 2) - pot.Vtot(low_phi, T + dT / 2))
|
|
1922
|
+
dDeltaV_m = np.abs(pot.Vtot(high_phi, T - dT / 2) - pot.Vtot(low_phi, T - dT / 2))
|
|
1923
|
+
dDeltaVdT = (dDeltaV_p - dDeltaV_m) / dT
|
|
1924
|
+
|
|
1925
|
+
# previously we used the broken phase geff of the dark sector,
|
|
1926
|
+
# This seemed wrong to me, so I put here the symmetric phase
|
|
1927
|
+
# Note that this gives slightly different alpha values for T < 100 MeV
|
|
1928
|
+
|
|
1929
|
+
# Rad. energy density of the sector with the PT
|
|
1930
|
+
rho_rad_PTsector = pot.radiationEnergyDensity(high_phi, T, include_decoupled=False)
|
|
1931
|
+
# Note: include_decoupled = False means that a secluded sector is not included
|
|
1932
|
+
|
|
1933
|
+
# Here we assume that the decoupled sector has the same temperature as the PT sector
|
|
1934
|
+
rho_rad_tot = pot.radiationEnergyDensity(high_phi, T, include_decoupled=True)
|
|
1935
|
+
|
|
1936
|
+
# Energy density:
|
|
1937
|
+
DeltaE = DeltaV - T * dDeltaVdT
|
|
1938
|
+
|
|
1939
|
+
# Alpha definitions:
|
|
1940
|
+
alpha_p = DeltaV / rho_rad_tot
|
|
1941
|
+
# Evaluate the enthalpy-normalized trace-anomaly definition separately
|
|
1942
|
+
# from the bag-model alpha used above.
|
|
1943
|
+
csSq_sym = calcSoundSpeedSq(pot, high_phi, T)
|
|
1944
|
+
V0 = pot.Vtot(pot.X0, pot.Tmin)
|
|
1945
|
+
theta_sym = pot.energyDensity(high_phi, T) + (pot.Vtot(high_phi, T)- V0)/csSq_sym
|
|
1946
|
+
csSq_bro = calcSoundSpeedSq(pot, low_phi, T)
|
|
1947
|
+
theta_bro = pot.energyDensity(low_phi, T) + (pot.Vtot(low_phi, T) - V0)/csSq_bro
|
|
1948
|
+
alpha_theta = (theta_sym - theta_bro)/(3 * (-pot.Vtot(high_phi, T) + V0 + pot.energyDensity(high_phi, T)))
|
|
1949
|
+
|
|
1950
|
+
|
|
1951
|
+
alpha_e = DeltaE / rho_rad_tot
|
|
1952
|
+
|
|
1953
|
+
bosons_low = pot.boson_massSq(low_phi, 0) # low-T phase masses
|
|
1954
|
+
bosons_high = pot.boson_massSq(high_phi, 0) # high-T phase masses
|
|
1955
|
+
fermions_low = pot.fermion_massSq(low_phi)
|
|
1956
|
+
fermions_high = pot.fermion_massSq(high_phi)
|
|
1957
|
+
|
|
1958
|
+
# alpha_inf
|
|
1959
|
+
gauge_coupling = pot.mass_spectrum.boson_gauge_couplings
|
|
1960
|
+
m2_bos_after, dof_bos, _, is_physical = bosons_low
|
|
1961
|
+
m2_bos_before, _, _, _ = bosons_high
|
|
1962
|
+
m2_fer_after, dof_fer = fermions_low
|
|
1963
|
+
m2_fer_before, _ = fermions_high
|
|
1964
|
+
|
|
1965
|
+
delta_m2_bos = np.maximum(m2_bos_after - m2_bos_before, 0)
|
|
1966
|
+
m2factor = np.sum(dof_bos * is_physical * delta_m2_bos, axis=-1) / 24.0
|
|
1967
|
+
|
|
1968
|
+
delta_m2_fer = np.maximum(m2_fer_after - m2_fer_before, 0)
|
|
1969
|
+
m2factor += np.sum(dof_fer * delta_m2_fer, axis=-1) / 48.0
|
|
1970
|
+
|
|
1971
|
+
m_bos_after = np.sqrt(np.where(m2_bos_after > 0, m2_bos_after, 0))
|
|
1972
|
+
# Avoid sqrt of negative mass squares by setting negatives to zero.
|
|
1973
|
+
# This only occurs for Goldstones, which are excluded via is_physical = 0.
|
|
1974
|
+
m_bos_before = np.sqrt(np.where(m2_bos_before > 0, m2_bos_before, 0))
|
|
1975
|
+
delta_m_bos = np.maximum(m_bos_after - m_bos_before, 0)
|
|
1976
|
+
|
|
1977
|
+
# alpha_eq, see eq. (2.16) in 1903.09642
|
|
1978
|
+
# the hydrodynamic alphas do not depend on the decoupled radiation bath!
|
|
1979
|
+
alpha_eq = T**3 / rho_rad_PTsector * np.sum(delta_m_bos * gauge_coupling**2 * dof_bos * is_physical, axis=-1)
|
|
1980
|
+
alpha_inf = T**2 / (24 * rho_rad_PTsector) * m2factor
|
|
1981
|
+
# alpha_hyd = (DeltaE + 3 * DeltaV) / (4 * rho_rad_PTsector)
|
|
1982
|
+
alpha_hyd = (theta_sym - theta_bro) / \
|
|
1983
|
+
(3 * (V0-pot.Vtot(high_phi, T, include_decoupled=False) +
|
|
1984
|
+
pot.energyDensity(high_phi, T, include_decoupled=False)))
|
|
1985
|
+
|
|
1986
|
+
return alpha_p, alpha_theta, alpha_e, alpha_hyd, alpha_inf, alpha_eq
|
|
1987
|
+
|
|
1988
|
+
|
|
1989
|
+
def calc_betaH_S3_approx(T, outdict, pot, phase_sym, phase_bro, tmin, tmax, verbose=False):
|
|
1990
|
+
"""
|
|
1991
|
+
Calculate the betaH parameter at temperature T
|
|
1992
|
+
using the derivative of the bounce action with respect to
|
|
1993
|
+
the temperature.
|
|
1994
|
+
"""
|
|
1995
|
+
S = calcAction(pot, T, phase_sym, phase_bro, outdict)
|
|
1996
|
+
if np.isinf(S):
|
|
1997
|
+
if verbose:
|
|
1998
|
+
print("Warning: S(T) is inf")
|
|
1999
|
+
return np.nan
|
|
2000
|
+
|
|
2001
|
+
dT = T * 1e-3
|
|
2002
|
+
# if dT is too large, set it to a smaller value
|
|
2003
|
+
if T + dT > tmax:
|
|
2004
|
+
dT = (tmax - T) / 100
|
|
2005
|
+
|
|
2006
|
+
# Try to calculate the derivative to the right of T
|
|
2007
|
+
SdT = calcAction(pot, T + dT, phase_sym, phase_bro, outdict)
|
|
2008
|
+
betaH = (SdT - S) / dT - S / T
|
|
2009
|
+
while np.isinf(SdT) and dT / T > 1e-15:
|
|
2010
|
+
if verbose:
|
|
2011
|
+
print("SdT is inf, reducing dT to ", dT, " and trying again")
|
|
2012
|
+
dT *= 0.1
|
|
2013
|
+
SdT = calcAction(pot, T + dT, phase_sym, phase_bro, outdict)
|
|
2014
|
+
betaH = (SdT - S) / dT - S / T
|
|
2015
|
+
|
|
2016
|
+
# if SdT is inf, we cannot calculate betaH
|
|
2017
|
+
# if beta/H is negative, there was a numerical issue
|
|
2018
|
+
if np.isinf(SdT) or betaH < 0:
|
|
2019
|
+
# Try to calculate the derivative to the left of T
|
|
2020
|
+
dT = T * 1e-3
|
|
2021
|
+
if T - dT < tmin:
|
|
2022
|
+
dT = (T - tmin) / 100
|
|
2023
|
+
SdT_ = calcAction(pot, T - dT, phase_sym, phase_bro, outdict)
|
|
2024
|
+
betaH = (S - SdT_) / dT - S / T
|
|
2025
|
+
while np.isinf(SdT_) and dT / T > 1e-15:
|
|
2026
|
+
if verbose:
|
|
2027
|
+
print("SdT is still inf, reducing dT to ", dT, " and trying again")
|
|
2028
|
+
dT *= 0.1
|
|
2029
|
+
SdT_ = calcAction(pot, T - dT, phase_sym, phase_bro, outdict)
|
|
2030
|
+
betaH = (S - SdT_) / dT - S / T
|
|
2031
|
+
if np.isinf(SdT_):
|
|
2032
|
+
if verbose:
|
|
2033
|
+
print("Warning: SdT_ is inf, cannot calculate betaH")
|
|
2034
|
+
return np.nan
|
|
2035
|
+
|
|
2036
|
+
if np.isinf(SdT):
|
|
2037
|
+
if verbose:
|
|
2038
|
+
print("Warning: S(T+dT) is inf, cannot calculate betaH")
|
|
2039
|
+
return np.nan
|
|
2040
|
+
|
|
2041
|
+
if betaH <= 0 and verbose:
|
|
2042
|
+
print("Warning: betaH is negative!")
|
|
2043
|
+
|
|
2044
|
+
return betaH
|
|
2045
|
+
|
|
2046
|
+
|
|
2047
|
+
def calcMeanBubbleSeparation(T, Tmax, Sint, Pint, Hint):
|
|
2048
|
+
"""Calculate the mean bubble separation at temperature T.
|
|
2049
|
+
|
|
2050
|
+
Parameters
|
|
2051
|
+
----------
|
|
2052
|
+
T : float
|
|
2053
|
+
Symmetric phase temperature
|
|
2054
|
+
Tmax : float
|
|
2055
|
+
Upper limit of the integral, usually the nucleation temperature
|
|
2056
|
+
phase_sym : scipy.interpolate.interp1d object
|
|
2057
|
+
The symmetric phase
|
|
2058
|
+
Sint : scipy.interpolate.interp1d object
|
|
2059
|
+
The action
|
|
2060
|
+
Pint : scipy.interpolate.interp1d object
|
|
2061
|
+
The true vacuum fraction.
|
|
2062
|
+
Hint : scipy.interpolate.interp1d object
|
|
2063
|
+
The Hubble rate
|
|
2064
|
+
Returns
|
|
2065
|
+
----------
|
|
2066
|
+
R : float
|
|
2067
|
+
Mean bubble separation."""
|
|
2068
|
+
|
|
2069
|
+
# See eq. (5.42) in 2305.02357
|
|
2070
|
+
Tr = np.linspace(T, Tmax, 10_000)
|
|
2071
|
+
integrant = Gamma(Tr, Sint(Tr)) * (1 - Pint(Tr)) / (Tr * Hint(Tr)) * (T / Tr) ** 3
|
|
2072
|
+
integrant[np.isnan(integrant)] = 0 # Replace NaNs (due to infinite action) with 0
|
|
2073
|
+
res = integrate.trapezoid(integrant, x=Tr)
|
|
2074
|
+
res = np.power(res, -1 / 3)
|
|
2075
|
+
return res
|
|
2076
|
+
|
|
2077
|
+
|
|
2078
|
+
def calcMeanBubbleSeparationwExp(T, Tmax, Sint, Pint, Hint, CSint, SFRint):
|
|
2079
|
+
"""Calculate the mean bubble separation at temperature T.
|
|
2080
|
+
Include the effect of cosmic expansion.
|
|
2081
|
+
|
|
2082
|
+
Parameters
|
|
2083
|
+
----------
|
|
2084
|
+
T : float
|
|
2085
|
+
Symmetric phase temperature
|
|
2086
|
+
Tmax : float
|
|
2087
|
+
Upper limit of the integral, usually the nucleation temperature
|
|
2088
|
+
phase_sym : scipy.interpolate.interp1d object
|
|
2089
|
+
The symmetric phase
|
|
2090
|
+
Sint : scipy.interpolate.interp1d object
|
|
2091
|
+
The action
|
|
2092
|
+
Pint : scipy.interpolate.interp1d object
|
|
2093
|
+
The true vacuum fraction.
|
|
2094
|
+
Hint : scipy.interpolate.interp1d object
|
|
2095
|
+
The Hubble rate
|
|
2096
|
+
CSint: scipy.interpolate.interp1d object
|
|
2097
|
+
The sound speed squared
|
|
2098
|
+
SRFint: scipy.interpolate.interp1d object
|
|
2099
|
+
The scale factor ratio
|
|
2100
|
+
Returns
|
|
2101
|
+
----------
|
|
2102
|
+
R : float
|
|
2103
|
+
Mean bubble separation."""
|
|
2104
|
+
|
|
2105
|
+
# See eq. (5.42) in 2305.02357
|
|
2106
|
+
Tr = np.linspace(T, Tmax, 10_000)
|
|
2107
|
+
integrant = Gamma(Tr, Sint(Tr)) * (1 - Pint(Tr)) / (3 * Hint(Tr) * Tr * CSint(Tr) ) \
|
|
2108
|
+
* SFRint(Tr)**3
|
|
2109
|
+
integrant[np.isnan(integrant)] = 0 # Replace NaNs (due to infinite action) with 0
|
|
2110
|
+
res = integrate.trapezoid(integrant, x=Tr)
|
|
2111
|
+
res = np.power(res, -1 / 3)
|
|
2112
|
+
return res
|
|
2113
|
+
|
|
2114
|
+
|
|
2115
|
+
|
|
2116
|
+
def calcTf(Tperc, Tlow, Pint, pot, verbose=False):
|
|
2117
|
+
"""Calculate the final temperature at which the transition ends.
|
|
2118
|
+
|
|
2119
|
+
Parameters
|
|
2120
|
+
----------
|
|
2121
|
+
Tperc : float
|
|
2122
|
+
Symmetric phase percolation temperature, i.e. maximally possible value of the final temperature
|
|
2123
|
+
Tlow : float
|
|
2124
|
+
Lowest temperature considered in the percolation computation. At this temperature, the true vacuum
|
|
2125
|
+
fraction Pint is >= f_final ~ 0.99. The final temperature must thus be <= Tlow.
|
|
2126
|
+
phase_sym : scipy.interpolate.interp1d object
|
|
2127
|
+
The symmetric phase
|
|
2128
|
+
Sint : scipy.interpolate.interp1d object
|
|
2129
|
+
The action
|
|
2130
|
+
Pint : scipy.interpolate.interp1d object
|
|
2131
|
+
The true vacuum fraction.
|
|
2132
|
+
Returns
|
|
2133
|
+
----------
|
|
2134
|
+
Tf : float
|
|
2135
|
+
Final temperature."""
|
|
2136
|
+
|
|
2137
|
+
f_final = pot.config.percolationConf.f_final # true vacuum fraction at final temperature
|
|
2138
|
+
|
|
2139
|
+
# Step 1: Check if the true vacuum fraction at Tlow is >= f_final
|
|
2140
|
+
if Pint(Tlow) < f_final:
|
|
2141
|
+
# It looks like the transition cannot completely finish
|
|
2142
|
+
msg = "The transition cannot completely finish, Pint(Tlow) < f_final: " + str(Pint(Tlow)) + " < " + str(f_final)
|
|
2143
|
+
raise errors.EternalInflationError(msg)
|
|
2144
|
+
|
|
2145
|
+
# Step 2: Find Tf by solving Pint(Tf) = f_final
|
|
2146
|
+
try:
|
|
2147
|
+
Tf = optimize.brentq(lambda TSYM: Pint(TSYM) - f_final, Tlow, Tperc)
|
|
2148
|
+
except Exception as e:
|
|
2149
|
+
msg = "Error in calculating Tf: " + str(e)
|
|
2150
|
+
if verbose:
|
|
2151
|
+
print(msg)
|
|
2152
|
+
raise errors.PercolationError(e)
|
|
2153
|
+
|
|
2154
|
+
return Tf
|
|
2155
|
+
|
|
2156
|
+
|
|
2157
|
+
# ==================================================
|
|
2158
|
+
# Nucleation computation
|
|
2159
|
+
# ==================================================
|
|
2160
|
+
|
|
2161
|
+
|
|
2162
|
+
def findTunnelingTmax(DV: callable, Tmin: float, Tmax: float, tol=1e-15, maxiter=100):
|
|
2163
|
+
"""Finds the largest temperature Tmax in [Tmin, Tmax] such that
|
|
2164
|
+
V(start_phase.valAt(T), T) > V(end_phase.valAt(T), T).
|
|
2165
|
+
|
|
2166
|
+
Important: `tol=1e-15` is required to to find the right Tmax,
|
|
2167
|
+
`tol=1e-8` is not enough!
|
|
2168
|
+
|
|
2169
|
+
Parameters
|
|
2170
|
+
----------
|
|
2171
|
+
DV : callable
|
|
2172
|
+
Tmin : float
|
|
2173
|
+
Tmax : float
|
|
2174
|
+
tol : float, optional
|
|
2175
|
+
maxiter : int, optional
|
|
2176
|
+
|
|
2177
|
+
Returns
|
|
2178
|
+
-------
|
|
2179
|
+
float
|
|
2180
|
+
The maximal temperature where V(start_phase, Tmax) > V(end_phase, Tmax).
|
|
2181
|
+
"""
|
|
2182
|
+
|
|
2183
|
+
if DV(Tmax) > 0:
|
|
2184
|
+
return Tmax
|
|
2185
|
+
# Search for the root of DV(T) = 0 in [Tmin, Tmax]
|
|
2186
|
+
try:
|
|
2187
|
+
Tmax_new = optimize.brentq(DV, Tmin, Tmax, xtol=tol, maxiter=maxiter)
|
|
2188
|
+
return Tmax_new
|
|
2189
|
+
except ValueError:
|
|
2190
|
+
# Maybe DV(Tmin) < 0 for numerical reasons:
|
|
2191
|
+
try:
|
|
2192
|
+
Tmax_new = optimize.brentq(DV, (Tmin + Tmax)/2.0, Tmax, xtol=tol, maxiter=maxiter)
|
|
2193
|
+
return Tmax_new
|
|
2194
|
+
except ValueError:
|
|
2195
|
+
# No crossing found; fall back to Tmin.
|
|
2196
|
+
return Tmin
|
|
2197
|
+
|
|
2198
|
+
|
|
2199
|
+
def findTminFromUShapedNucl(Tmin, Tmax, args, start_phase, end_phase, outdict,
|
|
2200
|
+
Ttol, maxiter, CF, verbose, nuclCriterion):
|
|
2201
|
+
"""Find the minimum of the nucleation criterion between `Tmin` and `Tmax`
|
|
2202
|
+
|
|
2203
|
+
Parameters
|
|
2204
|
+
----------
|
|
2205
|
+
|
|
2206
|
+
Returns
|
|
2207
|
+
-------
|
|
2208
|
+
float | None:
|
|
2209
|
+
Temperature of the minimum of the nucleation criterion.
|
|
2210
|
+
If it is a hidden second order transition, return None
|
|
2211
|
+
"""
|
|
2212
|
+
ntest = 100
|
|
2213
|
+
Tmin_new = 0
|
|
2214
|
+
try:
|
|
2215
|
+
# Try to find the minimum of the tunneling action
|
|
2216
|
+
# between Tmin and Tmax
|
|
2217
|
+
def maximizer(x, *args):
|
|
2218
|
+
# This function is used to find the maximum of the
|
|
2219
|
+
# nucleation criterion, corresponding to the minimum of
|
|
2220
|
+
# a U-shaped action.
|
|
2221
|
+
return -evalNucleationCriterion(x, *args)
|
|
2222
|
+
|
|
2223
|
+
Ttest = np.nan
|
|
2224
|
+
|
|
2225
|
+
# This skips Tmin and Tmax, which have already been checked and would
|
|
2226
|
+
# yield boundary errors in the bracketing algorithm
|
|
2227
|
+
Trange = np.linspace(Tmin, Tmax, ntest, endpoint=False)[1:]
|
|
2228
|
+
for t in Trange:
|
|
2229
|
+
nc = evalNucleationCriterion(t, *args)
|
|
2230
|
+
if not np.isinf(nc):
|
|
2231
|
+
Ttest = t
|
|
2232
|
+
# usually this it the case after one or two iterations
|
|
2233
|
+
break
|
|
2234
|
+
|
|
2235
|
+
if np.isnan(Ttest):
|
|
2236
|
+
# It could be that the action is actually very hard to compute
|
|
2237
|
+
# because the transitions is extremely weak. In that case the
|
|
2238
|
+
# action jumps between -inf and +inf. We can then take the highest
|
|
2239
|
+
# temperature for which the nucleation criterion was found to be
|
|
2240
|
+
# fulfilled, even if it is infinitely large due to the numerical
|
|
2241
|
+
# precision.
|
|
2242
|
+
if any([outdict[T]["trantype"] == 2 for T in outdict]):
|
|
2243
|
+
max_key = max(T for T in outdict if outdict[T]["trantype"] == 2)
|
|
2244
|
+
if verbose:
|
|
2245
|
+
print(
|
|
2246
|
+
"Found a second-order transition at T = ",
|
|
2247
|
+
max_key * CF,
|
|
2248
|
+
"GeV. This was a little hard to find because it was "
|
|
2249
|
+
"treated as a very weak first-order transition.",
|
|
2250
|
+
)
|
|
2251
|
+
rdict = outdict[max_key]
|
|
2252
|
+
rdict["Tcrit"] = rdict["Tnuc"] = max_key
|
|
2253
|
+
return None
|
|
2254
|
+
|
|
2255
|
+
if all([evalNucleationCriterion(T, *args) < 0 if T <= Tmax and T >= Tmin else True for T in outdict]):
|
|
2256
|
+
# This means the nucleation criterion is never fulfilled between Tmin and Tmax.
|
|
2257
|
+
# The barrier is too high to allow for the nucleation to be possible.
|
|
2258
|
+
raise errors.NucleationError("Nucleation criterion failed: too low at all temperatures.")
|
|
2259
|
+
raise errors.NucleationError(
|
|
2260
|
+
"Nucleation criterion failed due to unknown reason: No finite nucleation "
|
|
2261
|
+
"criterion found in the range between Tmin and Tmax."
|
|
2262
|
+
)
|
|
2263
|
+
result = optimize.minimize_scalar(
|
|
2264
|
+
maximizer,
|
|
2265
|
+
bracket=(Tmin, Ttest, Tmax),
|
|
2266
|
+
args=args,
|
|
2267
|
+
method="brent",
|
|
2268
|
+
options={"xtol": (Tmax - Tmin) * 1e-5, "maxiter": maxiter, "disp": 0},
|
|
2269
|
+
)
|
|
2270
|
+
if result.success:
|
|
2271
|
+
# We found a minimum of the action! Let's check if the nucleation
|
|
2272
|
+
# criterion is fulfilled at this temperature.
|
|
2273
|
+
Tmin_u = float(np.squeeze(result.x)) # this is a hack to make the np array hashable
|
|
2274
|
+
action_u = outdict[Tmin_u]["action"]
|
|
2275
|
+
ncMin_u = nuclCriterion(action_u, Tmin_u, start_phase, end_phase)
|
|
2276
|
+
|
|
2277
|
+
Tmin_new = Tmin_u
|
|
2278
|
+
|
|
2279
|
+
if verbose:
|
|
2280
|
+
print("Finding Tmin_u done! Tmin_u = ", result.x * CF, "GeV")
|
|
2281
|
+
# Note: it can also happen that a minimum is found in
|
|
2282
|
+
# the range of Tmin and Tmax, even though there is no
|
|
2283
|
+
# actual minimum of the action. This can happen if the
|
|
2284
|
+
# action is u-shaped and so large that the nucleation
|
|
2285
|
+
# criterion is not fulfilled at all. In this case, one
|
|
2286
|
+
# still obtains a minimum of the action at an arbitrary
|
|
2287
|
+
# temperature because the nucleation criterion evaluates
|
|
2288
|
+
# to - inf everywhere, such that the "maximizer" is inf
|
|
2289
|
+
# everywhere. This is not a problem, because in any case
|
|
2290
|
+
# we are checking again if the nucleation criterion is fulfilled
|
|
2291
|
+
# at the found minimum of the action.
|
|
2292
|
+
|
|
2293
|
+
if verbose:
|
|
2294
|
+
print("action_u = ", action_u * CF, "GeV")
|
|
2295
|
+
print("ncMin_u = ", ncMin_u)
|
|
2296
|
+
if ncMin_u < 0:
|
|
2297
|
+
if verbose:
|
|
2298
|
+
print("Nucleation criterion not fulfilled at the minimum of the u-shaped action.")
|
|
2299
|
+
raise errors.NucleationError(
|
|
2300
|
+
"Nucleation criterion failed because the nucleation rate is too low at all temperatures."
|
|
2301
|
+
)
|
|
2302
|
+
if verbose:
|
|
2303
|
+
print("Nucleation criterion fulfilled at the minimum of the u-shaped action.")
|
|
2304
|
+
else:
|
|
2305
|
+
if verbose:
|
|
2306
|
+
print("Finding Tmin_u failed: ", result.message)
|
|
2307
|
+
raise errors.NucleationError("Nucleation criterion failed in the finding of Tmin_u:" + str(result.message))
|
|
2308
|
+
except Exception as err:
|
|
2309
|
+
raise errors.NucleationError("Nucleation calculation failed: " + str(err))
|
|
2310
|
+
|
|
2311
|
+
return Tmin_new
|
|
2312
|
+
|
|
2313
|
+
|
|
2314
|
+
def computeNucleationTemperature(V, dV, d2V, start_phase, end_phase,
|
|
2315
|
+
CF, Ttol=1e-8, maxiter=100, phitol=1e-8,
|
|
2316
|
+
overlapAngle=45.0, nuclCriterion=lambda S, T: 140 - S / (T + 1e-100),
|
|
2317
|
+
approximate_strength_threshold=1e-4, verbose=False,
|
|
2318
|
+
fullTunneling_params={}):
|
|
2319
|
+
"""Find the instanton and nucleation temperature for tunneling from
|
|
2320
|
+
`start_phase` to `end_phase`.
|
|
2321
|
+
|
|
2322
|
+
Parameters
|
|
2323
|
+
----------
|
|
2324
|
+
V, dV : callable
|
|
2325
|
+
The potential V(x,T) and its gradient.
|
|
2326
|
+
start_phase : PhaseInfo
|
|
2327
|
+
The metastable phase from which tunneling occurs.
|
|
2328
|
+
end_phase : PhaesInfo
|
|
2329
|
+
The destination phase.
|
|
2330
|
+
Tmax : float
|
|
2331
|
+
The highest temperature at which to try tunneling.
|
|
2332
|
+
Ttol : float, optional
|
|
2333
|
+
Tolerance for finding the nucleation temperature.
|
|
2334
|
+
maxiter : int, optional
|
|
2335
|
+
Maximum number of times to try tunneling.
|
|
2336
|
+
phitol : float, optional
|
|
2337
|
+
Tolerance for finding the minima.
|
|
2338
|
+
overlapAngle : float, optional
|
|
2339
|
+
If two phases are in the same direction, only try tunneling to the
|
|
2340
|
+
closer one. Set to zero to always try tunneling to all available phases.
|
|
2341
|
+
nuclCriterion : callable
|
|
2342
|
+
Function of the action *S* and temperature *T*. Should return 0 for the
|
|
2343
|
+
correct nucleation rate, > 0 for a low rate and < 0 for a high rate.
|
|
2344
|
+
Defaults to ``S/T - 140``.
|
|
2345
|
+
verbose : bool
|
|
2346
|
+
If true, print a message before each attempted tunneling.
|
|
2347
|
+
fullTunneling_params : dict
|
|
2348
|
+
Parameters to pass to :func:`pathDeformation.fullTunneling`.
|
|
2349
|
+
|
|
2350
|
+
Returns
|
|
2351
|
+
-------
|
|
2352
|
+
dict or None
|
|
2353
|
+
A description of the tunneling solution at the nucleation temperature,
|
|
2354
|
+
or None if there is no found solution. Has the following keys:
|
|
2355
|
+
|
|
2356
|
+
- *Tnuc* : the nucleation temperature
|
|
2357
|
+
- *low_vev, high_vev* : vevs for the low-T phase (the phase that the
|
|
2358
|
+
instanton tunnels to) and high-T phase (the phase that the instanton
|
|
2359
|
+
tunnels from).
|
|
2360
|
+
- *low_phase, high_phase* : identifier keys for the low-T and high-T
|
|
2361
|
+
phases.
|
|
2362
|
+
- *action* : The Euclidean action of the instanton.
|
|
2363
|
+
- *instanton* : Output from :func:`pathDeformation.fullTunneling`, or
|
|
2364
|
+
None for a second-order transition.
|
|
2365
|
+
- *trantype* : 1 or 2 for first or second-order transitions.
|
|
2366
|
+
"""
|
|
2367
|
+
outdict = {} # keys are T values
|
|
2368
|
+
args = (start_phase, end_phase, V, dV, d2V, phitol, overlapAngle, nuclCriterion,
|
|
2369
|
+
fullTunneling_params, verbose, outdict, CF)
|
|
2370
|
+
Tmin = max(start_phase.Tmin, end_phase.Tmin)
|
|
2371
|
+
|
|
2372
|
+
# We want Tmax to be the highest temperature at which tunneling is theoretically
|
|
2373
|
+
# possible, i.e. where the potential difference between the two phases is at least
|
|
2374
|
+
# DVmin. Find the largest Tmax for which V(start_phase) > V(end_phase)
|
|
2375
|
+
|
|
2376
|
+
def DV(T):
|
|
2377
|
+
# Be very accurate about the minima!
|
|
2378
|
+
x0 = findLocalMinimum(start_phase.valAt(T), T, dV, d2V)
|
|
2379
|
+
x1 = findLocalMinimum(end_phase.valAt(T), T, dV, d2V)
|
|
2380
|
+
# This below is necessary because sometimes the phase tracing is not
|
|
2381
|
+
# accurate enough.
|
|
2382
|
+
if np.sqrt(np.sum((x0 - start_phase.valAt(T))**2)) > 1:
|
|
2383
|
+
x0 = start_phase.valAt(T)
|
|
2384
|
+
if np.sqrt(np.sum((x1 - end_phase.valAt(T))**2)) > 1:
|
|
2385
|
+
x1 = end_phase.valAt(T)
|
|
2386
|
+
x = x0 - x1
|
|
2387
|
+
dist = np.sqrt(x.dot(x))
|
|
2388
|
+
# Add small epsilon to pot. value in end phase to ensure breaking of degeneracy!
|
|
2389
|
+
# This might cause problems for very weak transitions,but on the otherhand, they
|
|
2390
|
+
# are not interesting
|
|
2391
|
+
eps = dist * 1e-3
|
|
2392
|
+
return V(x0, T) - (V(x1, T) + eps)
|
|
2393
|
+
|
|
2394
|
+
# Always check that the Tmax is where DV is > 0
|
|
2395
|
+
Tmax = findTunnelingTmax(DV, Tmin, end_phase.Tmax)
|
|
2396
|
+
|
|
2397
|
+
# Check for "hidden"" second order transitions
|
|
2398
|
+
if not checkNucleationPossibility(Tmin, Tmax, args, outdict, V, dV,
|
|
2399
|
+
start_phase, end_phase, CF,
|
|
2400
|
+
approximate_strength_threshold, verbose):
|
|
2401
|
+
rdict = dict(
|
|
2402
|
+
Tcrit=Tmax,
|
|
2403
|
+
Tnuc=Tmax,
|
|
2404
|
+
Tperc=Tmax,
|
|
2405
|
+
low_vev=end_phase.valAt(Tmax),
|
|
2406
|
+
high_vev=start_phase.valAt(Tmax),
|
|
2407
|
+
low_phase=end_phase.key,
|
|
2408
|
+
high_phase=start_phase.key,
|
|
2409
|
+
action=np.nan,
|
|
2410
|
+
full_tunneling_info=np.nan,
|
|
2411
|
+
instanton=np.nan,
|
|
2412
|
+
trantype=2,
|
|
2413
|
+
)
|
|
2414
|
+
rdict["Tcrit"] = rdict["Tnuc"] = Tmax
|
|
2415
|
+
return rdict
|
|
2416
|
+
|
|
2417
|
+
ncrit_Tmin = evalNucleationCriterion(Tmin, *args)
|
|
2418
|
+
ncrit_Tmax = evalNucleationCriterion(Tmax, *args)
|
|
2419
|
+
|
|
2420
|
+
# Is it a second order transition?
|
|
2421
|
+
# Does the nucleation happen already at Tmax?
|
|
2422
|
+
# This could be a second order transition or an
|
|
2423
|
+
# immediate transition after a another one
|
|
2424
|
+
if ncrit_Tmax >= 0:
|
|
2425
|
+
if verbose:
|
|
2426
|
+
print(f"Nucl. crit fullfilled at Tmax = {Tmax*CF}!")
|
|
2427
|
+
Tnuc = Tmax
|
|
2428
|
+
rdict = outdict[Tmax]
|
|
2429
|
+
rdict["Tcrit"] = Tmax
|
|
2430
|
+
if rdict["trantype"] != 2:
|
|
2431
|
+
filtered_outdict = {k: {kk: vv for kk, vv in v.items() if kk != "instanton"} for k, v in outdict.items()}
|
|
2432
|
+
filtered_outdict = dict(sorted(filtered_outdict.items()))
|
|
2433
|
+
rdict["full_tunneling_info"] = filtered_outdict
|
|
2434
|
+
# assert rdict['trantype'] == 2
|
|
2435
|
+
return rdict
|
|
2436
|
+
|
|
2437
|
+
# Is the nucleation criterion fulfilled at Tmin?
|
|
2438
|
+
if ncrit_Tmin < 0:
|
|
2439
|
+
if verbose:
|
|
2440
|
+
print("Nucleation criterion not fulfilled at Tmin")
|
|
2441
|
+
# The nucleation criterion is not fulfilled at the boundaries of
|
|
2442
|
+
# the temperature interval. It could be that the action is
|
|
2443
|
+
# U-shaped such that the nucleation criterion is not fulfilled
|
|
2444
|
+
# at Tmin and Tmax, but maybe somewhere in between.
|
|
2445
|
+
# In this case we need to find the minimum of the action
|
|
2446
|
+
# between Tmin and Tmax. This is done by minimizing the
|
|
2447
|
+
# tunneling action at T.
|
|
2448
|
+
if verbose:
|
|
2449
|
+
print("The bounce action might be U-shaped. Try to find the minimum of it")
|
|
2450
|
+
print("between Tmin = ", Tmin * CF, "GeV and Tmax = ", Tmax * CF, "GeV.")
|
|
2451
|
+
Tmin = findTminFromUShapedNucl(
|
|
2452
|
+
Tmin, Tmax, args, start_phase, end_phase, outdict, Ttol, maxiter, CF, verbose, nuclCriterion
|
|
2453
|
+
)
|
|
2454
|
+
# Hidden second order transition?
|
|
2455
|
+
if Tmin is None:
|
|
2456
|
+
rdict = dict(Tcrit=Tmax, Tnuc=Tmax, Tperc=Tmax,
|
|
2457
|
+
low_vev=end_phase.valAt(Tmax),
|
|
2458
|
+
high_vev=start_phase.valAt(Tmax),
|
|
2459
|
+
low_phase=end_phase.key,
|
|
2460
|
+
high_phase=start_phase.key,
|
|
2461
|
+
action=np.nan, full_tunneling_info=np.nan,
|
|
2462
|
+
instanton=np.nan, trantype=2)
|
|
2463
|
+
rdict["Tcrit"] = rdict["Tnuc"] = Tmax
|
|
2464
|
+
return rdict
|
|
2465
|
+
|
|
2466
|
+
# Check if the tunneling is energetically allowed between Tmin and Tmax
|
|
2467
|
+
try:
|
|
2468
|
+
Tnuc = optimize.brentq(evalNucleationCriterion, Tmin, Tmax, args=args, xtol=Ttol, maxiter=maxiter, disp=False)
|
|
2469
|
+
if verbose:
|
|
2470
|
+
print("Brentq done after first try. Tnuc = ", Tnuc * CF, "GeV")
|
|
2471
|
+
except ValueError:
|
|
2472
|
+
print("Brentq failed for first try, retrying with larger Ttol.")
|
|
2473
|
+
try:
|
|
2474
|
+
Tnuc = optimize.brentq(
|
|
2475
|
+
evalNucleationCriterion, Tmin, Tmax, args=args, xtol=Ttol * 10, maxiter=maxiter, disp=False
|
|
2476
|
+
)
|
|
2477
|
+
if verbose:
|
|
2478
|
+
print("Brentq done after second try! Tnuc = ", Tnuc * CF, "GeV")
|
|
2479
|
+
except Exception:
|
|
2480
|
+
if verbose:
|
|
2481
|
+
print("Brentq failed again after increasing the numerical tolerance by a factor 10.")
|
|
2482
|
+
raise errors.NucleationError("Nucleation criterion failed due to unknown reason.")
|
|
2483
|
+
|
|
2484
|
+
try:
|
|
2485
|
+
if Tnuc in outdict:
|
|
2486
|
+
rdict = outdict[Tnuc]
|
|
2487
|
+
# Remove 'instanton' key from each sub-dictionary in outdict before saving, because
|
|
2488
|
+
# it is no longer needed and could cause memory issues, becasuse it can become quite
|
|
2489
|
+
# large.
|
|
2490
|
+
filtered_outdict = {k: {kk: vv for kk, vv in v.items() if kk != "instanton"} for k, v in outdict.items()}
|
|
2491
|
+
# This is new and allows to re-use the computed tunneling solutions also at a
|
|
2492
|
+
# later part of the code, especially the percolation computation
|
|
2493
|
+
filtered_outdict = dict(sorted(filtered_outdict.items()))
|
|
2494
|
+
rdict["full_tunneling_info"] = filtered_outdict
|
|
2495
|
+
else:
|
|
2496
|
+
raise errors.NucleationError(
|
|
2497
|
+
"Tnuc not in outdict. This can only happen if the nucleation "
|
|
2498
|
+
"criterion could not be fulfilled at any temperature."
|
|
2499
|
+
)
|
|
2500
|
+
except UnboundLocalError as err:
|
|
2501
|
+
if verbose:
|
|
2502
|
+
print(
|
|
2503
|
+
"Tnuc was never defined. This happens if the nucleation "
|
|
2504
|
+
"criterion could not be fulfilled at any temperature, but a "
|
|
2505
|
+
"np.nan was returned. This is likely a numerical bug."
|
|
2506
|
+
)
|
|
2507
|
+
raise errors.NucleationError(
|
|
2508
|
+
"Nucleation criterion failed, probably due to a numerical error: "
|
|
2509
|
+
+ str(err)
|
|
2510
|
+
)
|
|
2511
|
+
|
|
2512
|
+
if "trantype" in rdict and rdict["trantype"] > 0:
|
|
2513
|
+
if rdict["trantype"] == 2:
|
|
2514
|
+
# This is not a first-order transition, but actually a second-order one. This was
|
|
2515
|
+
# however only found here because the barrier was tiny and the nucleation
|
|
2516
|
+
# criterion is fulfilled trivially. We create a second-order transition dictionary
|
|
2517
|
+
# and return it.
|
|
2518
|
+
if verbose:
|
|
2519
|
+
print(
|
|
2520
|
+
"The transition is actually a second-order transition, "
|
|
2521
|
+
"because the action is zero at the transition."
|
|
2522
|
+
)
|
|
2523
|
+
rdict["Tcrit"] = rdict["Tnuc"] = Tnuc
|
|
2524
|
+
return rdict
|
|
2525
|
+
rdict["Tcrit"] = Tmax
|
|
2526
|
+
return rdict
|
|
2527
|
+
elif "trantype" in rdict and rdict["trantype"] == 0:
|
|
2528
|
+
# Check if there is a second-order transition just below the found Tnuc
|
|
2529
|
+
keys = sorted(key for key in outdict.keys() if key != "full_tunneling_info" and key < Tnuc)
|
|
2530
|
+
max_key = keys[-1] if keys else None
|
|
2531
|
+
if max_key is not None and outdict[max_key]["trantype"] == 2:
|
|
2532
|
+
# If there is a second-order transition just below the found Tnuc, return it
|
|
2533
|
+
if verbose:
|
|
2534
|
+
print(
|
|
2535
|
+
"The found transition had an infinite action. But found a "
|
|
2536
|
+
"second-order transition just below the found Tnuc. "
|
|
2537
|
+
"Returning it. Tcrit = ",
|
|
2538
|
+
max_key * CF,
|
|
2539
|
+
"GeV",
|
|
2540
|
+
)
|
|
2541
|
+
rdict = outdict[max_key]
|
|
2542
|
+
rdict["Tcrit"] = rdict["Tnuc"] = max_key
|
|
2543
|
+
return rdict
|
|
2544
|
+
else:
|
|
2545
|
+
if verbose:
|
|
2546
|
+
print(
|
|
2547
|
+
"The transition has action = 0 and trantype = 0, which means "
|
|
2548
|
+
"that it is a second-order transition, but no second-order "
|
|
2549
|
+
"transition was found just below the found Tnuc. This is "
|
|
2550
|
+
"likely a numerical issue."
|
|
2551
|
+
)
|
|
2552
|
+
return rdict
|
|
2553
|
+
else:
|
|
2554
|
+
raise errors.NucleationError(
|
|
2555
|
+
"The nucleation computation failed: Either the barrier is too high or the transition is second order."
|
|
2556
|
+
)
|
|
2557
|
+
|
|
2558
|
+
|
|
2559
|
+
def checkNucleationPossibility(Tmin, Tmax, args, outdict, V, dV, start_phase,
|
|
2560
|
+
end_phase, conversionFactor, apprx_strength_thr,
|
|
2561
|
+
verbose) -> tuple[bool, dict]:
|
|
2562
|
+
"""Do some checks to see if tunneling is possible.
|
|
2563
|
+
|
|
2564
|
+
Parameters
|
|
2565
|
+
----------
|
|
2566
|
+
tr : TransitionInfo
|
|
2567
|
+
The transition information
|
|
2568
|
+
Returns
|
|
2569
|
+
-------
|
|
2570
|
+
tuple[bool, dict] :
|
|
2571
|
+
(True, {}) if possible otherwise, (False, {returndict})."""
|
|
2572
|
+
# We want Tmax to be the highest temperature at which tunneling is theoretically
|
|
2573
|
+
# possible, i.e. where the potential difference between the two phases is at least
|
|
2574
|
+
# DVmin. Find the largest Tmax for which V(start_phase) > V(end_phase)
|
|
2575
|
+
|
|
2576
|
+
rdict = {}
|
|
2577
|
+
# Check 1 for "hidden" second-order transitions: action is zero at Tmax
|
|
2578
|
+
nuclCritTmax = evalNucleationCriterion(Tmax, *args)
|
|
2579
|
+
if np.isinf(nuclCritTmax):
|
|
2580
|
+
if Tmax in outdict:
|
|
2581
|
+
rdict = outdict[Tmax]
|
|
2582
|
+
if "trantype" in rdict and rdict["trantype"] == 2:
|
|
2583
|
+
if verbose:
|
|
2584
|
+
print(
|
|
2585
|
+
"The transition is actually a second-order transition "
|
|
2586
|
+
"because the action is zero at the transition."
|
|
2587
|
+
)
|
|
2588
|
+
return False
|
|
2589
|
+
|
|
2590
|
+
# Check 2 for "hidden" second-order transitions: the strength of the transition would in any case be tiny
|
|
2591
|
+
def approx_strength(T):
|
|
2592
|
+
# Approximate strength parameter of the transition, if it happened at temperature T
|
|
2593
|
+
DV = V(start_phase.valAt(T), T) - (V(end_phase.valAt(T), T))
|
|
2594
|
+
return np.abs(DV) / T**4
|
|
2595
|
+
|
|
2596
|
+
approx_strength_range = np.array([approx_strength(T) for T in np.linspace(Tmin, Tmax, 100)])
|
|
2597
|
+
if np.all(approx_strength_range < apprx_strength_thr):
|
|
2598
|
+
if verbose:
|
|
2599
|
+
print(f"PT strength {np.max(approx_strength_range)} below threshold: {apprx_strength_thr}.")
|
|
2600
|
+
print(f"This is treated as a second-order transition at Tmax = {Tmax * conversionFactor} GeV.")
|
|
2601
|
+
# Create a second-order transition dictionary
|
|
2602
|
+
return False
|
|
2603
|
+
|
|
2604
|
+
# Tunneling possible, return true and empty dict.
|
|
2605
|
+
return True
|
|
2606
|
+
|
|
2607
|
+
|
|
2608
|
+
def computeNucleationTemperature2(
|
|
2609
|
+
tr,
|
|
2610
|
+
V,
|
|
2611
|
+
dV,
|
|
2612
|
+
d2V,
|
|
2613
|
+
start_phase,
|
|
2614
|
+
end_phase,
|
|
2615
|
+
Tmax,
|
|
2616
|
+
conversionFactor,
|
|
2617
|
+
Ttol=1e-8,
|
|
2618
|
+
maxiter=100,
|
|
2619
|
+
phitol=1e-8,
|
|
2620
|
+
overlapAngle=45.0,
|
|
2621
|
+
nuclCriterion=lambda S, T: 140 - S / (T + 1e-100),
|
|
2622
|
+
approximate_strength_threshold=1e-4,
|
|
2623
|
+
verbose=False,
|
|
2624
|
+
fullTunneling_params={},
|
|
2625
|
+
):
|
|
2626
|
+
"""Refactored version of `computeNucleationTemperature`
|
|
2627
|
+
Find the instanton and nucleation temeprature for tunneling from
|
|
2628
|
+
`start_phase` to `end_phase`.
|
|
2629
|
+
|
|
2630
|
+
Parameters
|
|
2631
|
+
----------
|
|
2632
|
+
tr: TransitionInfo
|
|
2633
|
+
Object containing the transition informations
|
|
2634
|
+
V, dV, d2V : callable
|
|
2635
|
+
The potential V(x,T) and its gradient.
|
|
2636
|
+
start_phase : PhaseInfo
|
|
2637
|
+
The metastable phase from which tunneling occurs.
|
|
2638
|
+
end_phase : PhaesInfo
|
|
2639
|
+
The destination phase.
|
|
2640
|
+
Tmax : float
|
|
2641
|
+
The highest temperature at which to try tunneling.
|
|
2642
|
+
Ttol : float, optional
|
|
2643
|
+
Tolerance for finding the nucleation temperature.
|
|
2644
|
+
maxiter : int, optional
|
|
2645
|
+
Maximum number of times to try tunneling.
|
|
2646
|
+
phitol : float, optional
|
|
2647
|
+
Tolerance for finding the minima.
|
|
2648
|
+
overlapAngle : float, optional
|
|
2649
|
+
If two phases are in the same direction, only try tunneling to the
|
|
2650
|
+
closer one. Set to zero to always try tunneling to all available phases.
|
|
2651
|
+
nuclCriterion : callable
|
|
2652
|
+
Function of the action *S* and temperature *T*. Should return 0 for the
|
|
2653
|
+
correct nucleation rate, > 0 for a low rate and < 0 for a high rate.
|
|
2654
|
+
Defaults to ``S/T - 140``.
|
|
2655
|
+
verbose : bool
|
|
2656
|
+
If true, print a message before each attempted tunneling.
|
|
2657
|
+
fullTunneling_params : dict
|
|
2658
|
+
Parameters to pass to :func:`pathDeformation.fullTunneling`.
|
|
2659
|
+
|
|
2660
|
+
Returns
|
|
2661
|
+
-------
|
|
2662
|
+
dict or None
|
|
2663
|
+
A description of the tunneling solution at the nucleation temperature,
|
|
2664
|
+
or None if there is no found solution. Has the following keys:
|
|
2665
|
+
|
|
2666
|
+
- *Tnuc* : the nucleation temperature
|
|
2667
|
+
- *low_vev, high_vev* : vevs for the low-T phase (the phase that the
|
|
2668
|
+
instanton tunnels to) and high-T phase (the phase that the instanton
|
|
2669
|
+
tunnels from).
|
|
2670
|
+
- *low_phase, high_phase* : identifier keys for the low-T and high-T
|
|
2671
|
+
phases.
|
|
2672
|
+
- *action* : The Euclidean action of the instanton.
|
|
2673
|
+
- *instanton* : Output from :func:`pathDeformation.fullTunneling`, or
|
|
2674
|
+
None for a second-order transition.
|
|
2675
|
+
- *trantype* : 1 or 2 for first or second-order transitions.
|
|
2676
|
+
"""
|
|
2677
|
+
# If we need to calculate the bounce action brute-force if it is U-shaped, this is
|
|
2678
|
+
# the maximum number of test points to use.
|
|
2679
|
+
ntest = 100
|
|
2680
|
+
CF = conversionFactor
|
|
2681
|
+
outdict = {} # keys are T values
|
|
2682
|
+
# args for the tunneling function
|
|
2683
|
+
args = (start_phase, end_phase, V, dV, d2V, phitol, overlapAngle,
|
|
2684
|
+
nuclCriterion, fullTunneling_params, verbose, outdict, CF)
|
|
2685
|
+
# usually: lowest temperature of starting phase. But it can happen that for some
|
|
2686
|
+
# other reason, the start and end phases have been swapped. In that case, we don't
|
|
2687
|
+
# want to make an error here.
|
|
2688
|
+
Tmin = max(start_phase.Tmin, end_phase.Tmin)
|
|
2689
|
+
# usually highest temperature of end phase, unless it is below Tmin (which is bad)
|
|
2690
|
+
T_highest_other = max(Tmin, end_phase.Tmax)
|
|
2691
|
+
Tmax = min(Tmax, T_highest_other)
|
|
2692
|
+
|
|
2693
|
+
def DV(T):
|
|
2694
|
+
# Add small epsilon to pot. value in end phase to ensure breaking of degeneracy!
|
|
2695
|
+
x0 = start_phase.valAt(T)
|
|
2696
|
+
x1 = end_phase.valAt(T)
|
|
2697
|
+
dist = np.sqrt((x0-x1)**2)
|
|
2698
|
+
eps = dist * 1e-3
|
|
2699
|
+
return V(start_phase.valAt(T), T) - (V(end_phase.valAt(T), T) + eps)
|
|
2700
|
+
|
|
2701
|
+
# Check if the Potential difference is large enough to tunnel (numerically)
|
|
2702
|
+
Tmax = findTunnelingTmax(DV, Tmin, Tmax)
|
|
2703
|
+
|
|
2704
|
+
# check if tunneling is possible
|
|
2705
|
+
tPossible, rdict = checkNucleationPossibility(Tmin, Tmax, args, outdict, V, dV,
|
|
2706
|
+
start_phase, end_phase, CF,
|
|
2707
|
+
approximate_strength_threshold, verbose)
|
|
2708
|
+
if not tPossible:
|
|
2709
|
+
return rdict
|
|
2710
|
+
|
|
2711
|
+
ncrit_Tmin = evalNucleationCriterion(Tmin, *args)
|
|
2712
|
+
ncrit_Tmax = evalNucleationCriterion(Tmax, *args)
|
|
2713
|
+
|
|
2714
|
+
# Is it a second order transition?
|
|
2715
|
+
if ncrit_Tmax > 0:
|
|
2716
|
+
if verbose:
|
|
2717
|
+
print(f"Nucl. crit fullfilled at Tmax = {Tmax*CF}!")
|
|
2718
|
+
Tnuc = Tmax
|
|
2719
|
+
rdict = outdict[Tmax]
|
|
2720
|
+
assert rdict["trantype"] == 2
|
|
2721
|
+
return rdict
|
|
2722
|
+
|
|
2723
|
+
# Is there more than one bubble nucleated at Tmin
|
|
2724
|
+
# Or is the nucleation criterion u-shaped?
|
|
2725
|
+
if ncrit_Tmin < 0:
|
|
2726
|
+
# find a temperature where the the criterion becomes positive (> 1 bubble)
|
|
2727
|
+
def abort_fmin(T, outdict=outdict, nc=evalNucleationCriterion):
|
|
2728
|
+
val = nc(T, *args)
|
|
2729
|
+
if val > 0 and not np.isnan(val):
|
|
2730
|
+
raise StopIteration(T)
|
|
2731
|
+
|
|
2732
|
+
Tguess = np.nan
|
|
2733
|
+
# first search for a non-inf value of the nucleation criterion:
|
|
2734
|
+
for t in np.linspace(Tmin, Tmax, ntest, endpoint=False)[1:]:
|
|
2735
|
+
nc = evalNucleationCriterion(t, *args)
|
|
2736
|
+
if not np.isinf(nc):
|
|
2737
|
+
Tguess = t
|
|
2738
|
+
# Is the nucleation rate to low everywhere?
|
|
2739
|
+
if np.isnan(Tguess):
|
|
2740
|
+
if any([outdict[T]["trantype"] == 2 for T in outdict]):
|
|
2741
|
+
max_key = max(T for T in outdict if outdict[T]["trantype"] == 2)
|
|
2742
|
+
rdict = outdict[max_key]
|
|
2743
|
+
rdict["Tcrit"] = rdict["Tnuc"] = max_key
|
|
2744
|
+
return rdict
|
|
2745
|
+
if all([evalNucleationCriterion(T, *args) < 0 if T <= Tmax and T >= Tmin else True for T in outdict]):
|
|
2746
|
+
# This means the nucleation criterion is never fulfilled between Tmin and Tmax. The barrier is too high
|
|
2747
|
+
# to allow for the nucleation to be possible.
|
|
2748
|
+
raise errors.NucleationError("Nucleation rate too low at all temperatures.")
|
|
2749
|
+
|
|
2750
|
+
try:
|
|
2751
|
+
Tmin = abs(
|
|
2752
|
+
optimize.fmin(
|
|
2753
|
+
lambda x: -evalNucleationCriterion(abs(x), *args), Tguess, callback=abort_fmin, maxiter=20, disp=0
|
|
2754
|
+
)[0]
|
|
2755
|
+
)
|
|
2756
|
+
if evalNucleationCriterion(Tmin, *args) < 0:
|
|
2757
|
+
# The nucleation criterion is not fullfilled anywhere!
|
|
2758
|
+
raise errors.NucleationError("Nucleation rate to low at all temperatures!")
|
|
2759
|
+
except StopIteration as err:
|
|
2760
|
+
Tmin = abs(err.args[0])
|
|
2761
|
+
|
|
2762
|
+
try:
|
|
2763
|
+
Tnuc = optimize.brentq(evalNucleationCriterion, Tmin, Tmax, args=args, xtol=Ttol, maxiter=maxiter, disp=False)
|
|
2764
|
+
except Exception as e:
|
|
2765
|
+
print("Nucleation calculation failed!")
|
|
2766
|
+
print(e)
|
|
2767
|
+
raise errors.NucleationError("Unknown nulceation error!")
|
|
2768
|
+
|
|
2769
|
+
rdict = outdict[Tnuc]
|
|
2770
|
+
filtered_outdict = {k: {kk: vv for kk, vv in v.items() if kk != "instanton"} for k, v in outdict.items()}
|
|
2771
|
+
# This is new and allows to re-use the computed tunneling solutions also at a
|
|
2772
|
+
# later part of the code, especially the percolation computation
|
|
2773
|
+
filtered_outdict = dict(sorted(filtered_outdict.items()))
|
|
2774
|
+
rdict["full_tunneling_info"] = filtered_outdict
|
|
2775
|
+
return rdict
|
|
2776
|
+
|
|
2777
|
+
|
|
2778
|
+
def evalNucleationCriterion(T, start_phase, end_phase, V, dV,
|
|
2779
|
+
d2V, phitol, overlapAngle, nuclCriterion,
|
|
2780
|
+
fullTunneling_params, verbose, outdict,
|
|
2781
|
+
conversionFactor):
|
|
2782
|
+
"""Find the lowest action tunneling solution.
|
|
2783
|
+
|
|
2784
|
+
Return ``nuclCriterion(S,T)``, and store a dictionary describing the
|
|
2785
|
+
transition in outdict for key `T`."""
|
|
2786
|
+
try:
|
|
2787
|
+
T = T[0] # need this when the function is run from optimize.fmin
|
|
2788
|
+
except Exception:
|
|
2789
|
+
pass
|
|
2790
|
+
|
|
2791
|
+
if T in outdict:
|
|
2792
|
+
return nuclCriterion(outdict[T]["action"], T, start_phase, end_phase)
|
|
2793
|
+
|
|
2794
|
+
# Loop through all the phases, adding acceptable minima
|
|
2795
|
+
x0 = findLocalMinimum(start_phase.valAt(T), T, dV, d2V)
|
|
2796
|
+
# Check if we accidentally hopped into the other minimum
|
|
2797
|
+
if np.sqrt(np.sum((x0 - start_phase.valAt(T))**2)) > 1:
|
|
2798
|
+
x0 = start_phase.valAt(T)
|
|
2799
|
+
V0 = V(x0, T)
|
|
2800
|
+
p = end_phase
|
|
2801
|
+
if p.Tmin > T:
|
|
2802
|
+
if verbose:
|
|
2803
|
+
print(
|
|
2804
|
+
"Phase %s not valid at T = %g" % (p.key, T * conversionFactor),
|
|
2805
|
+
"GeV < Tmin = ",
|
|
2806
|
+
p.Tmin * conversionFactor,
|
|
2807
|
+
"GeV",
|
|
2808
|
+
)
|
|
2809
|
+
# raise Exception("Tunneling not possible, low phase does not exist at T = ", T)
|
|
2810
|
+
return np.inf
|
|
2811
|
+
|
|
2812
|
+
if p.Tmax < T:
|
|
2813
|
+
if verbose:
|
|
2814
|
+
print(
|
|
2815
|
+
"Phase %s not valid at T = %g" % (p.key, T * conversionFactor),
|
|
2816
|
+
"GeV > Tmax = ",
|
|
2817
|
+
p.Tmax * conversionFactor,
|
|
2818
|
+
"GeV",
|
|
2819
|
+
)
|
|
2820
|
+
# raise Exception("Tunneling not possible, low phase does not exist at T = ", T)
|
|
2821
|
+
return -np.inf
|
|
2822
|
+
|
|
2823
|
+
x1 = findLocalMinimum(p.valAt(T), T, dV, d2V)
|
|
2824
|
+
if np.sqrt(np.sum((x1 - end_phase.valAt(T))**2)) > 1:
|
|
2825
|
+
x1 = end_phase.valAt(T)
|
|
2826
|
+
V1 = V(x1, T)
|
|
2827
|
+
|
|
2828
|
+
if V1 >= V0:
|
|
2829
|
+
if verbose:
|
|
2830
|
+
print(
|
|
2831
|
+
"Tunneling energetically not possible."
|
|
2832
|
+
+ "Tmax might be too high or the numerical accuracy is not sufficient."
|
|
2833
|
+
+ " This is not a problem, just a warning."
|
|
2834
|
+
)
|
|
2835
|
+
|
|
2836
|
+
tdict = dict(low_vev=x1, high_vev=x0, Tnuc=T, low_phase=end_phase.key, high_phase=start_phase.key)
|
|
2837
|
+
# tunnel_list.append(tdict)
|
|
2838
|
+
# This is a check to see if the two phases are in the same direction.
|
|
2839
|
+
# Future work, focusing on multiple discrete symmetries might
|
|
2840
|
+
# act here.
|
|
2841
|
+
# If two phases are in the same direction, we only want to try tunneling to the closer one:
|
|
2842
|
+
# if overlapAngle > 0:
|
|
2843
|
+
# excluded = []
|
|
2844
|
+
# cos_overlap = np.cos(overlapAngle * np.pi/180)
|
|
2845
|
+
# for i in range(1, len(tunnel_list)):
|
|
2846
|
+
# for j in range(i):
|
|
2847
|
+
# xi = tunnel_list[i]['low_vev']
|
|
2848
|
+
# xj = tunnel_list[j]['low_vev']
|
|
2849
|
+
# xi2 = np.sum((xi-x0)**2)
|
|
2850
|
+
# xj2 = np.sum((xj-x0)**2)
|
|
2851
|
+
# dotij = np.sum((xj-x0)*(xi-x0))
|
|
2852
|
+
# if dotij >= np.sqrt(xi2*xj2) * cos_overlap:
|
|
2853
|
+
# excluded.append(i if xi2 > xj2 else j)
|
|
2854
|
+
# for i in sorted(excluded)[::-1]:
|
|
2855
|
+
# del tunnel_list[i]
|
|
2856
|
+
|
|
2857
|
+
outdict = bounceAction(
|
|
2858
|
+
T, V, dV, outdict, tdict, verbose=verbose, conversionFactor=conversionFactor, **fullTunneling_params
|
|
2859
|
+
)
|
|
2860
|
+
|
|
2861
|
+
S = outdict[T]["action"]
|
|
2862
|
+
crit = nuclCriterion(S, T, start_phase, end_phase)
|
|
2863
|
+
|
|
2864
|
+
return crit
|
|
2865
|
+
|
|
2866
|
+
|
|
2867
|
+
def makeSecondOrderDict(transition, conversionFactor: float) -> dict:
|
|
2868
|
+
"""Create a dictionary describing a second-order phase transition."""
|
|
2869
|
+
transition_dict = dict(
|
|
2870
|
+
Tcrit=transition.Tcrit,
|
|
2871
|
+
Tcrit_SM_GeV=transition.Tcrit * conversionFactor,
|
|
2872
|
+
Tnuc=transition.Tnuc,
|
|
2873
|
+
Tperc=transition.Tperc,
|
|
2874
|
+
low_vev=transition.low_vev,
|
|
2875
|
+
high_vev=transition.high_vev,
|
|
2876
|
+
low_phase=transition.low_phase,
|
|
2877
|
+
high_phase=transition.high_phase,
|
|
2878
|
+
action=np.nan,
|
|
2879
|
+
trantype=2,
|
|
2880
|
+
)
|
|
2881
|
+
return transition_dict
|
|
2882
|
+
|
|
2883
|
+
|
|
2884
|
+
def findLocalMinimum(
|
|
2885
|
+
X: np.ndarray, T: float, dV: callable, d2V: callable, phitol: float = 1e-6, step_size: float = 0.1
|
|
2886
|
+
) -> np.ndarray:
|
|
2887
|
+
r"""Find the local minimum with the algorithm developed
|
|
2888
|
+
in BSMPT
|
|
2889
|
+
|
|
2890
|
+
.. math::
|
|
2891
|
+
\vec{\epsilon} = H^{-1}\nabla V
|
|
2892
|
+
|
|
2893
|
+
\vec{\phi}_{min}^{i+1} = \vec{\phi}_{min}^i - \vec{\epsilon}
|
|
2894
|
+
|
|
2895
|
+
Parameters
|
|
2896
|
+
----------
|
|
2897
|
+
X : np.ndarray
|
|
2898
|
+
The initial guess for the location of the minimum
|
|
2899
|
+
T : float
|
|
2900
|
+
Temperature
|
|
2901
|
+
dV : callable
|
|
2902
|
+
Gradient of the potenital :math:`\nabla V(X, T)`
|
|
2903
|
+
d2V : callable
|
|
2904
|
+
Hessian matrix of the potenital :math:`\partial^2 V(X,T)/\partial \phi_i\partial\phi_j`
|
|
2905
|
+
phitol : float
|
|
2906
|
+
The desired accuracy of the minimum
|
|
2907
|
+
step_size : float
|
|
2908
|
+
The multiplyier controlling the step size in the direction of the minimum.
|
|
2909
|
+
|
|
2910
|
+
Returns
|
|
2911
|
+
-------
|
|
2912
|
+
np.ndarray:
|
|
2913
|
+
The minimum."""
|
|
2914
|
+
hess_offset = np.diag(np.ones_like(X) * 1e-5)
|
|
2915
|
+
niter = 0
|
|
2916
|
+
while True:
|
|
2917
|
+
niter += 1
|
|
2918
|
+
Hess = d2V(X, T)
|
|
2919
|
+
# Stop if zero eigenvalues
|
|
2920
|
+
if np.linalg.det(Hess) == 0:
|
|
2921
|
+
break
|
|
2922
|
+
Hinv = np.linalg.inv(Hess + hess_offset)
|
|
2923
|
+
eps = np.matmul(Hinv, dV(X, T))
|
|
2924
|
+
X = X - eps * step_size
|
|
2925
|
+
if np.sqrt(np.sum(eps * eps)) <= phitol:
|
|
2926
|
+
break
|
|
2927
|
+
if niter >= 200:
|
|
2928
|
+
break
|
|
2929
|
+
return X
|