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.
Files changed (95) hide show
  1. transitionlistener/__init__.py +21 -0
  2. transitionlistener/bubbledynamics.py +2250 -0
  3. transitionlistener/bubbledynamics_fixedstep.py +2929 -0
  4. transitionlistener/check_installation.py +309 -0
  5. transitionlistener/ckm.py +91 -0
  6. transitionlistener/colors.py +288 -0
  7. transitionlistener/config.py +185 -0
  8. transitionlistener/constants.py +64 -0
  9. transitionlistener/counterterms/__init__.py +230 -0
  10. transitionlistener/counterterms/cw.py +298 -0
  11. transitionlistener/counterterms/tensors.py +343 -0
  12. transitionlistener/counterterms/twohdm.py +520 -0
  13. transitionlistener/counterterms/twohdm_solver.py +374 -0
  14. transitionlistener/errors.py +315 -0
  15. transitionlistener/examples/bp_et.yaml +64 -0
  16. transitionlistener/examples/bp_lisa.yaml +70 -0
  17. transitionlistener/examples/bp_muares.yaml +69 -0
  18. transitionlistener/examples/bp_pta.yaml +64 -0
  19. transitionlistener/examples/example_dark_photon.yaml +73 -0
  20. transitionlistener/examples/example_grid.yaml +55 -0
  21. transitionlistener/examples/example_line.yaml +51 -0
  22. transitionlistener/examples/example_point.yaml +73 -0
  23. transitionlistener/examples/example_random_conformal.yaml +42 -0
  24. transitionlistener/examples/example_ultranest.yaml +39 -0
  25. transitionlistener/finiteT.py +468 -0
  26. transitionlistener/generic_potential.py +1521 -0
  27. transitionlistener/gridplots.py +592 -0
  28. transitionlistener/gwfopt.py +604 -0
  29. transitionlistener/helper_functions.py +843 -0
  30. transitionlistener/hydrodynamics.py +647 -0
  31. transitionlistener/interface/__init__.py +95 -0
  32. transitionlistener/interface/check.py +51 -0
  33. transitionlistener/interface/cli.py +211 -0
  34. transitionlistener/interface/config_loader.py +264 -0
  35. transitionlistener/interface/logging_utils.py +30 -0
  36. transitionlistener/interface/output_schema.py +296 -0
  37. transitionlistener/interface/pipeline.py +300 -0
  38. transitionlistener/interface/samplers.py +565 -0
  39. transitionlistener/interface/scans.py +468 -0
  40. transitionlistener/interface/single_point.py +581 -0
  41. transitionlistener/interface/state.py +61 -0
  42. transitionlistener/lineplots.py +597 -0
  43. transitionlistener/logo/TL-logo_large.png +0 -0
  44. transitionlistener/logo/TL-logo_small.png +0 -0
  45. transitionlistener/models/TL_2HDM.py +512 -0
  46. transitionlistener/models/TL_conformal_dark_u1.py +202 -0
  47. transitionlistener/models/TL_dark_U1.py +198 -0
  48. transitionlistener/models/TL_dark_U1_g_parameterization.py +198 -0
  49. transitionlistener/models/TL_dark_flipflop.py +274 -0
  50. transitionlistener/models/templatePotential.py +260 -0
  51. transitionlistener/nucleation.py +412 -0
  52. transitionlistener/observability.py +790 -0
  53. transitionlistener/particles.py +436 -0
  54. transitionlistener/pathDeformation.py +1462 -0
  55. transitionlistener/percolation_adaptive_gridbuilders.py +1385 -0
  56. transitionlistener/percolation_adaptive_rate.py +1049 -0
  57. transitionlistener/percolation_adaptivestepsize.py +1632 -0
  58. transitionlistener/percolation_fixedstepsize.py +618 -0
  59. transitionlistener/phases.py +1499 -0
  60. transitionlistener/plot_settings.py +121 -0
  61. transitionlistener/plots.py +1781 -0
  62. transitionlistener/runtime_options.py +278 -0
  63. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/bandwidths.npy +0 -0
  64. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/density.npy +0 -0
  65. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/freqs.npy +0 -0
  66. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log.txt +16 -0
  67. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log10rhogrid.npy +0 -0
  68. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log10rholabels.txt +30 -0
  69. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/pulsar_list.txt +1 -0
  70. transitionlistener/tab_data/HLV-O2-PLI.csv +82 -0
  71. transitionlistener/tab_data/HLV-O2-noise.csv +83 -0
  72. transitionlistener/tab_data/HLVK-design-PLI.csv +61 -0
  73. transitionlistener/tab_data/HLVK-design-noise.csv +83 -0
  74. transitionlistener/tab_data/NG15_PLI_hc.csv +31 -0
  75. transitionlistener/tab_data/NG15_noise_hc.csv +56 -0
  76. transitionlistener/tab_data/PLI-dataset.csv +327 -0
  77. transitionlistener/tab_data/energydensities.dat +1001 -0
  78. transitionlistener/tab_data/finiteT_b.dat.txt +1999 -0
  79. transitionlistener/tab_data/finiteT_f.dat.txt +1000 -0
  80. transitionlistener/tab_data/geff_SM.dat +10009 -0
  81. transitionlistener/tab_data/muAres_f_Hz_PLI_h2OmegaGW.csv +131 -0
  82. transitionlistener/tab_data/muAres_f_Hz_h2Omega_noise.csv +99 -0
  83. transitionlistener/tab_data/noise-dataset.csv +327 -0
  84. transitionlistener/tab_data/numberdensities.dat +1001 -0
  85. transitionlistener/tab_data/pressures.dat +1001 -0
  86. transitionlistener/thermodynamics.py +686 -0
  87. transitionlistener/transitionObservables.py +1199 -0
  88. transitionlistener/transitionObservables_fixedstep.py +976 -0
  89. transitionlistener/transitions.py +1000 -0
  90. transitionlistener/tunneling1D.py +910 -0
  91. transitionlistener-2.0.0.dist-info/METADATA +817 -0
  92. transitionlistener-2.0.0.dist-info/RECORD +95 -0
  93. transitionlistener-2.0.0.dist-info/WHEEL +4 -0
  94. transitionlistener-2.0.0.dist-info/entry_points.txt +2 -0
  95. 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