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,910 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module (along with a few functions in :mod:`.helper_functions`) contains
|
|
3
|
+
everything that is needed to calculate instantons in one field dimension.
|
|
4
|
+
The primary class is :class:`SingleFieldInstanton`, which can calculate the
|
|
5
|
+
instanton solution in any number of spatial dimensions using the overshoot /
|
|
6
|
+
undershoot method. Additional classes inherit common functionality from this
|
|
7
|
+
one, and can be used to calculate the bubble wall profile with constant
|
|
8
|
+
friction (:class:`WallWithConstFriction`) instead of radius-dependent friction,
|
|
9
|
+
or to calculate the instanton in the presence of gravity (*not yet
|
|
10
|
+
implemented*).
|
|
11
|
+
|
|
12
|
+
Part of TransitionListener v2.0
|
|
13
|
+
Documentation: https://tasillo.de/TransitionListener/
|
|
14
|
+
|
|
15
|
+
Authors:
|
|
16
|
+
Jonas Matuszak <jonas.matuszak@kit.edu>
|
|
17
|
+
Carlo Tasillo <carlo.tasillo@ific.uv.es>
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import absolute_import
|
|
21
|
+
from __future__ import division
|
|
22
|
+
from __future__ import print_function
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
from scipy import optimize, special, interpolate
|
|
26
|
+
from collections import namedtuple
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from scipy.integrate import simpson
|
|
30
|
+
except ImportError:
|
|
31
|
+
# scipy.version < 1.6
|
|
32
|
+
from scipy.integrate import simps as simpson
|
|
33
|
+
|
|
34
|
+
from . import helper_functions
|
|
35
|
+
from . import errors
|
|
36
|
+
from .helper_functions import rkqs, IntegrationError
|
|
37
|
+
from .helper_functions import cubicInterpFunction
|
|
38
|
+
|
|
39
|
+
import sys
|
|
40
|
+
if sys.version_info >= (3,0):
|
|
41
|
+
xrange = range
|
|
42
|
+
|
|
43
|
+
class SingleFieldInstanton:
|
|
44
|
+
r"""
|
|
45
|
+
This class will calculate properties of an instanton with a single scalar
|
|
46
|
+
Field without gravity using the overshoot/undershoot method.
|
|
47
|
+
|
|
48
|
+
Most users will probably be primarily interested in the functions
|
|
49
|
+
:func:`findProfile` and :func:`findAction`.
|
|
50
|
+
|
|
51
|
+
Note
|
|
52
|
+
----
|
|
53
|
+
When the bubble is thin-walled (due to nearly degenerate minima), an
|
|
54
|
+
approximate solution is found to the equations of motion and integration
|
|
55
|
+
starts close to the wall itself (instead of always starting at the center
|
|
56
|
+
of the bubble). This way the overshoot/undershoot method runs just as fast
|
|
57
|
+
for extremely thin-walled bubbles as it does for thick-walled bubbles.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
phi_absMin : float
|
|
62
|
+
The field value at the stable vacuum to which the instanton
|
|
63
|
+
tunnels. Nowhere in the code is it *required* that there actually be a
|
|
64
|
+
minimum at `phi_absMin`, but the :func:`findProfile` function will only
|
|
65
|
+
use initial conditions between `phi_absMin` and `phi_metaMin`, and the
|
|
66
|
+
code is optimized for thin-walled bubbles when the center of the
|
|
67
|
+
instanton is close to `phi_absMin`.
|
|
68
|
+
phi_metaMin : float
|
|
69
|
+
The field value in the metastable vacuum.
|
|
70
|
+
V : callable
|
|
71
|
+
The potential function. It should take as its single parameter the field
|
|
72
|
+
value `phi`.
|
|
73
|
+
dV, d2V : callable, optional
|
|
74
|
+
The potential's first and second derivatives. If not None, these
|
|
75
|
+
override the methods :func:`dV` and :func:`d2V`.
|
|
76
|
+
phi_eps : float, optional
|
|
77
|
+
A small value used to calculate derivatives (if not overriden by
|
|
78
|
+
the user) and in the function :func:`dV_from_absMin`. The input should
|
|
79
|
+
be unitless; it is later rescaled by ``abs(phi_absMin - phi_metaMin)``.
|
|
80
|
+
alpha : int or float, optional
|
|
81
|
+
The coefficient for the friction term in the ODE. This is also
|
|
82
|
+
the number of spacetime dimensions minus 1.
|
|
83
|
+
phi_bar : float, optional
|
|
84
|
+
The field value at the edge of the barrier. If `None`, it is found by
|
|
85
|
+
:func:`findBarrierLocation`.
|
|
86
|
+
rscale : float, optional
|
|
87
|
+
The approximate radial scale of the instanton. If `None` it is found by
|
|
88
|
+
:func:`findRScale`.
|
|
89
|
+
|
|
90
|
+
Raises
|
|
91
|
+
------
|
|
92
|
+
PotentialError
|
|
93
|
+
when the barrier is non-existent or when the presumably stable minimum
|
|
94
|
+
has a higher energy that the metastable minimum.
|
|
95
|
+
|
|
96
|
+
Examples
|
|
97
|
+
--------
|
|
98
|
+
The snippet below illustrates how to compute thin- and thick-walled instanton profiles.
|
|
99
|
+
It stems from the original ``cosmoTransitions`` package::
|
|
100
|
+
|
|
101
|
+
from cosmoTransitions.tunneling1D import SingleFieldInstanton
|
|
102
|
+
import matplotlib.pyplot as plt
|
|
103
|
+
|
|
104
|
+
# Thin-walled example
|
|
105
|
+
def V1(phi):
|
|
106
|
+
return 0.25 * phi**4 - 0.49 * phi**3 + 0.235 * phi**2
|
|
107
|
+
|
|
108
|
+
def dV1(phi):
|
|
109
|
+
return phi * (phi - 0.47) * (phi - 1.0)
|
|
110
|
+
|
|
111
|
+
profile = SingleFieldInstanton(1.0, 0.0, V1, dV1).findProfile()
|
|
112
|
+
plt.plot(profile.R, profile.Phi, label="Thin-walled")
|
|
113
|
+
|
|
114
|
+
# Thick-walled example
|
|
115
|
+
def V2(phi):
|
|
116
|
+
return 0.25 * phi**4 - 0.4 * phi**3 + 0.1 * phi**2
|
|
117
|
+
|
|
118
|
+
def dV2(phi):
|
|
119
|
+
return phi * (phi - 0.2) * (phi - 1.0)
|
|
120
|
+
|
|
121
|
+
profile = SingleFieldInstanton(1.0, 0.0, V2, dV2).findProfile()
|
|
122
|
+
plt.plot(profile.R, profile.Phi, label="Thick-walled")
|
|
123
|
+
|
|
124
|
+
plt.xlabel(r"Radius $r$")
|
|
125
|
+
plt.ylabel(r"Field $\phi$")
|
|
126
|
+
plt.legend()
|
|
127
|
+
plt.show()
|
|
128
|
+
"""
|
|
129
|
+
def __init__(self, phi_absMin, phi_metaMin, V,
|
|
130
|
+
dV=None, d2V=None, phi_eps=1e-3, alpha=2,
|
|
131
|
+
phi_bar=None, rscale=None):
|
|
132
|
+
"""Store the potential and prepare numerical parameters for shooting."""
|
|
133
|
+
self.phi_absMin, self.phi_metaMin = phi_absMin, phi_metaMin
|
|
134
|
+
self.V = V
|
|
135
|
+
if V(phi_metaMin) <= V(phi_absMin):
|
|
136
|
+
raise errors.PotentialError(
|
|
137
|
+
"V(phi_metaMin) <= V(phi_absMin); tunneling cannot occur.",
|
|
138
|
+
"stable, not metastable",
|
|
139
|
+
)
|
|
140
|
+
if dV is not None:
|
|
141
|
+
self.dV = dV
|
|
142
|
+
|
|
143
|
+
if d2V is not None:
|
|
144
|
+
self.d2V = d2V
|
|
145
|
+
|
|
146
|
+
if phi_bar is None:
|
|
147
|
+
self.phi_bar = self.findBarrierLocation()
|
|
148
|
+
else:
|
|
149
|
+
self.phi_bar = phi_bar
|
|
150
|
+
|
|
151
|
+
if rscale is None:
|
|
152
|
+
self.rscale = self.findRScale()
|
|
153
|
+
else:
|
|
154
|
+
self.rscale = rscale
|
|
155
|
+
self.alpha = alpha
|
|
156
|
+
self.phi_eps = phi_eps * abs(phi_absMin - phi_metaMin)
|
|
157
|
+
|
|
158
|
+
def dV(self, phi):
|
|
159
|
+
R"""
|
|
160
|
+
Calculates `dV/dphi` using finite differences.
|
|
161
|
+
|
|
162
|
+
The finite difference is given by `self.phi_eps`, and the derivative
|
|
163
|
+
is calculated to fourth order.
|
|
164
|
+
"""
|
|
165
|
+
eps = self.phi_eps
|
|
166
|
+
V = self.V
|
|
167
|
+
return (V(phi-2*eps) - 8*V(phi-eps) + 8*V(phi+eps) - V(phi+2*eps)
|
|
168
|
+
) / (12.*eps)
|
|
169
|
+
|
|
170
|
+
def dV_from_absMin(self, delta_phi):
|
|
171
|
+
R"""
|
|
172
|
+
Calculates `dV/dphi` at ``phi = phi_absMin + delta_phi``.
|
|
173
|
+
|
|
174
|
+
It is sometimes helpful to find `dV/dphi` extremely close to the
|
|
175
|
+
minimum. In this case, floating-point error can be significant. To get
|
|
176
|
+
increased accuracy, this function expands about the minimum in
|
|
177
|
+
a Taylor series and uses that for nearby values. That is,
|
|
178
|
+
:math:`V'(\phi) \approx V''(\phi_{\rm absMin})(\phi-\phi_{\rm absMin})`.
|
|
179
|
+
For values that are farther away, it instead uses :func:`dV`.
|
|
180
|
+
It blends the two methods so that there are no numerical
|
|
181
|
+
discontinuities.
|
|
182
|
+
|
|
183
|
+
This uses `self.phi_eps` to determine whether the field is considered
|
|
184
|
+
nearby or not.
|
|
185
|
+
"""
|
|
186
|
+
phi = self.phi_absMin + delta_phi
|
|
187
|
+
dV = self.dV(phi)
|
|
188
|
+
# If phi is very close to phi_absMin, it should be safer to assume
|
|
189
|
+
# that dV is zero exactly at phi_absMin and instead calculate dV from
|
|
190
|
+
# d2V.
|
|
191
|
+
if self.phi_eps > 0:
|
|
192
|
+
dV_ = self.d2V(phi) * delta_phi
|
|
193
|
+
# blend the two together so that there are no discontinuites
|
|
194
|
+
blend_factor = np.exp(-(delta_phi/self.phi_eps)**2)
|
|
195
|
+
dV = dV_*blend_factor + dV*(1-blend_factor)
|
|
196
|
+
return dV
|
|
197
|
+
|
|
198
|
+
def d2V(self, phi):
|
|
199
|
+
R"""
|
|
200
|
+
Calculates `d^2V/dphi^2` using finite differences.
|
|
201
|
+
|
|
202
|
+
The finite difference is given by `self.phi_eps`, and the derivative
|
|
203
|
+
is calculated to fourth order.
|
|
204
|
+
"""
|
|
205
|
+
eps = self.phi_eps
|
|
206
|
+
V = self.V
|
|
207
|
+
return (-V(phi-2*eps) + 16*V(phi-eps) - 30*V(phi)
|
|
208
|
+
+ 16*V(phi+eps) - V(phi+2*eps)) / (12.*eps*eps)
|
|
209
|
+
|
|
210
|
+
def findBarrierLocation(self):
|
|
211
|
+
R"""
|
|
212
|
+
Find edge of the potential barrier.
|
|
213
|
+
|
|
214
|
+
Returns
|
|
215
|
+
-------
|
|
216
|
+
phi_barrier : float
|
|
217
|
+
The value such that `V(phi_barrier) = V(phi_metaMin)`
|
|
218
|
+
"""
|
|
219
|
+
phi_tol = abs(self.phi_metaMin - self.phi_absMin) * 1e-12
|
|
220
|
+
V_phimeta = self.V(self.phi_metaMin)
|
|
221
|
+
phi1 = self.phi_metaMin
|
|
222
|
+
phi2 = self.phi_absMin
|
|
223
|
+
phi0 = 0.5 * (phi1+phi2)
|
|
224
|
+
|
|
225
|
+
# Do a very simple binary search to narrow down on the right answer.
|
|
226
|
+
while abs(phi1-phi2) > phi_tol:
|
|
227
|
+
V0 = self.V(phi0)
|
|
228
|
+
if V0 > V_phimeta:
|
|
229
|
+
phi1 = phi0
|
|
230
|
+
else:
|
|
231
|
+
phi2 = phi0
|
|
232
|
+
phi0 = 0.5 * (phi1+phi2)
|
|
233
|
+
return phi0
|
|
234
|
+
|
|
235
|
+
def findRscaleNew(self, phimeta, phi0):
|
|
236
|
+
r"""This estimates the time the field needs to roll down to the minimum
|
|
237
|
+
by assuming a linear potential between phi0 and phimeta.
|
|
238
|
+
|
|
239
|
+
Parameters
|
|
240
|
+
----------
|
|
241
|
+
V : function
|
|
242
|
+
Returns the potential at V(phi)
|
|
243
|
+
phi0 : float
|
|
244
|
+
The field release point
|
|
245
|
+
phimeta : float
|
|
246
|
+
The meta stable minimum of the potential
|
|
247
|
+
ndim : int
|
|
248
|
+
Number of dimensions.
|
|
249
|
+
|
|
250
|
+
The analytic
|
|
251
|
+
solution to the equation
|
|
252
|
+
|
|
253
|
+
.. math::
|
|
254
|
+
\frac{d^2\phi}{dr^2} + \frac{\nu}{r}\frac{d\phi}{dr} = b
|
|
255
|
+
|
|
256
|
+
is
|
|
257
|
+
|
|
258
|
+
.. math::
|
|
259
|
+
\phi(r) = \phi_0 + \frac{b}{2(1+\nu)} r^2
|
|
260
|
+
|
|
261
|
+
Get the time that takes the field to roll down
|
|
262
|
+
a linear potential."""
|
|
263
|
+
|
|
264
|
+
nu = self.alpha
|
|
265
|
+
R = np.sqrt((phimeta-phi0)**2/np.abs(self.V(phimeta) - self.V(phi0)) *2*(1+nu))
|
|
266
|
+
return R
|
|
267
|
+
|
|
268
|
+
def findRScale(self):
|
|
269
|
+
R"""
|
|
270
|
+
Find the characteristic length scale for tunneling over the potential
|
|
271
|
+
barrier.
|
|
272
|
+
|
|
273
|
+
The characteristic length scale should formally be given by the period
|
|
274
|
+
of oscillations about the top of the potential barrier. However, it is
|
|
275
|
+
perfectly acceptable for the potential barrier to have a flat top, in
|
|
276
|
+
which case a naive calculation of the length scale would be infinite.
|
|
277
|
+
Instead, this function finds the top of the barrier along with a cubic
|
|
278
|
+
function that has a maximum at the barrier top and a minimum at the
|
|
279
|
+
metastable minimum. The returned length scale is then the period of
|
|
280
|
+
oscillations about this cubic maximum.
|
|
281
|
+
|
|
282
|
+
Raises
|
|
283
|
+
------
|
|
284
|
+
PotentialError
|
|
285
|
+
when the barrier is non-existent.
|
|
286
|
+
"""
|
|
287
|
+
"""
|
|
288
|
+
NOT USED:
|
|
289
|
+
We could also do a sanity check in case the barrier goes to zero.
|
|
290
|
+
A second way of finding the scale is to see how long it would take
|
|
291
|
+
the field to roll from one minimum to the other if the potential were
|
|
292
|
+
purely linear and there were no friction.
|
|
293
|
+
|
|
294
|
+
Parameters
|
|
295
|
+
----------
|
|
296
|
+
second_check : float
|
|
297
|
+
If bigger than zero, do the sanity check. Return value is then the
|
|
298
|
+
larger of the first scale and the second scale times
|
|
299
|
+
`second_check`.
|
|
300
|
+
"""
|
|
301
|
+
phi_tol = abs(self.phi_bar - self.phi_metaMin) * 1e-6
|
|
302
|
+
x1 = min(self.phi_bar, self.phi_metaMin)
|
|
303
|
+
x2 = max(self.phi_bar, self.phi_metaMin)
|
|
304
|
+
phi_bar_top = optimize.fminbound(
|
|
305
|
+
lambda x: -self.V(x), x1, x2, xtol=phi_tol)
|
|
306
|
+
if phi_bar_top + phi_tol > x2 or phi_bar_top - phi_tol < x1:
|
|
307
|
+
raise errors.PotentialError(
|
|
308
|
+
"Minimization is placing the top of the potential barrier "
|
|
309
|
+
"outside of the interval defined by phi_bar and phi_metaMin. "
|
|
310
|
+
"Assume that the barrier does not exist.",
|
|
311
|
+
"no barrier",
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
Vtop = self.V(phi_bar_top) - self.V(self.phi_metaMin)
|
|
315
|
+
xtop = phi_bar_top - self.phi_metaMin
|
|
316
|
+
# Cubic function given by (ignoring linear and constant terms):
|
|
317
|
+
# f(x) = C [(-1/3)x^3 + (1/2)x^2 xtop]
|
|
318
|
+
# C = 6 Vtop / xtop^3
|
|
319
|
+
# f''(xtop) = - C xtop
|
|
320
|
+
# d2V = -6*Vtop / xtop**2
|
|
321
|
+
# rscale = 1 / sqrt(d2V)
|
|
322
|
+
if Vtop <= 0:
|
|
323
|
+
raise errors.PotentialError(
|
|
324
|
+
"Barrier height is not positive, does not exist.",
|
|
325
|
+
"no barrier",
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
rscale1 = abs(xtop) / np.sqrt(abs(6*Vtop))
|
|
329
|
+
return rscale1
|
|
330
|
+
# The following would calculate it a separate way, but this goes
|
|
331
|
+
# to infinity when delta_V goes to zero, so it's a bad way of doing it
|
|
332
|
+
delta_phi = abs(self.phi_absMin - self.phi_metaMin)
|
|
333
|
+
delta_V = abs(self.V(self.phi_absMin) - self.V(self.phi_metaMin))
|
|
334
|
+
rscale2 = np.sqrt(2*delta_phi**2 / (delta_V+1e-100))
|
|
335
|
+
return max(rscale1, rscale2)
|
|
336
|
+
|
|
337
|
+
_exactSolution_rval = namedtuple("exactSolution_rval", "phi dphi")
|
|
338
|
+
def exactSolution(self, r, phi0, dV, d2V):
|
|
339
|
+
R"""
|
|
340
|
+
Find `phi(r)` given `phi(r=0)`, assuming a quadratic potential.
|
|
341
|
+
|
|
342
|
+
Parameters
|
|
343
|
+
----------
|
|
344
|
+
r : float
|
|
345
|
+
The radius at which the solution should be calculated.
|
|
346
|
+
phi0 : float
|
|
347
|
+
The field at `r=0`.
|
|
348
|
+
dV, d2V : float
|
|
349
|
+
The potential's first and second derivatives evaluated at `phi0`.
|
|
350
|
+
|
|
351
|
+
Returns
|
|
352
|
+
-------
|
|
353
|
+
phi, dphi : float
|
|
354
|
+
The field and its derivative evaluated at `r`.
|
|
355
|
+
|
|
356
|
+
Notes
|
|
357
|
+
-----
|
|
358
|
+
|
|
359
|
+
If the potential at the point :math:`\phi_0` is a simple quadratic, the
|
|
360
|
+
solution to the instanton equation of motion can be determined exactly.
|
|
361
|
+
The non-singular solution to
|
|
362
|
+
|
|
363
|
+
.. math::
|
|
364
|
+
\frac{d^2\phi}{dr^2} + \frac{\alpha}{r}\frac{d\phi}{dr} =
|
|
365
|
+
V'(\phi_0) + V''(\phi_0) (\phi-\phi_0)
|
|
366
|
+
|
|
367
|
+
is
|
|
368
|
+
|
|
369
|
+
.. math::
|
|
370
|
+
\phi(r)-\phi_0 = \frac{V'}{V''}\left[
|
|
371
|
+
\Gamma(\nu+1)\left(\frac{\beta r}{2}\right)^{-\nu} I_\nu(\beta r) - 1
|
|
372
|
+
\right]
|
|
373
|
+
|
|
374
|
+
where :math:`\nu = \frac{\alpha-1}{2}`, :math:`I_\nu` is the modified
|
|
375
|
+
Bessel function, and :math:`\beta^2 = V''(\phi_0) > 0`. If instead
|
|
376
|
+
:math:`-\beta^2 = V''(\phi_0) < 0`, the solution is the same but with
|
|
377
|
+
:math:`I_\nu \rightarrow J_\nu`.
|
|
378
|
+
|
|
379
|
+
"""
|
|
380
|
+
beta = np.sqrt(abs(d2V))
|
|
381
|
+
beta_r = beta * r # may overflow when r is very large; handled below
|
|
382
|
+
nu = 0.5 * (self.alpha - 1)
|
|
383
|
+
gamma = special.gamma # Gamma function
|
|
384
|
+
iv, jv = special.iv, special.jv # (modified) Bessel function
|
|
385
|
+
if beta_r < 1e-2:
|
|
386
|
+
# Use a small-r approximation for the Bessel function.
|
|
387
|
+
s = +1 if d2V > 0 else -1
|
|
388
|
+
phi = 0.0
|
|
389
|
+
dphi = 0.0
|
|
390
|
+
for k in range(1,4):
|
|
391
|
+
_ = (0.5*beta_r)**(2*k-2) * s**k / (gamma(k+1)*gamma(k+1+nu))
|
|
392
|
+
phi += _
|
|
393
|
+
dphi += _ * (2*k)
|
|
394
|
+
phi *= 0.25 * gamma(nu+1) * r**2 * dV * s
|
|
395
|
+
dphi *= 0.25 * gamma(nu+1) * r * dV * s
|
|
396
|
+
phi += phi0
|
|
397
|
+
elif d2V > 0:
|
|
398
|
+
import warnings
|
|
399
|
+
# If beta_r is very large, this will throw off overflow and divide
|
|
400
|
+
# by zero errors in iv(). It will return np.inf though, which is
|
|
401
|
+
# what we want. Just ignore the warnings.
|
|
402
|
+
with warnings.catch_warnings():
|
|
403
|
+
warnings.simplefilter("ignore")
|
|
404
|
+
phi = (gamma(nu+1)*(0.5*beta_r)**-nu * iv(nu, beta_r)-1) * dV/d2V
|
|
405
|
+
dphi = -nu*((0.5*beta_r)**-nu / r) * iv(nu, beta_r)
|
|
406
|
+
dphi += (0.5*beta_r)**-nu * 0.5*beta \
|
|
407
|
+
* (iv(nu-1, beta_r)+iv(nu+1, beta_r))
|
|
408
|
+
dphi *= gamma(nu+1) * dV/d2V
|
|
409
|
+
phi += phi0
|
|
410
|
+
else:
|
|
411
|
+
phi = (gamma(nu+1)*(0.5*beta_r)**-nu * jv(nu, beta_r) - 1) * dV/d2V
|
|
412
|
+
dphi = -nu*((0.5*beta_r)**-nu / r) * jv(nu, beta_r)
|
|
413
|
+
dphi += (0.5*beta_r)**-nu * 0.5*beta \
|
|
414
|
+
* (jv(nu-1, beta_r)-jv(nu+1, beta_r))
|
|
415
|
+
dphi *= gamma(nu+1) * dV/d2V
|
|
416
|
+
phi += phi0
|
|
417
|
+
return self._exactSolution_rval(phi, dphi)
|
|
418
|
+
|
|
419
|
+
_initialConditions_rval = namedtuple(
|
|
420
|
+
"initialConditions_rval", "r0 phi dphi")
|
|
421
|
+
def initialConditions(self, delta_phi0, rmin, delta_phi_cutoff):
|
|
422
|
+
R"""
|
|
423
|
+
Finds the initial conditions for integration.
|
|
424
|
+
|
|
425
|
+
The instanton equations of motion are singular at `r=0`, so we
|
|
426
|
+
need to start the integration at some larger radius. This
|
|
427
|
+
function finds the value `r0` such that `phi(r0) = phi_cutoff`.
|
|
428
|
+
If there is no such value, it returns the intial conditions at `rmin`.
|
|
429
|
+
|
|
430
|
+
Parameters
|
|
431
|
+
----------
|
|
432
|
+
delta_phi0 : float
|
|
433
|
+
`delta_phi0 = phi(r=0) - phi_absMin`
|
|
434
|
+
rmin : float
|
|
435
|
+
The smallest acceptable radius at which to start integration.
|
|
436
|
+
delta_phi_cutoff : float
|
|
437
|
+
The desired value for `phi(r0)`.
|
|
438
|
+
`delta_phi_cutoff = phi(r0) - phi_absMin`.
|
|
439
|
+
|
|
440
|
+
Returns
|
|
441
|
+
-------
|
|
442
|
+
r0, phi, dphi : float
|
|
443
|
+
The initial radius and the field and its derivative at that radius.
|
|
444
|
+
|
|
445
|
+
Notes
|
|
446
|
+
-----
|
|
447
|
+
The field values are calculated using :func:`exactSolution`.
|
|
448
|
+
"""
|
|
449
|
+
phi0 = self.phi_absMin + delta_phi0
|
|
450
|
+
dV = self.dV_from_absMin(delta_phi0)
|
|
451
|
+
d2V = self.d2V(phi0)
|
|
452
|
+
phi_r0, dphi_r0 = self.exactSolution(rmin, phi0, dV, d2V)
|
|
453
|
+
if abs(phi_r0 - self.phi_absMin) > abs(delta_phi_cutoff):
|
|
454
|
+
# The initial conditions at rmin work. Stop here.
|
|
455
|
+
return self._initialConditions_rval(rmin, phi_r0, dphi_r0)
|
|
456
|
+
if np.sign(dphi_r0) != np.sign(delta_phi0):
|
|
457
|
+
# The field is evolving in the wrong direction.
|
|
458
|
+
# Increasing r0 won't increase |delta_phi_r0|/
|
|
459
|
+
return rmin, phi_r0, dphi_r0
|
|
460
|
+
|
|
461
|
+
# Find the smallest r0 such that delta_phi_r0 > delta_phi_cutoff
|
|
462
|
+
r = rmin
|
|
463
|
+
while np.isfinite(r):
|
|
464
|
+
# Once r grows beyond the floating-point range, r *= 10 produces
|
|
465
|
+
# +inf and the exactSolution call below fails to converge. The
|
|
466
|
+
# loop guard on np.isfinite(r) catches that and exits cleanly.
|
|
467
|
+
rlast = r
|
|
468
|
+
r *= 10
|
|
469
|
+
phi, dphi = self.exactSolution(r, phi0, dV, d2V)
|
|
470
|
+
if abs(phi - self.phi_absMin) > abs(delta_phi_cutoff):
|
|
471
|
+
break
|
|
472
|
+
|
|
473
|
+
# Now find where phi - self.phi_absMin = delta_phi_cutoff exactly
|
|
474
|
+
|
|
475
|
+
def deltaPhiDiff(r_):
|
|
476
|
+
p = self.exactSolution(r_, phi0, dV, d2V)[0]
|
|
477
|
+
return abs(p - self.phi_absMin) - abs(delta_phi_cutoff)
|
|
478
|
+
|
|
479
|
+
try:
|
|
480
|
+
r0 = optimize.brentq(deltaPhiDiff, rlast, r, disp=False)
|
|
481
|
+
except Exception as e:
|
|
482
|
+
msg = (
|
|
483
|
+
"Could not find initial conditions for integration. The bubble "
|
|
484
|
+
"wall may be too thin-walled to integrate: "
|
|
485
|
+
+ str(e)
|
|
486
|
+
)
|
|
487
|
+
raise errors.TunnelingError(msg)
|
|
488
|
+
|
|
489
|
+
phi_r0, dphi_r0 = self.exactSolution(r0, phi0, dV, d2V)
|
|
490
|
+
return self._initialConditions_rval(r0, phi_r0, dphi_r0)
|
|
491
|
+
|
|
492
|
+
def equationOfMotion(self, y, r):
|
|
493
|
+
"""
|
|
494
|
+
Used to integrate the bubble wall.
|
|
495
|
+
"""
|
|
496
|
+
return np.array([y[1], self.dV(y[0])-self.alpha*y[1]/r])
|
|
497
|
+
|
|
498
|
+
_integrateProfile_rval = namedtuple(
|
|
499
|
+
"integrateProfile_rval", "r y convergence_type")
|
|
500
|
+
def integrateProfile(self, r0, y0, dr0,
|
|
501
|
+
epsfrac, epsabs, drmin, rmax, *eqn_args):
|
|
502
|
+
R"""
|
|
503
|
+
Integrate the bubble wall equation:
|
|
504
|
+
|
|
505
|
+
.. math::
|
|
506
|
+
\frac{d^2\phi}{dr^2} + \frac{\alpha}{r}\frac{d\phi}{dr} =
|
|
507
|
+
\frac{dV}{d\phi}.
|
|
508
|
+
|
|
509
|
+
The integration will stop when it either overshoots or undershoots
|
|
510
|
+
the false vacuum minimum, or when it converges upon the false vacuum
|
|
511
|
+
minimum.
|
|
512
|
+
|
|
513
|
+
Parameters
|
|
514
|
+
----------
|
|
515
|
+
r0 : float
|
|
516
|
+
The starting radius for the integration.
|
|
517
|
+
y0 : array_like
|
|
518
|
+
The starting values [phi(r0), dphi(r0)].
|
|
519
|
+
dr0 : float
|
|
520
|
+
The starting integration stepsize.
|
|
521
|
+
epsfrac, epsabs : float
|
|
522
|
+
The error tolerances used for integration. This is fed into
|
|
523
|
+
:func:`helper_functions.rkqs` and is used to test for convergence.
|
|
524
|
+
drmin : float
|
|
525
|
+
The minimum allowed value of `dr` before raising an error.
|
|
526
|
+
rmax : float
|
|
527
|
+
The maximum allowed value of `r-r0` before raising an error.
|
|
528
|
+
eqn_args : tuple
|
|
529
|
+
Extra arguments to pass to :func:`equationOfMotion`. Useful for
|
|
530
|
+
subclasses.
|
|
531
|
+
|
|
532
|
+
Returns
|
|
533
|
+
-------
|
|
534
|
+
r : float
|
|
535
|
+
The final radius.
|
|
536
|
+
y : array_like
|
|
537
|
+
The final field values [phi, dphi]
|
|
538
|
+
convergence_type : str
|
|
539
|
+
Either 'overshoot', 'undershoot', or 'converged'.
|
|
540
|
+
|
|
541
|
+
Raises
|
|
542
|
+
------
|
|
543
|
+
helper_functions.IntegrationError
|
|
544
|
+
"""
|
|
545
|
+
dr = dr0
|
|
546
|
+
# dY is the ODE that we use
|
|
547
|
+
def dY(y,r,args=eqn_args):
|
|
548
|
+
return self.equationOfMotion(y,r,*args)
|
|
549
|
+
dydr0 = dY(y0, r0)
|
|
550
|
+
ysign = np.sign(y0[0]-self.phi_metaMin)
|
|
551
|
+
# positive means we're heading down, negative means heading up.
|
|
552
|
+
rmax += r0
|
|
553
|
+
|
|
554
|
+
convergence_type = None
|
|
555
|
+
while True:
|
|
556
|
+
dy, dr, drnext = rkqs(y0, dydr0, r0, dY, dr, epsfrac, epsabs)
|
|
557
|
+
r1 = r0 + dr
|
|
558
|
+
y1 = y0 + dy
|
|
559
|
+
dydr1 = dY(y1,r1)
|
|
560
|
+
|
|
561
|
+
# Check for completion
|
|
562
|
+
if (r1 > rmax):
|
|
563
|
+
raise IntegrationError("r > rmax")
|
|
564
|
+
elif (dr < drmin):
|
|
565
|
+
raise IntegrationError("dr < drmin")
|
|
566
|
+
elif (abs(y1 - np.array([self.phi_metaMin,0])) < 3*epsabs).all():
|
|
567
|
+
r,y = r1,y1
|
|
568
|
+
convergence_type = "converged"
|
|
569
|
+
break
|
|
570
|
+
|
|
571
|
+
elif y1[1]*ysign > 0 or (y1[0]-self.phi_metaMin)*ysign < 0:
|
|
572
|
+
f = cubicInterpFunction(y0, dr*dydr0, y1, dr*dydr1)
|
|
573
|
+
if(y1[1]*ysign > 0):
|
|
574
|
+
# Extrapolate to where dphi(r) = 0
|
|
575
|
+
x = optimize.brentq(lambda x: f(x)[1], 0, 1)
|
|
576
|
+
convergence_type = "undershoot"
|
|
577
|
+
else:
|
|
578
|
+
# Extrapolate to where phi(r) = phi_metaMin
|
|
579
|
+
x = optimize.brentq(lambda x: f(x)[0]-self.phi_metaMin, 0,1)
|
|
580
|
+
convergence_type = "overshoot"
|
|
581
|
+
r = r0 + dr*x
|
|
582
|
+
y = f(x)
|
|
583
|
+
break
|
|
584
|
+
# Advance the integration variables
|
|
585
|
+
r0,y0,dydr0 = r1,y1,dydr1
|
|
586
|
+
dr = drnext
|
|
587
|
+
# Check convergence for a second time.
|
|
588
|
+
# The extrapolation in overshoot/undershoot might have gotten us within
|
|
589
|
+
# the acceptable error.
|
|
590
|
+
if (abs(y - np.array([self.phi_metaMin,0])) < 3*epsabs).all():
|
|
591
|
+
convergence_type = "converged"
|
|
592
|
+
return self._integrateProfile_rval(r, y, convergence_type)
|
|
593
|
+
|
|
594
|
+
profile_rval = namedtuple("Profile1D", "R Phi dPhi Rerr")
|
|
595
|
+
def integrateAndSaveProfile(self, R, y0, dr,
|
|
596
|
+
epsfrac, epsabs,drmin, *eqn_args):
|
|
597
|
+
"""
|
|
598
|
+
Integrate the bubble profile, saving the output in an array.
|
|
599
|
+
|
|
600
|
+
Parameters
|
|
601
|
+
----------
|
|
602
|
+
R: array_like
|
|
603
|
+
The array of points at which we want to save the profile.
|
|
604
|
+
y0 : float
|
|
605
|
+
The starting values [phi(r0), dphi(r0)].
|
|
606
|
+
dr : float
|
|
607
|
+
Starting stepsize.
|
|
608
|
+
epsfrac, epsabs : float
|
|
609
|
+
The error tolerances used for integration. This
|
|
610
|
+
is fed into :func:`helper_functions.rkqs`.
|
|
611
|
+
drmin : float
|
|
612
|
+
The smallest allowed stepsize.
|
|
613
|
+
eqn_args : tuple
|
|
614
|
+
Extra arguments to pass to :func:`equationOfMotion`. Useful for
|
|
615
|
+
subclasses.
|
|
616
|
+
|
|
617
|
+
Returns
|
|
618
|
+
-------
|
|
619
|
+
R, Phi, dPhi : array_like
|
|
620
|
+
Radii and field values which make up the bubble profile.
|
|
621
|
+
Rerr : float or None
|
|
622
|
+
The first value of `r` at which ``dr < drmin``, or `None` if
|
|
623
|
+
``dr >= drmin`` always.
|
|
624
|
+
|
|
625
|
+
Notes
|
|
626
|
+
-----
|
|
627
|
+
Subclasses can use this function without overriding it even if the
|
|
628
|
+
subclass uses more fields/values in its equation of motion (i.e.,
|
|
629
|
+
``len(y0) > 2``). This is accomplished by setting the class variable
|
|
630
|
+
`profile_rval` to a different named tuple type with more than four
|
|
631
|
+
inputs. The first three should always be *R, Phi, dPhi*, and the last
|
|
632
|
+
one should be *Rerr*, but additional values can be stuck in between.
|
|
633
|
+
"""
|
|
634
|
+
N = len(R)
|
|
635
|
+
R, r0 = np.array(R), R[0]
|
|
636
|
+
Yout = np.zeros((N,len(y0)))
|
|
637
|
+
Yout[0] = y0
|
|
638
|
+
# dY is the ODE that we use
|
|
639
|
+
def dY(y,r,args=eqn_args):
|
|
640
|
+
return self.equationOfMotion(y,r,*args)
|
|
641
|
+
dydr0 = dY(y0, r0)
|
|
642
|
+
Rerr = None
|
|
643
|
+
|
|
644
|
+
i = 1
|
|
645
|
+
while i < N:
|
|
646
|
+
dy, dr, drnext = rkqs(y0, dydr0, r0, dY, dr, epsfrac, epsabs)
|
|
647
|
+
if (dr >= drmin):
|
|
648
|
+
r1 = r0 + dr
|
|
649
|
+
y1 = y0 + dy
|
|
650
|
+
else:
|
|
651
|
+
y1 = y0 + dy*drmin/dr
|
|
652
|
+
dr = drnext = drmin
|
|
653
|
+
r1 = r0 + dr
|
|
654
|
+
if Rerr is not None: Rerr = r1
|
|
655
|
+
dydr1 = dY(y1,r1)
|
|
656
|
+
# Fill the arrays, if necessary
|
|
657
|
+
if (r0 < R[i] <= r1):
|
|
658
|
+
f = cubicInterpFunction(y0, dr*dydr0, y1, dr*dydr1)
|
|
659
|
+
while (i < N and r0 < R[i] <= r1):
|
|
660
|
+
x = (R[i]-r0)/dr
|
|
661
|
+
Yout[i] = f(x)
|
|
662
|
+
i += 1
|
|
663
|
+
|
|
664
|
+
# Advance the integration variables
|
|
665
|
+
r0,y0,dydr0 = r1,y1,dydr1
|
|
666
|
+
dr = drnext
|
|
667
|
+
|
|
668
|
+
rval = (R,)+tuple(Yout.T)+eqn_args+(Rerr,)
|
|
669
|
+
return self.profile_rval(*rval)
|
|
670
|
+
|
|
671
|
+
def findProfile(self, xguess=None, xtol=1e-4, phitol=1e-4,
|
|
672
|
+
thinCutoff=.01, npoints=500, rmin=1e-4, rmax=1e4,
|
|
673
|
+
max_interior_pts=None):
|
|
674
|
+
R"""
|
|
675
|
+
Calculate the bubble profile by iteratively over/undershooting.
|
|
676
|
+
|
|
677
|
+
This will call :func:`integrateProfile` many times, trying to find
|
|
678
|
+
the correct initial condition `phi(r=0)` such that the field ends up
|
|
679
|
+
in the metastable vacuum at infinity. Once the correct initial
|
|
680
|
+
condition is found, it calls :func:`integrateAndSaveProfile` to find
|
|
681
|
+
the profile along the length of the wall.
|
|
682
|
+
|
|
683
|
+
Parameters
|
|
684
|
+
----------
|
|
685
|
+
xguess : float, optional
|
|
686
|
+
The initial guess for `x`. If `None`, `xguess` is set such
|
|
687
|
+
that ``phi_guess = self.phi_bar``.
|
|
688
|
+
xtol : float, optional
|
|
689
|
+
Target accuracy in `x`.
|
|
690
|
+
phitol : float, optional
|
|
691
|
+
Fractional error tolerance in integration.
|
|
692
|
+
thinCutoff : float, optional
|
|
693
|
+
Equal to `delta_phi_cutoff / (phi_metaMin - phi_absMin)`, where
|
|
694
|
+
`delta_phi_cutoff` is used in :func:`initialConditions`.
|
|
695
|
+
npoints : int
|
|
696
|
+
Number of points to return in the profile.
|
|
697
|
+
rmin : float
|
|
698
|
+
Relative to ``self.rscale``. Sets the smallest starting
|
|
699
|
+
radius, the starting stepsize, and the smallest allowed stepsize
|
|
700
|
+
(``0.01*rmin``).
|
|
701
|
+
rmax : float
|
|
702
|
+
Relative ``self.rscale``. Sets the maximum allowed integration
|
|
703
|
+
distance.
|
|
704
|
+
max_interior_pts : int
|
|
705
|
+
Maximum number of points to place between ``r=0`` and the start of
|
|
706
|
+
integration. If None, ``max_interior_pts=npoints/2``. If zero, no
|
|
707
|
+
points are added to the bubble interior.
|
|
708
|
+
|
|
709
|
+
Returns
|
|
710
|
+
-------
|
|
711
|
+
R, Phi, dPhi : array_like
|
|
712
|
+
Radii and field values which make up the bubble profile. Note that
|
|
713
|
+
`R[0]` can be much bigger than zero for thin-walled bubbles.
|
|
714
|
+
Rerr : float or None
|
|
715
|
+
The first value of `r` at which ``dr < drmin``, or `None` if
|
|
716
|
+
``dr >= drmin`` always.
|
|
717
|
+
|
|
718
|
+
Notes
|
|
719
|
+
-----
|
|
720
|
+
For very thin-walled bubbles, the initial value of `phi` can be
|
|
721
|
+
extremely close to the stable minimum and small variations in `phi`
|
|
722
|
+
can cause large variations in the integration. Rather than varying
|
|
723
|
+
`phi(r=0)` directly, it is easier to vary a parameter `x` defined by
|
|
724
|
+
|
|
725
|
+
.. math::
|
|
726
|
+
\phi(r=0) = \phi_{\rm absMin}
|
|
727
|
+
+ e^{-x}(\phi_{\rm metaMin}-\phi_{\rm absMin})
|
|
728
|
+
|
|
729
|
+
This way, `phi = phi_metaMin` when `x` is zero and
|
|
730
|
+
`phi = phi_absMin` when `x` is infinity.
|
|
731
|
+
"""
|
|
732
|
+
# Set x parameters
|
|
733
|
+
xmin = xtol*10
|
|
734
|
+
xmax = np.inf
|
|
735
|
+
if xguess is not None:
|
|
736
|
+
x = xguess
|
|
737
|
+
else:
|
|
738
|
+
x = -np.log(abs((self.phi_bar-self.phi_absMin) /
|
|
739
|
+
(self.phi_metaMin-self.phi_absMin)))
|
|
740
|
+
xincrease = 5.0
|
|
741
|
+
# The relative amount to increase x by if there is no upper bound.
|
|
742
|
+
# --
|
|
743
|
+
# Set r parameters
|
|
744
|
+
rmin *= self.rscale
|
|
745
|
+
dr0 = rmin
|
|
746
|
+
drmin = 0.01*rmin
|
|
747
|
+
rmax *= self.rscale
|
|
748
|
+
# --
|
|
749
|
+
# Set the phi parameters
|
|
750
|
+
delta_phi = self.phi_metaMin - self.phi_absMin
|
|
751
|
+
epsabs = abs(np.array([delta_phi, delta_phi/self.rscale])*phitol)
|
|
752
|
+
epsfrac = np.array([1,1]) * phitol
|
|
753
|
+
delta_phi_cutoff = thinCutoff * delta_phi
|
|
754
|
+
# The sign for delta_phi_cutoff doesn't matter
|
|
755
|
+
# --
|
|
756
|
+
integration_args = (dr0, epsfrac, epsabs, drmin, rmax)
|
|
757
|
+
rf = None
|
|
758
|
+
while True:
|
|
759
|
+
delta_phi0 = np.exp(-x)*delta_phi
|
|
760
|
+
|
|
761
|
+
# The below part is new and tries to avoid
|
|
762
|
+
# the problem of having a too small dV
|
|
763
|
+
# when the bubble is thin-walled. This makes the
|
|
764
|
+
# integration more stable. There are still cases
|
|
765
|
+
# where it fails, but this is a good start. Those cases
|
|
766
|
+
# seem to be when the bubble is extremely thin-walled.
|
|
767
|
+
phi0 = self.phi_absMin + delta_phi0
|
|
768
|
+
dV = self.dV_from_absMin(delta_phi0)
|
|
769
|
+
if dV < 1e-200: # Sometimes we need such a really small dV!
|
|
770
|
+
# The derivative of the potential is too small, we are too close to
|
|
771
|
+
# the minimum. This is a problem for the integration.
|
|
772
|
+
raise errors.TooSmalldVError
|
|
773
|
+
|
|
774
|
+
try:
|
|
775
|
+
r0_, phi0, dphi0 = self.initialConditions(
|
|
776
|
+
delta_phi0, rmin, delta_phi_cutoff)
|
|
777
|
+
except errors.TunnelingError as e:
|
|
778
|
+
msg = (
|
|
779
|
+
"An error occurred in findProfile due to the initial "
|
|
780
|
+
"conditions computation. This is likely due to the bubble "
|
|
781
|
+
"being too thin-walled. "
|
|
782
|
+
+ str(e)
|
|
783
|
+
)
|
|
784
|
+
raise errors.TunnelingError(msg)
|
|
785
|
+
|
|
786
|
+
if not np.isfinite(r0_) or not np.isfinite(x):
|
|
787
|
+
# Use the last finite values instead
|
|
788
|
+
# (assuming there are such values)
|
|
789
|
+
assert rf is not None, "Failed to retrieve initial "\
|
|
790
|
+
"conditions on the first try."
|
|
791
|
+
break
|
|
792
|
+
r0 = r0_
|
|
793
|
+
y0 = np.array([phi0, dphi0])
|
|
794
|
+
try:
|
|
795
|
+
rf, yf, ctype = self.integrateProfile(r0, y0, *integration_args)
|
|
796
|
+
except IntegrationError as e:
|
|
797
|
+
raise errors.TunnelingError(
|
|
798
|
+
"Integration failed: " + str(e))
|
|
799
|
+
|
|
800
|
+
# Check for overshoot, undershoot
|
|
801
|
+
if ctype == "converged":
|
|
802
|
+
break
|
|
803
|
+
elif ctype == "undershoot": # x is too low
|
|
804
|
+
xmin = x
|
|
805
|
+
x = x*xincrease if xmax == np.inf else .5*(xmin+xmax)
|
|
806
|
+
elif ctype == "overshoot": # x is too high
|
|
807
|
+
xmax = x
|
|
808
|
+
x = .5*(xmin+xmax)
|
|
809
|
+
# Check if we've reached xtol
|
|
810
|
+
if (xmax-xmin) < xtol:
|
|
811
|
+
break
|
|
812
|
+
|
|
813
|
+
# Integrate a second time, this time getting the points along the way
|
|
814
|
+
R = np.linspace(r0, rf, npoints)
|
|
815
|
+
profile = self.integrateAndSaveProfile(R, y0, dr0,
|
|
816
|
+
epsfrac, epsabs, drmin)
|
|
817
|
+
# Make points interior to the bubble.
|
|
818
|
+
if max_interior_pts is None:
|
|
819
|
+
max_interior_pts = len(R) // 2
|
|
820
|
+
if max_interior_pts > 0:
|
|
821
|
+
dx0 = R[1]-R[0]
|
|
822
|
+
if R[0] / dx0 <= max_interior_pts:
|
|
823
|
+
n = int(np.ceil(R[0]/dx0))
|
|
824
|
+
R_int = np.linspace(0, R[0], n+1)[:-1]
|
|
825
|
+
else:
|
|
826
|
+
n = max_interior_pts
|
|
827
|
+
# R[0] = dx0 * (n + a*n*(n+1)/2)
|
|
828
|
+
a = (R[0]/dx0 - n) * 2/(n*(n+1))
|
|
829
|
+
N = np.arange(1,n+1)[::-1]
|
|
830
|
+
R_int = R[0] - dx0*(N + 0.5*a*N*(N+1))
|
|
831
|
+
R_int[0] = 0.0 # enforce this exactly
|
|
832
|
+
Phi_int = np.empty_like(R_int)
|
|
833
|
+
dPhi_int = np.empty_like(R_int)
|
|
834
|
+
Phi_int[0] = self.phi_absMin + delta_phi0
|
|
835
|
+
dPhi_int[0] = 0.0
|
|
836
|
+
dV = self.dV_from_absMin(delta_phi0)
|
|
837
|
+
d2V = self.d2V(Phi_int[0])
|
|
838
|
+
for i in range(1,len(R_int)):
|
|
839
|
+
Phi_int[i], dPhi_int[i] = self.exactSolution(
|
|
840
|
+
R_int[i], Phi_int[0], dV, d2V)
|
|
841
|
+
R = np.append(R_int, profile.R)
|
|
842
|
+
Phi = np.append(Phi_int, profile.Phi)
|
|
843
|
+
dPhi = np.append(dPhi_int, profile.dPhi)
|
|
844
|
+
profile = self.profile_rval(R,Phi,dPhi, profile.Rerr)
|
|
845
|
+
return profile
|
|
846
|
+
|
|
847
|
+
def findAction(self, profile):
|
|
848
|
+
R"""
|
|
849
|
+
Calculate the Euclidean action for the instanton:
|
|
850
|
+
|
|
851
|
+
.. math::
|
|
852
|
+
S = \int [(d\phi/dr)^2 + V(\phi)] r^\alpha dr d\Omega_\alpha
|
|
853
|
+
|
|
854
|
+
Arguments
|
|
855
|
+
---------
|
|
856
|
+
profile
|
|
857
|
+
Output from :func:`findProfile()`.
|
|
858
|
+
|
|
859
|
+
Returns
|
|
860
|
+
-------
|
|
861
|
+
float
|
|
862
|
+
The Euclidean action.
|
|
863
|
+
"""
|
|
864
|
+
r, phi, dphi = profile.R, profile.Phi, profile.dPhi
|
|
865
|
+
# Find the area of an n-sphere (alpha=n):
|
|
866
|
+
d = self.alpha+1 # Number of dimensions in the integration
|
|
867
|
+
area = r**self.alpha * 2*np.pi**(d*.5)/special.gamma(d*.5)
|
|
868
|
+
# And integrate the profile
|
|
869
|
+
integrand = 0.5 * dphi**2 + self.V(phi) - self.V(self.phi_metaMin)
|
|
870
|
+
integrand *= area
|
|
871
|
+
S = simpson(integrand, x=r)
|
|
872
|
+
# Find the bulk term in the bubble interior
|
|
873
|
+
volume = r[0]**d * np.pi**(d*.5)/special.gamma(d*.5 + 1)
|
|
874
|
+
S += volume * (self.V(phi[0]) - self.V(self.phi_metaMin))
|
|
875
|
+
return S
|
|
876
|
+
|
|
877
|
+
def evenlySpacedPhi(self, phi, dphi, npoints=100, k=1, fixAbs=True):
|
|
878
|
+
"""
|
|
879
|
+
This method takes `phi` and `dphi` as input, which will probably
|
|
880
|
+
come from the output of :func:`findProfile`, and returns a different
|
|
881
|
+
set of arrays `phi2` and `dphi2` such that `phi2` is linearly spaced
|
|
882
|
+
(instead of `r`).
|
|
883
|
+
|
|
884
|
+
Parameters
|
|
885
|
+
----------
|
|
886
|
+
phi, dphi : array_like
|
|
887
|
+
npoints : int
|
|
888
|
+
The number of points to output.
|
|
889
|
+
k : int
|
|
890
|
+
The degree of spline fitting. ``k=1`` means linear interpolation.
|
|
891
|
+
fixAbs : bool
|
|
892
|
+
If true, make phi go all the way to `phi_absMin`.
|
|
893
|
+
"""
|
|
894
|
+
if fixAbs is True:
|
|
895
|
+
phi = np.append(self.phi_absMin, np.append(phi, self.phi_metaMin))
|
|
896
|
+
dphi = np.append(0.0, np.append(dphi, 0.0))
|
|
897
|
+
else:
|
|
898
|
+
phi = np.append(phi, self.phi_metaMin)
|
|
899
|
+
dphi = np.append(dphi, 0.0)
|
|
900
|
+
# Make sure that phi is increasing everywhere
|
|
901
|
+
# (this is uglier than it ought to be)
|
|
902
|
+
i = helper_functions.monotonicIndices(phi)
|
|
903
|
+
# Now do the interpolation
|
|
904
|
+
tck = interpolate.splrep(phi[i], dphi[i], k=k)
|
|
905
|
+
if fixAbs:
|
|
906
|
+
p = np.linspace(self.phi_absMin, self.phi_metaMin, npoints)
|
|
907
|
+
else:
|
|
908
|
+
p = np.linspace(phi[i][0], self.phi_metaMin, npoints)
|
|
909
|
+
dp = interpolate.splev(p, tck)
|
|
910
|
+
return p, dp
|