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,1462 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A module for finding instantons between vacua in multiple field dimensions.
|
|
3
|
+
|
|
4
|
+
The basic strategy is an iterative process:
|
|
5
|
+
|
|
6
|
+
1. Make an ansatz for the path along which the field will travel.
|
|
7
|
+
2. Split up the equations of motion into components that are parallel and
|
|
8
|
+
perpendicular to the direction of travel along the path.
|
|
9
|
+
3. The direction of motion parallel to the path reduces to a
|
|
10
|
+
one-dimensional equation of motion, which can be solved using the
|
|
11
|
+
overshoot / undershoot techniques in :mod:`.tunneling1D`. Solve it.
|
|
12
|
+
4. Treating the motion of the field as a classical particle moving in an
|
|
13
|
+
inverted potential, calculate the normal forces that would need to act
|
|
14
|
+
on the particle to keep it on the path. If this forces are (close enough
|
|
15
|
+
to) zero, the ansatz was correctly. Otherwise iteratively deform the path
|
|
16
|
+
in the direction of the normal forces, stopping when the forces go to
|
|
17
|
+
zero.
|
|
18
|
+
5. Loop back to step 3 until no further deformation is necessary.
|
|
19
|
+
|
|
20
|
+
The classes :class:`Deformation_Spline` and :class:`Deformation_Points` will
|
|
21
|
+
perform step 3, while :func:`fullTunneling` will run the entire loop.
|
|
22
|
+
|
|
23
|
+
For more explicit details, see the original paper
|
|
24
|
+
`Comput. Phys. Commun. 183 (2012)`_ [`arXiv:1109.4189`_].
|
|
25
|
+
|
|
26
|
+
.. _`Comput. Phys. Commun. 183 (2012)`:
|
|
27
|
+
http://dx.doi.org/10.1016/j.cpc.2012.04.004
|
|
28
|
+
|
|
29
|
+
.. _`arXiv:1109.4189`: http://arxiv.org/abs/1109.4189
|
|
30
|
+
|
|
31
|
+
Part of TransitionListener v2.0
|
|
32
|
+
Documentation: https://tasillo.de/TransitionListener/
|
|
33
|
+
|
|
34
|
+
Authors:
|
|
35
|
+
Jonas Matuszak <jonas.matuszak@kit.edu>
|
|
36
|
+
Carlo Tasillo <carlo.tasillo@ific.uv.es>
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from typing import Callable
|
|
40
|
+
import numpy as np
|
|
41
|
+
from dataclasses import dataclass
|
|
42
|
+
from scipy import optimize, interpolate, integrate
|
|
43
|
+
from collections import namedtuple
|
|
44
|
+
from . import tunneling1D
|
|
45
|
+
from . import helper_functions
|
|
46
|
+
from . import errors
|
|
47
|
+
|
|
48
|
+
class Deformation_Spline(object):
|
|
49
|
+
"""
|
|
50
|
+
Deform a path in the presence of a potential such that the normal forces
|
|
51
|
+
along the path are zero.
|
|
52
|
+
|
|
53
|
+
This class fits a spline to the points, and does the actual deformation
|
|
54
|
+
on the spline rather than on the points themselves. This make the path
|
|
55
|
+
somewhat smoother than it would otherwise be (which is generally desirable),
|
|
56
|
+
but it does make it difficult to resolve sharp turns in the path.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
phi : array_like
|
|
61
|
+
The list of points that constitutes the original path. Should have
|
|
62
|
+
shape ``(n_points, n_dimensions)``.
|
|
63
|
+
dphidr : array_like
|
|
64
|
+
The 'speed' along the path at the initial points. This does not change
|
|
65
|
+
as the path deforms. Should have shape ``(n_points,)``. Gets saved into
|
|
66
|
+
the attribute `v2` as ``v2 = dphidr[:,np.newaxis]**2``.
|
|
67
|
+
dV : callable
|
|
68
|
+
The potential gradient as a function of phi. The output shape should be
|
|
69
|
+
the same as the input shape, which will be ``(..., n_dimensions)``.
|
|
70
|
+
nb : int, optional
|
|
71
|
+
Number of basis splines to use.
|
|
72
|
+
kb : int, optional
|
|
73
|
+
Order of basis splines.
|
|
74
|
+
v2min : float, optional
|
|
75
|
+
The smallest the square of dphidr is allowed to be, relative
|
|
76
|
+
to the characteristic force exterted by F_ext. Note that the
|
|
77
|
+
self-correcting nature of the deformation goes away when dphidr=0.
|
|
78
|
+
fix_start, fix_end : bool, optional
|
|
79
|
+
If True, the force on the first/last point along the path is set to
|
|
80
|
+
zero, so the point will not change in the deformation step.
|
|
81
|
+
save_all_steps : bool, optional
|
|
82
|
+
If True, each step gets saved into ``self.phi_list`` and
|
|
83
|
+
``self.F_list``.
|
|
84
|
+
|
|
85
|
+
Attributes
|
|
86
|
+
----------
|
|
87
|
+
phi : array_like
|
|
88
|
+
Set during initialization, and then rewritten at each step.
|
|
89
|
+
num_steps : int
|
|
90
|
+
Total number of steps taken.
|
|
91
|
+
"""
|
|
92
|
+
"""
|
|
93
|
+
Additional (private) attributes
|
|
94
|
+
-------------------------------
|
|
95
|
+
_L : float
|
|
96
|
+
Total length of the path, set during initialization.
|
|
97
|
+
_t : array_like
|
|
98
|
+
Array from (0,1] marking the locations of each point.
|
|
99
|
+
_X, _dX, _d2X : array_like
|
|
100
|
+
Spline basis functions and their derivatives evaluated at `_t`. Set
|
|
101
|
+
during initialization.
|
|
102
|
+
_beta : array_like
|
|
103
|
+
The spline coefficients for each dimension. Recalculated each step.
|
|
104
|
+
_F_prev, _phi_prev : array_like
|
|
105
|
+
The normal force and the path points at the last step.
|
|
106
|
+
"""
|
|
107
|
+
def __init__(self, phi: np.ndarray, dphidr: np.ndarray, dV: callable,
|
|
108
|
+
nb: int=10, kb: int=3, v2min: float=0.0,
|
|
109
|
+
fix_start: bool=False, fix_end: bool=False,
|
|
110
|
+
save_all_steps: bool=False):
|
|
111
|
+
"""Pre-compute spline basis functions and store the deformation state."""
|
|
112
|
+
# First step: convert phi to a set of path lengths.
|
|
113
|
+
phi = np.asanyarray(phi)
|
|
114
|
+
dphi = phi[1:]-phi[:-1]
|
|
115
|
+
dL = np.sqrt(np.sum(dphi*dphi,axis=-1))
|
|
116
|
+
y = np.cumsum(dL)
|
|
117
|
+
self._L = y[-1]
|
|
118
|
+
self._t = np.append(0,y)/self._L
|
|
119
|
+
self._t[0] = 1e-100 # Without this, the first data point isn't in
|
|
120
|
+
# any bin (this matters for dX).
|
|
121
|
+
|
|
122
|
+
# Create the starting spline:
|
|
123
|
+
# make the knots and then the spline matrices at each point t
|
|
124
|
+
t0 = np.append(np.append([0.]*(kb-1), np.linspace(0,1,nb+3-kb)),
|
|
125
|
+
[1.]*(kb-1))
|
|
126
|
+
self._X,self._dX,self._d2X = helper_functions.Nbspld2(t0, self._t, kb)
|
|
127
|
+
self._t = self._t[:,np.newaxis] # Shape (n, 1)
|
|
128
|
+
# subtract off the linear component.
|
|
129
|
+
phi0, phi1 = phi[:1], phi[-1:] # These are shape (1,N)
|
|
130
|
+
phi_lin = phi0 + (phi1-phi0)*self._t
|
|
131
|
+
self._beta, residues, rank, s = np.linalg.lstsq(self._X, phi - phi_lin, rcond=None)
|
|
132
|
+
|
|
133
|
+
# save the points for future use.
|
|
134
|
+
self.phi = np.asanyarray(phi) # shape (n,N)
|
|
135
|
+
self.v2 = np.asanyarray(dphidr)[:,np.newaxis]**2 # shape (n,1)
|
|
136
|
+
self.dV = dV
|
|
137
|
+
self.F_list = []
|
|
138
|
+
self.phi_list = []
|
|
139
|
+
self._phi_prev = self._F_prev = None
|
|
140
|
+
self.save_all_steps = save_all_steps
|
|
141
|
+
self.fix_start, self.fix_end = fix_start, fix_end
|
|
142
|
+
self.num_steps = 0
|
|
143
|
+
|
|
144
|
+
# ensure that v2 isn't too small:
|
|
145
|
+
v2 = dphidr**2
|
|
146
|
+
v2min *= np.max(np.sum(dV(self.phi)**2, -1)**.5*self._L/nb)
|
|
147
|
+
v2[v2 < v2min] = v2min
|
|
148
|
+
self.v2 = v2[:,np.newaxis]
|
|
149
|
+
|
|
150
|
+
_forces_rval = namedtuple("forces_rval", "F_norm dV")
|
|
151
|
+
def forces(self):
|
|
152
|
+
"""
|
|
153
|
+
Calculate the normal force and potential gradient on the path.
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
F_norm, dV : array_like
|
|
158
|
+
"""
|
|
159
|
+
X, dX, d2X = self._X, self._dX, self._d2X
|
|
160
|
+
beta = self._beta
|
|
161
|
+
# First find phi, dphi, and d2phi. Note that dphi needs to get a
|
|
162
|
+
# linear component added in, while d2phi does not.
|
|
163
|
+
phi = self.phi
|
|
164
|
+
dphi = np.sum(beta[np.newaxis,:,:]*dX[:,:,np.newaxis], axis=1) \
|
|
165
|
+
+ (self.phi[-1]-self.phi[1])[np.newaxis,:]
|
|
166
|
+
d2phi = np.sum(beta[np.newaxis,:,:]*d2X[:,:,np.newaxis], axis=1)
|
|
167
|
+
# Compute dphi/ds, where s is the path length instead of the path
|
|
168
|
+
# parameter t. This is just the direction along the path.
|
|
169
|
+
dphi_sq = np.sum(dphi*dphi, axis=-1)[:,np.newaxis]
|
|
170
|
+
dphids = dphi/np.sqrt(dphi_sq)
|
|
171
|
+
# Then find the acceleration along the path, i.e. d2phi/ds2:
|
|
172
|
+
d2phids2 = (d2phi - dphi * np.sum(dphi*d2phi, axis=-1)[:,np.newaxis] /
|
|
173
|
+
dphi_sq)/dphi_sq
|
|
174
|
+
# Now we have the path at the points t, as well as its derivatives with
|
|
175
|
+
# respect to its path length. We still need the normal force on the path.
|
|
176
|
+
dV = self.dV(phi)
|
|
177
|
+
dV_perp = dV - np.sum(dV*dphids, axis=-1)[:,np.newaxis]*dphids
|
|
178
|
+
F_norm = d2phids2 * self.v2 - dV_perp
|
|
179
|
+
if (self.fix_start):
|
|
180
|
+
F_norm[0] = 0.0
|
|
181
|
+
if (self.fix_end):
|
|
182
|
+
F_norm[-1] = 0.0
|
|
183
|
+
return self._forces_rval(F_norm, dV)
|
|
184
|
+
|
|
185
|
+
_step_rval = namedtuple("step_rval", "stepsize step_reversed fRatio")
|
|
186
|
+
def step(self, lastStep: float, maxstep: float=.1, minstep: float=1e-4,
|
|
187
|
+
reverseCheck: float=.15, stepIncrease: float=1.5,
|
|
188
|
+
stepDecrease: float=5., checkAfterFit: bool=True,
|
|
189
|
+
verbose: bool=False):
|
|
190
|
+
"""
|
|
191
|
+
Deform the path one step.
|
|
192
|
+
|
|
193
|
+
Each point is pushed in the direction of the normal force - the force
|
|
194
|
+
that the path exerts on a classical particle moving with speed `dphidr`
|
|
195
|
+
in a potential with gradient `dV` such that the particle stays on the
|
|
196
|
+
path. A stepsize of 1 corresponds to moving the path an amount
|
|
197
|
+
``L*N/(dV_max)``, where `L` is the length of the (original) path,
|
|
198
|
+
`N` is the normal force, and `dV_max` is the maximum force exerted by
|
|
199
|
+
the potential along the path.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
lastStep : float
|
|
204
|
+
Size of the last step.
|
|
205
|
+
maxstep, minstep : float, optional
|
|
206
|
+
reverseCheck : float, optional
|
|
207
|
+
Percentage of points for which the force can reverse direcitons
|
|
208
|
+
(relative to the last step) before the stepsize is decreased.
|
|
209
|
+
If ``reverseCheck >= 1``, the stepsize is kept at `lastStep`.
|
|
210
|
+
stepIncrease, stepDecrease : float, optional
|
|
211
|
+
The amount to increase or decrease stepsize over the last step.
|
|
212
|
+
Both should be bigger than 1.
|
|
213
|
+
checkAfterFit : bool, optional
|
|
214
|
+
If True, the convergence test is performed after the points are fit
|
|
215
|
+
to a spline. If False, it's done beforehand.
|
|
216
|
+
verbose : bool, optional
|
|
217
|
+
If True, output is printed at each step.
|
|
218
|
+
|
|
219
|
+
Returns
|
|
220
|
+
-------
|
|
221
|
+
stepsize : float
|
|
222
|
+
The stepsize used for this step.
|
|
223
|
+
step_reversed : bool
|
|
224
|
+
True if this step was reversed, otherwise False
|
|
225
|
+
fRatio : float
|
|
226
|
+
The ratio of the maximum normal force to the maximum potential
|
|
227
|
+
gradient. When the path is a perfect fit, this should go to zero. If
|
|
228
|
+
``checkAfterFit == True``, the normal force in this ratio is defined
|
|
229
|
+
by the change in phi this step *after* being fit to a spline. Note
|
|
230
|
+
that if the spline does a poor job of fitting the points after the
|
|
231
|
+
deformation in this step (which might be the case if there are not
|
|
232
|
+
enough basis functions), and if ``checkAfterFit == False``, this
|
|
233
|
+
ratio can be non-zero or large even if there is no change in ``phi``.
|
|
234
|
+
|
|
235
|
+
Notes
|
|
236
|
+
-----
|
|
237
|
+
In prior versions of this function (CosmoTransitions v1.0.2 and
|
|
238
|
+
earlier), the start and end points of the
|
|
239
|
+
path were effectively held fixed during the main deformation. This was
|
|
240
|
+
because the line ``phi_lin = phi[:1] + ...`` was calculated *before* the
|
|
241
|
+
line ``phi = phi+F*stepsize``. Since the spline basis functions are
|
|
242
|
+
zero at the start and end points (the spline is added on top of the
|
|
243
|
+
straight line between the end points), when the points were later taken
|
|
244
|
+
from the spline the end points wouldn't move. This was by design, since
|
|
245
|
+
for thin-walled bubbles the endpoints should stay fixed at the two
|
|
246
|
+
vacua. However, this caused problems for thick-walled bubbles where the
|
|
247
|
+
end points should move.
|
|
248
|
+
|
|
249
|
+
To get around this, prior versions added an extra block of code to move
|
|
250
|
+
the end points before the main deformation. However, this was
|
|
251
|
+
unnecessarily complicated and led to error-prone code. In this version,
|
|
252
|
+
the end points are always allowed to move if the force `F` is non-zero.
|
|
253
|
+
In the thin-walled case, the force should be almost exactly zero at
|
|
254
|
+
the end points anyways (there is zero potential gradient and `dphidr` is
|
|
255
|
+
zero), so they should stay fixed on their own.
|
|
256
|
+
"""
|
|
257
|
+
# Find out the direction of the deformation.
|
|
258
|
+
F, dV = self.forces()
|
|
259
|
+
F_max = np.max(np.sqrt(np.sum(F*F,-1)))
|
|
260
|
+
dV_max = np.max(np.sqrt(np.sum(dV*dV,-1)))
|
|
261
|
+
fRatio1 = F_max / dV_max
|
|
262
|
+
# Rescale the normal force so that it's relative to L:
|
|
263
|
+
F *= self._L / dV_max
|
|
264
|
+
|
|
265
|
+
# Now, see how big the stepsize should be
|
|
266
|
+
stepsize = lastStep
|
|
267
|
+
phi = self.phi
|
|
268
|
+
assert(maxstep > minstep)
|
|
269
|
+
step_reversed = False
|
|
270
|
+
if reverseCheck < 1 and self._F_prev is not None:
|
|
271
|
+
FdotFlast = np.sum(F*self._F_prev, axis=1)
|
|
272
|
+
if np.sum(FdotFlast < 0) > len(FdotFlast)*reverseCheck:
|
|
273
|
+
# we want to reverse the last step
|
|
274
|
+
if stepsize > minstep:
|
|
275
|
+
step_reversed = True
|
|
276
|
+
phi = self._phi_prev
|
|
277
|
+
F = self._F_prev
|
|
278
|
+
if verbose: print("step reversed")
|
|
279
|
+
stepsize = lastStep/stepDecrease
|
|
280
|
+
else:
|
|
281
|
+
# No (large number of) indices reversed, just do a regular
|
|
282
|
+
# step. Increase the stepsize a bit over the last one.
|
|
283
|
+
stepsize = lastStep * stepIncrease
|
|
284
|
+
if stepsize > maxstep: stepsize = maxstep
|
|
285
|
+
if stepsize < minstep: stepsize = minstep
|
|
286
|
+
|
|
287
|
+
# Save the state before the step
|
|
288
|
+
self._phi_prev = phi
|
|
289
|
+
self._F_prev = F
|
|
290
|
+
if self.save_all_steps:
|
|
291
|
+
self.phi_list.append(phi)
|
|
292
|
+
self.F_list.append(F)
|
|
293
|
+
|
|
294
|
+
# Now make the step. It's important to not use += so we do not mutate
|
|
295
|
+
# the value stored in self.phi_list.
|
|
296
|
+
phi = phi+F*stepsize
|
|
297
|
+
|
|
298
|
+
# fit to the spline
|
|
299
|
+
phi_lin = phi[:1] + (phi[-1:]-phi[:1])*self._t
|
|
300
|
+
phi -= phi_lin
|
|
301
|
+
self._beta, residues, rank, s = np.linalg.lstsq(self._X, phi, rcond=None)
|
|
302
|
+
phi = np.sum(self._beta[np.newaxis,:,:]*self._X[:,:,np.newaxis], axis=1)
|
|
303
|
+
phi += phi_lin
|
|
304
|
+
self.phi = phi
|
|
305
|
+
|
|
306
|
+
Ffit = (phi-self._phi_prev)/stepsize
|
|
307
|
+
fRatio2 = np.max(np.sqrt(np.sum(Ffit*Ffit,-1)))/self._L
|
|
308
|
+
|
|
309
|
+
if verbose:
|
|
310
|
+
print("step: %i; stepsize: %0.2e; fRatio1 %0.2e; fRatio2: %0.2e"
|
|
311
|
+
% (self.num_steps, stepsize, fRatio1, fRatio2))
|
|
312
|
+
|
|
313
|
+
fRatio = fRatio2 if checkAfterFit else fRatio1
|
|
314
|
+
return self._step_rval(stepsize, step_reversed, fRatio)
|
|
315
|
+
|
|
316
|
+
def deformPath(self, startstep : float=2e-3, fRatioConv: float=.02,
|
|
317
|
+
converge_0: float=5., fRatioIncrease: float=5.,
|
|
318
|
+
maxiter: int=500, verbose: bool=False, callback=None,
|
|
319
|
+
step_params={}) -> bool:
|
|
320
|
+
"""
|
|
321
|
+
Deform the path many individual steps, stopping either when the
|
|
322
|
+
convergence criterium is reached, when the maximum number of iterations
|
|
323
|
+
is reached, or when the path appears to be running away from
|
|
324
|
+
convergence.
|
|
325
|
+
|
|
326
|
+
Parameters
|
|
327
|
+
----------
|
|
328
|
+
startstep : float, optional
|
|
329
|
+
Starting stepsize used in :func:`step`.
|
|
330
|
+
fRatioConv : float, optional
|
|
331
|
+
The routine will stop when the maximum normal force on the path
|
|
332
|
+
divided by the maximum potential gradient is less than this.
|
|
333
|
+
converge_0 : float, optional
|
|
334
|
+
On the first step, use a different convergence criterion. Check if
|
|
335
|
+
``fRatio < convergence_0 * fRatioConv``.
|
|
336
|
+
fRatioIncrease :float, optional
|
|
337
|
+
The maximum fractional amount that fRatio can increase before
|
|
338
|
+
raising an error.
|
|
339
|
+
maxiter : int, optional
|
|
340
|
+
Maximum number of steps to take (ignoring reversed steps).
|
|
341
|
+
verbose : bool, optional
|
|
342
|
+
If True, print the ending condition.
|
|
343
|
+
callback : callable, optional
|
|
344
|
+
Called after each step. Should accept an instance of this class as a
|
|
345
|
+
parameter, and return False if deformation should stop.
|
|
346
|
+
step_params : dict, optional
|
|
347
|
+
Parameters to pass to :func:`step`.
|
|
348
|
+
|
|
349
|
+
Returns
|
|
350
|
+
-------
|
|
351
|
+
deformation_converged : bool
|
|
352
|
+
True if the routine stopped because it converged (as determined by
|
|
353
|
+
`fRatioConv`), False otherwise.
|
|
354
|
+
"""
|
|
355
|
+
minfRatio = np.inf
|
|
356
|
+
minfRatio_index = 0
|
|
357
|
+
minfRatio_beta = None
|
|
358
|
+
minfRatio_phi = None
|
|
359
|
+
stepsize = startstep
|
|
360
|
+
deformation_converged = False
|
|
361
|
+
while True:
|
|
362
|
+
self.num_steps += 1
|
|
363
|
+
stepsize, step_reversed, fRatio = self.step(stepsize, **step_params)
|
|
364
|
+
if callback is not None and not callback(self):
|
|
365
|
+
break
|
|
366
|
+
minfRatio = min(minfRatio, fRatio)
|
|
367
|
+
if fRatio < fRatioConv or (self.num_steps == 1
|
|
368
|
+
and fRatio < converge_0*fRatioConv):
|
|
369
|
+
if verbose:
|
|
370
|
+
print("Path deformation converged. " + "%i steps. fRatio = %0.5e" % (self.num_steps,fRatio))
|
|
371
|
+
deformation_converged = True
|
|
372
|
+
break
|
|
373
|
+
if minfRatio == fRatio:
|
|
374
|
+
minfRatio_beta = self._beta
|
|
375
|
+
minfRatio_index = self.num_steps
|
|
376
|
+
minfRatio_phi = self.phi
|
|
377
|
+
if fRatio > fRatioIncrease*minfRatio and not step_reversed:
|
|
378
|
+
self._beta = minfRatio_beta
|
|
379
|
+
self.phi = minfRatio_phi
|
|
380
|
+
self.phi_list = self.phi_list[:minfRatio_index]
|
|
381
|
+
self.F_list = self.F_list[:minfRatio_index]
|
|
382
|
+
raise errors.DeformationError()
|
|
383
|
+
if self.num_steps >= maxiter:
|
|
384
|
+
if verbose:
|
|
385
|
+
print("Maximum number of deformation iterations reached.")
|
|
386
|
+
break
|
|
387
|
+
return deformation_converged
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
class Deformation_Points(object):
|
|
391
|
+
"""
|
|
392
|
+
Deform a path in the presence of a potential such that the normal forces
|
|
393
|
+
along the path are zero.
|
|
394
|
+
|
|
395
|
+
Unlike :class:`Deformation_Spline`, this class changes the points
|
|
396
|
+
themselves rather than fitting a spline to the points. It is a more
|
|
397
|
+
straightforward implementation, and when run with comparable inputs (i.e.,
|
|
398
|
+
the number of basis splines is about the same as the number of points), this
|
|
399
|
+
method tends to be somewhat faster. The individual stepsizes here change
|
|
400
|
+
with the total number of points, whereas in the spline implementation they
|
|
401
|
+
mostly depend on the number of basis functions. However, as long as the path
|
|
402
|
+
is fairly smooth, the total number of splines in that class can probably be
|
|
403
|
+
smaller than the total number of points in this class, so this class will
|
|
404
|
+
tend to be somewhat slower.
|
|
405
|
+
|
|
406
|
+
The two implementations should converge upon the same answer when the
|
|
407
|
+
number of points and basis functions get large.
|
|
408
|
+
|
|
409
|
+
Parameters
|
|
410
|
+
----------
|
|
411
|
+
phi : array_like
|
|
412
|
+
The list of points that constitutes the original path. Should have
|
|
413
|
+
shape ``(n_points, n_dimensions)``.
|
|
414
|
+
dphidr : array_like
|
|
415
|
+
The 'speed' along the path at the initial points. This does not change
|
|
416
|
+
as the path deforms. Should have shape ``(n_points,)``. Gets saved into
|
|
417
|
+
the attribute ``self.v2`` as ``v2 = dphidr[:,np.newaxis]**2``.
|
|
418
|
+
dV : callable
|
|
419
|
+
The potential gradient as a function of phi. The output shape should be
|
|
420
|
+
the same as the input shape, which will be ``(..., n_dimensions)``.
|
|
421
|
+
fix_start, fix_end : bool, optional
|
|
422
|
+
If True, the force on the first/last point along the path is set to
|
|
423
|
+
zero, so the point will not change in the deformation step.
|
|
424
|
+
save_all_steps : bool, optional
|
|
425
|
+
If True, each step gets saved into ``self.phi_list`` and
|
|
426
|
+
``self.F_list``.
|
|
427
|
+
|
|
428
|
+
Attributes
|
|
429
|
+
----------
|
|
430
|
+
phi : array_like
|
|
431
|
+
Set during initialization, and then rewritten at each step.
|
|
432
|
+
num_steps : int
|
|
433
|
+
Total number of steps taken.
|
|
434
|
+
"""
|
|
435
|
+
def __init__(self, phi: np.ndarray, dphidr: np.ndarray,
|
|
436
|
+
dV: Callable[[np.ndarray], np.ndarray],
|
|
437
|
+
fix_start=False, fix_end=False, save_all_steps=False):
|
|
438
|
+
"""Store the points and derivative data required for
|
|
439
|
+
point-wise deformation."""
|
|
440
|
+
self.phi = np.asanyarray(phi) # shape (n,N)
|
|
441
|
+
self.v2 = np.asanyarray(dphidr)[:,np.newaxis]**2 # shape (n,1)
|
|
442
|
+
self.dV = dV
|
|
443
|
+
self.F_list = []
|
|
444
|
+
self.phi_list = []
|
|
445
|
+
self.save_all_steps = save_all_steps
|
|
446
|
+
self.fix_start, self.fix_end = fix_start, fix_end
|
|
447
|
+
self.num_steps = 0
|
|
448
|
+
|
|
449
|
+
_forces_rval = namedtuple("forces_rval", "F_norm dV")
|
|
450
|
+
def forces(self, phi : np.ndarray=None):
|
|
451
|
+
"""
|
|
452
|
+
Calculate the normal force and potential gradient on the path.
|
|
453
|
+
|
|
454
|
+
Returns
|
|
455
|
+
-------
|
|
456
|
+
F_norm, dV : array_like
|
|
457
|
+
"""
|
|
458
|
+
if phi is None: phi = self.phi
|
|
459
|
+
# Let `t` be some variable that parametrizes the points such that
|
|
460
|
+
# t_i = i. Calculate the derivs of phi w/ respect to t.
|
|
461
|
+
dphi = helper_functions.deriv14_const_dx(phi.T).T
|
|
462
|
+
d2phi = helper_functions.deriv23_const_dx(phi.T).T
|
|
463
|
+
# Let `x` be some variable that parametrizes the path such that
|
|
464
|
+
# |dphi/dx| = 1. Calculate the derivs.
|
|
465
|
+
dphi_abssq = np.sum(dphi*dphi, axis=-1)[:,np.newaxis]
|
|
466
|
+
dphi /= np.sqrt(dphi_abssq) # This is now dphi/dx
|
|
467
|
+
d2phi /= dphi_abssq # = d2phi/dx2 + (dphi/dx)(d2phi/dt2)/(dphi/dt)^2
|
|
468
|
+
d2phi -= np.sum(d2phi*dphi, axis=-1)[:,np.newaxis] * dphi # = d2phi/dx2
|
|
469
|
+
# Calculate the total force.
|
|
470
|
+
dV = self.dV(phi)
|
|
471
|
+
dV_perp = dV - np.sum(dV*dphi, axis=-1)[:,np.newaxis] * dphi
|
|
472
|
+
F_norm = d2phi*self.v2 - dV_perp
|
|
473
|
+
if (self.fix_start):
|
|
474
|
+
F_norm[0] = 0.0
|
|
475
|
+
if (self.fix_end):
|
|
476
|
+
F_norm[-1] = 0.0
|
|
477
|
+
return self._forces_rval(F_norm, dV)
|
|
478
|
+
|
|
479
|
+
_step_rval = namedtuple("step_rval", "stepsize fRatio")
|
|
480
|
+
def step(self, stepsize: float, minstep: float,
|
|
481
|
+
diff_check: float=0.1, step_decrease: float=2.):
|
|
482
|
+
"""
|
|
483
|
+
Take two half-steps in the direction of the normal force.
|
|
484
|
+
|
|
485
|
+
Parameters
|
|
486
|
+
----------
|
|
487
|
+
stepsize : float
|
|
488
|
+
Determines change in ``phi``: ``phi += F_norm*stepsize``.
|
|
489
|
+
minstep : float
|
|
490
|
+
The smallest the stepsize is allowed to be.
|
|
491
|
+
diff_check : float, optional
|
|
492
|
+
The stepsize is chosen such that difference between the forces at
|
|
493
|
+
beginning of the step and halfway through the step is small
|
|
494
|
+
compared to the force itself: ``max(F2-F1) < diff_check * max(F1)``,
|
|
495
|
+
where ``max`` here really means the maximum absolute value of the
|
|
496
|
+
force in each direction.
|
|
497
|
+
step_decrease : float, optional
|
|
498
|
+
Amount by which to decrease the stepsize if the step is too big.
|
|
499
|
+
|
|
500
|
+
Returns
|
|
501
|
+
-------
|
|
502
|
+
stepsize : float
|
|
503
|
+
The stepsize used for this step.
|
|
504
|
+
fRatio : float
|
|
505
|
+
The ratio of the maximum normal force to the maximum potential
|
|
506
|
+
gradient. When the path is a perfect fit, this should go to zero.
|
|
507
|
+
"""
|
|
508
|
+
F1, dV = self.forces()
|
|
509
|
+
F_max = np.max(np.sqrt(np.sum(F1*F1,-1)))
|
|
510
|
+
dV_max = np.max(np.sqrt(np.sum(dV*dV,-1)))
|
|
511
|
+
fRatio = F_max / dV_max
|
|
512
|
+
|
|
513
|
+
if self.save_all_steps:
|
|
514
|
+
self.phi_list.append(self.phi)
|
|
515
|
+
self.F_list.append(F1)
|
|
516
|
+
|
|
517
|
+
while True:
|
|
518
|
+
# Take one full step
|
|
519
|
+
# phi1 = self.phi + F*stepsize
|
|
520
|
+
# Take two half steps
|
|
521
|
+
phi2 = self.phi + F1*(stepsize*0.5)
|
|
522
|
+
F2 = self.forces(phi2)[0]
|
|
523
|
+
if stepsize <= minstep:
|
|
524
|
+
stepsize = minstep
|
|
525
|
+
break
|
|
526
|
+
# phi2 += F2*(stepsize*0.5)
|
|
527
|
+
DF_max = np.max(np.abs(F2-F1), axis=0)
|
|
528
|
+
F_max = np.max(np.abs(F1), axis=0)
|
|
529
|
+
if (DF_max < diff_check*F_max).all():
|
|
530
|
+
break
|
|
531
|
+
stepsize /= step_decrease
|
|
532
|
+
self.phi = phi2 + F2*(stepsize*0.5)
|
|
533
|
+
|
|
534
|
+
return self._step_rval(stepsize, fRatio)
|
|
535
|
+
|
|
536
|
+
def deformPath(self, startstep=.1, minstep=1e-6, step_increase=1.5,
|
|
537
|
+
fRatioConv=.02, converge_0=5., fRatioIncrease=20.,
|
|
538
|
+
maxiter=500, verbose=0, callback=None, step_params={}):
|
|
539
|
+
"""
|
|
540
|
+
Deform the path many individual steps, stopping either when the
|
|
541
|
+
convergence criterium is reached, when the maximum number of iterations
|
|
542
|
+
is reached, or when the path appears to be running away from
|
|
543
|
+
convergence.
|
|
544
|
+
|
|
545
|
+
Parameters
|
|
546
|
+
----------
|
|
547
|
+
startstep, maxstep : float, optional
|
|
548
|
+
Starting and maximum stepsizes used in :func:`step`, rescaled by
|
|
549
|
+
``|phi[0]-phi[1]| / (max(dV)*num_points)``.
|
|
550
|
+
fRatioConv : float, optional
|
|
551
|
+
The routine will stop when the maximum normal force on the path
|
|
552
|
+
divided by the maximum potential gradient is less than this.
|
|
553
|
+
converge_0 : float, optional
|
|
554
|
+
On the first step, use a different convergence criterion. Check if
|
|
555
|
+
``fRatio < convergence_0 * fRatioConv``.
|
|
556
|
+
fRatioIncrease :float, optional
|
|
557
|
+
The maximum fractional amount that fRatio can increase before
|
|
558
|
+
raising an error.
|
|
559
|
+
maxiter : int, optional
|
|
560
|
+
Maximum number of steps to take (ignoring reversed steps).
|
|
561
|
+
verbose : int, optional
|
|
562
|
+
If ``verbose >= 1``, print the ending condition.
|
|
563
|
+
If ``verbose >= 2``, print `fRatio` and `stepsize` at each step.
|
|
564
|
+
callback : callable, optional
|
|
565
|
+
Called after each step. Should accept an instance of this class as a
|
|
566
|
+
parameter, and return False if deformation should stop.
|
|
567
|
+
step_params : dict, optional
|
|
568
|
+
Parameters to pass to :func:`step`.
|
|
569
|
+
|
|
570
|
+
Returns
|
|
571
|
+
-------
|
|
572
|
+
deformation_converged : bool
|
|
573
|
+
True if the routine stopped because it converged (as determined by
|
|
574
|
+
`fRatioConv`), False otherwise.
|
|
575
|
+
"""
|
|
576
|
+
minfRatio = np.inf
|
|
577
|
+
Delta_phi = np.sum(np.sqrt((self.phi[0]-self.phi[-1])**2))
|
|
578
|
+
dV_max = np.max(np.sum(self.dV(self.phi)**2, axis=-1))**0.5
|
|
579
|
+
step_scale = Delta_phi / (len(self.phi) * dV_max)
|
|
580
|
+
stepsize = startstep * step_scale
|
|
581
|
+
minstep *= step_scale
|
|
582
|
+
deformation_converged = False
|
|
583
|
+
while True:
|
|
584
|
+
self.num_steps += 1
|
|
585
|
+
stepsize, fRatio = self.step(stepsize, minstep, **step_params)
|
|
586
|
+
if verbose >= 2:
|
|
587
|
+
print("step: %i; stepsize: %0.2e; fRatio: %0.2e"
|
|
588
|
+
% (self.num_steps, stepsize, fRatio))
|
|
589
|
+
stepsize *= step_increase
|
|
590
|
+
if callback is not None and not callback(self):
|
|
591
|
+
break
|
|
592
|
+
minfRatio = min(minfRatio, fRatio)
|
|
593
|
+
if fRatio < fRatioConv or (self.num_steps == 1
|
|
594
|
+
and fRatio < converge_0*fRatioConv):
|
|
595
|
+
if verbose >= 1:
|
|
596
|
+
print("Path deformation converged." +
|
|
597
|
+
"%i steps. fRatio = %0.5e" % (self.num_steps,fRatio))
|
|
598
|
+
deformation_converged = True
|
|
599
|
+
break
|
|
600
|
+
if minfRatio == fRatio:
|
|
601
|
+
minfRatio_index = self.num_steps
|
|
602
|
+
minfRatio_phi = self.phi
|
|
603
|
+
if fRatio > fRatioIncrease*minfRatio:
|
|
604
|
+
self.phi = minfRatio_phi
|
|
605
|
+
self.phi_list = self.phi_list[:minfRatio_index]
|
|
606
|
+
self.F_list = self.F_list[:minfRatio_index]
|
|
607
|
+
raise errors.DeformationError()
|
|
608
|
+
if self.num_steps >= maxiter:
|
|
609
|
+
if verbose: print("Maximum number of iterations reached.")
|
|
610
|
+
break
|
|
611
|
+
return deformation_converged
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
_extrapolatePhi_rtype = namedtuple("extrapolatePhi_rval", "phi s L")
|
|
615
|
+
def _extrapolatePhi(phi0, V=None, tails=0.2):
|
|
616
|
+
"""
|
|
617
|
+
Returns a list of points along the path, going linearly
|
|
618
|
+
beyond the path to include the nearest minima.
|
|
619
|
+
|
|
620
|
+
Parameters
|
|
621
|
+
----------
|
|
622
|
+
phi0 : array_like
|
|
623
|
+
The (multi-dimensional) path to extend.
|
|
624
|
+
V : callable or None
|
|
625
|
+
The potential to minimize, or None if the path should be extended a
|
|
626
|
+
fixed amount beyond its ends.
|
|
627
|
+
tails : float
|
|
628
|
+
The amount relative to the path length to extrapolate beyond the end of
|
|
629
|
+
the path (if V is None) or beyond the minima (if V is not None).
|
|
630
|
+
|
|
631
|
+
Returns
|
|
632
|
+
-------
|
|
633
|
+
phi : array_like
|
|
634
|
+
The extended list of points. The spacing between points in the extended
|
|
635
|
+
regions should be approximately the same as the spacing between the
|
|
636
|
+
input points.
|
|
637
|
+
s : array_like
|
|
638
|
+
The distance along the path (starting at ``phi0[0]``).
|
|
639
|
+
L : float
|
|
640
|
+
Total length of the path excluding tails.
|
|
641
|
+
"""
|
|
642
|
+
phi1 = phi = phi0
|
|
643
|
+
dphi = np.append(0,np.sum((phi1[1:]-phi1[:-1])**2,1)**.5)
|
|
644
|
+
s1 = np.cumsum(dphi)
|
|
645
|
+
L = s1[-1]
|
|
646
|
+
npoints = phi1.shape[0]
|
|
647
|
+
|
|
648
|
+
phi_hat0 = (phi[1]-phi[0])/np.sum((phi[1]-phi[0])**2)**.5
|
|
649
|
+
if V is None:
|
|
650
|
+
s0min = 0.0
|
|
651
|
+
else:
|
|
652
|
+
V0 = lambda x: V(phi[0] + phi_hat0*x*L)
|
|
653
|
+
s0min = optimize.fmin(V0, 0.0, disp=0, xtol=1e-5)[0]*L
|
|
654
|
+
if s0min > 0: s0min = 0.0
|
|
655
|
+
s0 = np.linspace(s0min - L*tails, 0.0, npoints*tails)[:-1]
|
|
656
|
+
phi0 = phi[0] + phi_hat0*s0[:,np.newaxis]
|
|
657
|
+
|
|
658
|
+
phi_hat2 = (phi[-1]-phi[-2])/np.sum((phi[-1]-phi[-2])**2)**.5
|
|
659
|
+
if V is None:
|
|
660
|
+
s2min = 0.0
|
|
661
|
+
else:
|
|
662
|
+
V2 = lambda x: V(phi[-1] + phi_hat2*(x-1)*L)
|
|
663
|
+
s2min = optimize.fmin(V2, 1, disp=0, xtol=1e-5)[0]*L
|
|
664
|
+
if s2min < L: s2min = L
|
|
665
|
+
s2 = np.linspace(L, s2min + L*tails, npoints*tails)[1:]
|
|
666
|
+
phi2 = phi[-1] + phi_hat2*(s2[:,np.newaxis]-L)
|
|
667
|
+
|
|
668
|
+
phi = np.append(phi0, np.append(phi1, phi2, 0), 0)
|
|
669
|
+
s = np.append(s0, np.append(s1, s2))
|
|
670
|
+
|
|
671
|
+
return _extrapolatePhi_rtype(phi, s, L)
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def _pathDeriv(phi):
|
|
675
|
+
"""Calculates to 4th order if len(phi) >= 5, otherwise 1st/2nd order."""
|
|
676
|
+
if len(phi) >= 5:
|
|
677
|
+
dphi = helper_functions.deriv14_const_dx(phi.T).T
|
|
678
|
+
elif len(phi) > 2:
|
|
679
|
+
dphi = np.empty_like(phi)
|
|
680
|
+
dphi[1:-1] = 0.5*(phi[2:] - phi[:-2])
|
|
681
|
+
dphi[0] = -1.5*phi[0] + 2*phi[1] - 0.5*phi[2]
|
|
682
|
+
dphi[-1] = +1.5*phi[-1] - 2*phi[-2] + 0.5*phi[-3]
|
|
683
|
+
else:
|
|
684
|
+
dphi = np.empty_like(phi)
|
|
685
|
+
dphi[:] = phi[1]-phi[0]
|
|
686
|
+
return dphi
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
class SplinePath(object):
|
|
690
|
+
"""
|
|
691
|
+
Fit a spline to a path in field space, and find the potential on that path.
|
|
692
|
+
|
|
693
|
+
The spline-fitting happens in several steps:
|
|
694
|
+
|
|
695
|
+
1. The derivatives of the input points are found, and used to
|
|
696
|
+
determine the path length and direction at each point.
|
|
697
|
+
2. If `extend_to_minima` is True, additional points are added at each end
|
|
698
|
+
of the path such that ends lie on local minima.
|
|
699
|
+
3. The points are fit to a spline, with the knots given by the path
|
|
700
|
+
distances from the first point.
|
|
701
|
+
4. If `reeval_distances` is True, the distances to each point are
|
|
702
|
+
re-evaluated using the spline. A new spline is fit with more accurate
|
|
703
|
+
knots.
|
|
704
|
+
|
|
705
|
+
The potential as a function of distance can be defined in one of two ways.
|
|
706
|
+
If `V_spline_samples` is None, the potential as a function of distance `x`
|
|
707
|
+
along the path is given by `V[pts(x)]`, where `pts(x)` is the spline
|
|
708
|
+
function that defines the path. If `V_spline_samples` is not None, the
|
|
709
|
+
potential is first evaluated `V_spline_samples` times along the path, and
|
|
710
|
+
another spline is fit to the output. In other words, when `V_spline_samples`
|
|
711
|
+
is None, the input potential `V` is evaluated for every value `x` passed to
|
|
712
|
+
to the class method :meth:`V`, whereas if `V_spline_samples` is not None,
|
|
713
|
+
the input potential is only evaluated during initialization.
|
|
714
|
+
|
|
715
|
+
Parameters
|
|
716
|
+
----------
|
|
717
|
+
pts : array_like
|
|
718
|
+
The points that describe the path, with shape ``(num_points, N_dim)``.
|
|
719
|
+
V : callable
|
|
720
|
+
The potential function. Input arrays will be shape ``(npts, N_dim)`` and
|
|
721
|
+
output should have shape ``(npts,)``. Can be None.
|
|
722
|
+
dV : callable, optional.
|
|
723
|
+
The gradient of the potential. Input arrays will be shape
|
|
724
|
+
``(npts, N_dim)`` and output should have shape ``(npts, N_dim)``. Only
|
|
725
|
+
used if ``V_spline_samples=None``.
|
|
726
|
+
V_spline_samples : int or None, optional
|
|
727
|
+
Number of samples to take along the path to create the spline
|
|
728
|
+
interpolation functions. If None, the potential is evaluated directly
|
|
729
|
+
from `V` given in the input. If not None, `V_spline_samples` should be
|
|
730
|
+
large enough to resolve the smallest features in the potential. For
|
|
731
|
+
example, the potential may have a very narrow potential barrier over
|
|
732
|
+
which multiple samples should be taken.
|
|
733
|
+
extend_to_minima : bool, optional
|
|
734
|
+
If True, the input path is extended at each end until it hits local
|
|
735
|
+
minima.
|
|
736
|
+
reeval_distances : bool, optional
|
|
737
|
+
If True, get more accurate distances to each knot by integrating along
|
|
738
|
+
the spline.
|
|
739
|
+
|
|
740
|
+
Attributes
|
|
741
|
+
----------
|
|
742
|
+
L : float
|
|
743
|
+
The total length of the path.
|
|
744
|
+
"""
|
|
745
|
+
def __init__(self, pts: np.ndarray, V: Callable[[np.ndarray], np.ndarray],
|
|
746
|
+
dV: Callable[[np.ndarray], np.ndarray]=None, V_spline_samples: int=100,
|
|
747
|
+
extend_to_minima: bool=False, reeval_distances: bool=True,
|
|
748
|
+
verbose: bool=False):
|
|
749
|
+
"""Construct a spline representation of the tunnelling path."""
|
|
750
|
+
assert len(pts) > 1
|
|
751
|
+
# 1. Find derivs
|
|
752
|
+
dpts = _pathDeriv(pts)
|
|
753
|
+
# 2. Extend the path
|
|
754
|
+
if extend_to_minima:
|
|
755
|
+
def V_lin(x, p0, dp0, V): return V(p0+x*dp0)
|
|
756
|
+
# extend at the front of the path
|
|
757
|
+
xmin = optimize.fmin(V_lin, 0.0, args=(pts[0], dpts[0], V),
|
|
758
|
+
xtol=1e-6, disp=0)[0] # Setting xtol to 1e-12 was okay, too
|
|
759
|
+
if xmin > 0.0: xmin = 0.0
|
|
760
|
+
nx = np.ceil(abs(xmin)-.5) + 1
|
|
761
|
+
if nx > 20000:
|
|
762
|
+
raise errors.SplineError(
|
|
763
|
+
"Maximum allowed size exceeded while extending the tunnelling path towards the minimum."
|
|
764
|
+
)
|
|
765
|
+
if nx > 100_000 and verbose:
|
|
766
|
+
msg = "WARNING: The tunneling path appears to be very long: Tried to extend it by %i points" % nx
|
|
767
|
+
msg += ", which is more than 100_000. This may lead to numerical issues."
|
|
768
|
+
msg += "\nThe point where this happens is at %s, with derivative = %s." % (pts[0], dpts[0])
|
|
769
|
+
print(msg)
|
|
770
|
+
x = np.linspace(xmin, 0, int(nx))[:, np.newaxis]
|
|
771
|
+
pt_ext = pts[0] + x*dpts[0]
|
|
772
|
+
|
|
773
|
+
# Sometimes there are unnecessary dimensions in the input. The
|
|
774
|
+
# combination of atleast_2d and squeeze removes those and avoids
|
|
775
|
+
# issues with the concatenation.
|
|
776
|
+
pt_ext = np.atleast_2d(np.squeeze(pt_ext))
|
|
777
|
+
pts_rest = np.atleast_2d(np.squeeze(pts[1:]))
|
|
778
|
+
pts = np.concatenate([pt_ext, pts_rest], axis=0)
|
|
779
|
+
|
|
780
|
+
# extend at the end of the path
|
|
781
|
+
xmin = optimize.fmin(V_lin, 0.0, args=(pts[-1], dpts[-1], V),
|
|
782
|
+
xtol=1e-6, disp=0)[0] # Setting xtol to 1e-12 was okay, too
|
|
783
|
+
if xmin < 0.0: xmin = 0.0
|
|
784
|
+
nx = np.ceil(abs(xmin)-.5) + 1
|
|
785
|
+
if nx > 20000:
|
|
786
|
+
raise errors.SplineError(
|
|
787
|
+
"Maximum allowed size exceeded while extending the tunnelling path towards the second minimum."
|
|
788
|
+
)
|
|
789
|
+
if nx > 100_000 and verbose:
|
|
790
|
+
msg = "WARNING: The tunneling path appears to be very long: Tried to extend it by %i points" % nx
|
|
791
|
+
msg += ", which is more than 100_000. This may lead to numerical issues."
|
|
792
|
+
msg += "\nThe point where this happens is at %s, with derivative = %s." % (pts[-1], dpts[-1])
|
|
793
|
+
print(msg)
|
|
794
|
+
x = np.linspace(xmin, 0, int(nx))[::-1, np.newaxis]
|
|
795
|
+
pt_ext = pts[-1] + x*dpts[-1]
|
|
796
|
+
pts = np.append(pts[:-1], pt_ext, axis=0)
|
|
797
|
+
# Recalculate the derivative
|
|
798
|
+
dpts = _pathDeriv(pts)
|
|
799
|
+
# 3. Find knot positions and fit the spline.
|
|
800
|
+
pdist = integrate.cumulative_trapezoid(np.sqrt(np.sum(dpts*dpts, axis=1)),
|
|
801
|
+
initial=0.0)
|
|
802
|
+
self.L = pdist[-1]
|
|
803
|
+
k = min(len(pts)-1, 3) # degree of the spline
|
|
804
|
+
self._path_tck = interpolate.splprep(pts.T, u=pdist, s=0, k=k)[0]
|
|
805
|
+
# 4. Re-evaluate the distance to each point.
|
|
806
|
+
if reeval_distances:
|
|
807
|
+
def dpdx(_, x):
|
|
808
|
+
dp = np.array(interpolate.splev(x, self._path_tck, der=1))
|
|
809
|
+
return np.sqrt(np.sum(dp*dp))
|
|
810
|
+
pdist = integrate.odeint(dpdx, 0., pdist,
|
|
811
|
+
rtol=0, atol=pdist[-1]*1e-8)[:,0]
|
|
812
|
+
self.L = pdist[-1]
|
|
813
|
+
self._path_tck = interpolate.splprep(pts.T, u=pdist, s=0, k=k)[0]
|
|
814
|
+
# Now make the potential spline.
|
|
815
|
+
self._V = V
|
|
816
|
+
self._dV = dV
|
|
817
|
+
self._V_tck = None
|
|
818
|
+
if V_spline_samples is not None:
|
|
819
|
+
x = np.linspace(0,self.L,V_spline_samples)
|
|
820
|
+
# extend 20% beyond this so that we more accurately model the
|
|
821
|
+
# path end points
|
|
822
|
+
x_ext = np.arange(x[1], self.L*.2, x[1])
|
|
823
|
+
x = np.append(-x_ext[::-1], x)
|
|
824
|
+
x = np.append(x, self.L+x_ext)
|
|
825
|
+
y = self.V(x)
|
|
826
|
+
self._V_tck = interpolate.splrep(x,y,s=0)
|
|
827
|
+
|
|
828
|
+
def V(self, x):
|
|
829
|
+
"""The potential as a function of the distance `x` along the path."""
|
|
830
|
+
if self._V_tck is not None:
|
|
831
|
+
return interpolate.splev(x, self._V_tck, der=0)
|
|
832
|
+
else:
|
|
833
|
+
pts = interpolate.splev(x, self._path_tck)
|
|
834
|
+
return self._V(np.array(pts).T)
|
|
835
|
+
|
|
836
|
+
def dV(self, x):
|
|
837
|
+
"""`dV/dx` as a function of the distance `x` along the path."""
|
|
838
|
+
if self._V_tck is not None:
|
|
839
|
+
return interpolate.splev(x, self._V_tck, der=1)
|
|
840
|
+
else:
|
|
841
|
+
pts = interpolate.splev(x, self._path_tck)
|
|
842
|
+
dpdx = interpolate.splev(x, self._path_tck, der=1)
|
|
843
|
+
dV = self._dV(np.array(pts).T)
|
|
844
|
+
return np.sum(dV.T*dpdx, axis=0)
|
|
845
|
+
|
|
846
|
+
def d2V(self, x):
|
|
847
|
+
"""`d^2V/dx^2` as a function of the distance `x` along the path."""
|
|
848
|
+
if self._V_tck is not None:
|
|
849
|
+
return interpolate.splev(x, self._V_tck, der=2)
|
|
850
|
+
else:
|
|
851
|
+
raise RuntimeError("No spline specified. Cannot calculate d2V.")
|
|
852
|
+
|
|
853
|
+
def pts(self, x):
|
|
854
|
+
"""
|
|
855
|
+
Returns the path points as a function of the distance `x` along the
|
|
856
|
+
path. Return value is an array with shape ``(len(x), N_dim)``.
|
|
857
|
+
"""
|
|
858
|
+
pts = interpolate.splev(x, self._path_tck)
|
|
859
|
+
return np.array(pts).T
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
@dataclass
|
|
863
|
+
class TunnelingSettings:
|
|
864
|
+
"""Configuration bundle passed around the full tunnelling workflow."""
|
|
865
|
+
|
|
866
|
+
V_spline_samples: int | None
|
|
867
|
+
tunneling_class: type
|
|
868
|
+
tunneling_init_params: dict
|
|
869
|
+
tunneling_findProfile_params: dict
|
|
870
|
+
deformation_class: type
|
|
871
|
+
deformation_init_params: dict
|
|
872
|
+
deformation_deform_params: dict
|
|
873
|
+
save_all_steps: bool
|
|
874
|
+
verbose: bool
|
|
875
|
+
callback: Callable | None
|
|
876
|
+
callback_data: object
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
@dataclass
|
|
880
|
+
class TunnelingOutputs:
|
|
881
|
+
"""Intermediate data collected during a single deformation iteration."""
|
|
882
|
+
|
|
883
|
+
path: SplinePath
|
|
884
|
+
tobj: object
|
|
885
|
+
profile1D: object
|
|
886
|
+
phi: np.ndarray
|
|
887
|
+
dphi: np.ndarray
|
|
888
|
+
pts: np.ndarray
|
|
889
|
+
deform_obj: Deformation_Spline
|
|
890
|
+
converged: bool
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
def _build_tunneling_settings(
|
|
894
|
+
V_spline_samples,
|
|
895
|
+
tunneling_class,
|
|
896
|
+
tunneling_init_params,
|
|
897
|
+
tunneling_findProfile_params,
|
|
898
|
+
deformation_class,
|
|
899
|
+
deformation_init_params,
|
|
900
|
+
deformation_deform_params,
|
|
901
|
+
save_all_steps: bool,
|
|
902
|
+
verbose: bool,
|
|
903
|
+
callback,
|
|
904
|
+
callback_data,
|
|
905
|
+
) -> TunnelingSettings:
|
|
906
|
+
"""Collect parameters and defaults used throughout the tunnelling routine."""
|
|
907
|
+
return TunnelingSettings(
|
|
908
|
+
V_spline_samples=V_spline_samples,
|
|
909
|
+
tunneling_class=tunneling_class,
|
|
910
|
+
tunneling_init_params=dict(tunneling_init_params or {}),
|
|
911
|
+
tunneling_findProfile_params=dict(tunneling_findProfile_params or {}),
|
|
912
|
+
deformation_class=deformation_class,
|
|
913
|
+
deformation_init_params=dict(deformation_init_params or {}),
|
|
914
|
+
deformation_deform_params=dict(deformation_deform_params or {}),
|
|
915
|
+
save_all_steps=save_all_steps,
|
|
916
|
+
verbose=verbose,
|
|
917
|
+
callback=callback,
|
|
918
|
+
callback_data=callback_data,
|
|
919
|
+
)
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
def _fit_spline_path(pts, settings: TunnelingSettings, V, dV, verbose):
|
|
923
|
+
"""Fit a spline to the current path guess; translate common errors."""
|
|
924
|
+
try:
|
|
925
|
+
return SplinePath(
|
|
926
|
+
pts,
|
|
927
|
+
V,
|
|
928
|
+
dV,
|
|
929
|
+
V_spline_samples=settings.V_spline_samples,
|
|
930
|
+
extend_to_minima=True,
|
|
931
|
+
verbose=verbose,
|
|
932
|
+
)
|
|
933
|
+
except ValueError as err:
|
|
934
|
+
if np.isnan(pts).any():
|
|
935
|
+
if verbose:
|
|
936
|
+
print("NaN in the path points. Returning InfiniteActionError.")
|
|
937
|
+
raise errors.InfiniteActionError from err
|
|
938
|
+
if verbose:
|
|
939
|
+
print(err.args[0])
|
|
940
|
+
raise errors.SplineError(err.args[0])
|
|
941
|
+
except Exception as err:
|
|
942
|
+
msg = err.args[0]
|
|
943
|
+
if msg == "Maximum allowed size exceeded":
|
|
944
|
+
msg = (
|
|
945
|
+
"Failed to fit a spline to the tunneling path. Tunneling "
|
|
946
|
+
"computation failed because the number of points to "
|
|
947
|
+
"construct a spline along the tunneling path is too large. "
|
|
948
|
+
"This error usually happens when the points between the "
|
|
949
|
+
"tunneling should appear don't match the points in the "
|
|
950
|
+
"potential. Make sure that phase.valAt(T) is only called "
|
|
951
|
+
"in the allowed range of temperatures."
|
|
952
|
+
)
|
|
953
|
+
if msg == "Invalid inputs.":
|
|
954
|
+
msg = (
|
|
955
|
+
"Invalid inputs to SplinePath. It might be that these are "
|
|
956
|
+
"close to each other because phitol in the tunneling "
|
|
957
|
+
"parameters was not set small enough: pts = %s"
|
|
958
|
+
) % (pts,)
|
|
959
|
+
if np.isclose(pts[0], pts[-1]).all():
|
|
960
|
+
msg += (
|
|
961
|
+
" The first and last points are identical. Return a "
|
|
962
|
+
"PotentialError with error_type 'no barrier'."
|
|
963
|
+
)
|
|
964
|
+
if verbose:
|
|
965
|
+
print(msg)
|
|
966
|
+
raise errors.PotentialError(msg, "no barrier") from err
|
|
967
|
+
if verbose:
|
|
968
|
+
print(msg)
|
|
969
|
+
raise errors.SplineError(msg) from err
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
def _create_tunneling_object(path: SplinePath, settings: TunnelingSettings):
|
|
973
|
+
"""Instantiate the 1D tunnelling solver for the current path."""
|
|
974
|
+
if settings.V_spline_samples is not None:
|
|
975
|
+
return settings.tunneling_class(
|
|
976
|
+
0.0,
|
|
977
|
+
path.L,
|
|
978
|
+
path.V,
|
|
979
|
+
path.dV,
|
|
980
|
+
path.d2V,
|
|
981
|
+
**settings.tunneling_init_params,
|
|
982
|
+
)
|
|
983
|
+
return settings.tunneling_class(
|
|
984
|
+
0.0,
|
|
985
|
+
path.L,
|
|
986
|
+
path.V,
|
|
987
|
+
path.dV,
|
|
988
|
+
None,
|
|
989
|
+
**settings.tunneling_init_params,
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
def _find_profile(tobj, settings: TunnelingSettings):
|
|
994
|
+
"""Run the 1D tunnelling solver and normalise its exceptions."""
|
|
995
|
+
try:
|
|
996
|
+
return tobj.findProfile(**settings.tunneling_findProfile_params)
|
|
997
|
+
except errors.TooSmalldVError as err:
|
|
998
|
+
raise errors.InfiniteActionError from err
|
|
999
|
+
except errors.TunnelingError as err:
|
|
1000
|
+
if settings.verbose:
|
|
1001
|
+
print(err.args[0])
|
|
1002
|
+
raise errors.InfiniteActionError from err
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
def _prepare_profile(tobj, profile1D):
|
|
1006
|
+
"""Re-sample the instanton profile on an evenly spaced grid."""
|
|
1007
|
+
phi, dphi = profile1D.Phi, profile1D.dPhi
|
|
1008
|
+
phi, dphi = tobj.evenlySpacedPhi(phi, dphi, npoints=len(phi), fixAbs=False)
|
|
1009
|
+
dphi[0] = dphi[-1] = 0.0
|
|
1010
|
+
return phi, dphi
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
def _deform_path(
|
|
1014
|
+
path: SplinePath,
|
|
1015
|
+
tobj,
|
|
1016
|
+
profile1D,
|
|
1017
|
+
phi,
|
|
1018
|
+
dphi,
|
|
1019
|
+
dV,
|
|
1020
|
+
pts,
|
|
1021
|
+
settings: TunnelingSettings,
|
|
1022
|
+
saved_steps,
|
|
1023
|
+
) -> TunnelingOutputs:
|
|
1024
|
+
"""Apply the transverse deformation step and track convergence state."""
|
|
1025
|
+
deform_obj = settings.deformation_class(pts, dphi, dV, **settings.deformation_init_params)
|
|
1026
|
+
if settings.callback and not settings.callback(path, tobj, profile1D, settings.callback_data):
|
|
1027
|
+
if not hasattr(deform_obj, "num_steps"):
|
|
1028
|
+
deform_obj.num_steps = 0
|
|
1029
|
+
return TunnelingOutputs(path, tobj, profile1D, phi, dphi, pts, deform_obj, True)
|
|
1030
|
+
try:
|
|
1031
|
+
converged = deform_obj.deformPath(**settings.deformation_deform_params)
|
|
1032
|
+
except errors.DeformationError as err:
|
|
1033
|
+
if settings.verbose:
|
|
1034
|
+
print(err.args[0])
|
|
1035
|
+
converged = False
|
|
1036
|
+
pts = deform_obj.phi
|
|
1037
|
+
if settings.save_all_steps:
|
|
1038
|
+
saved_steps.append(deform_obj.phi_list)
|
|
1039
|
+
return TunnelingOutputs(path, tobj, profile1D, phi, dphi, pts, deform_obj, converged)
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
def _should_stop(iter_output: TunnelingOutputs) -> bool:
|
|
1043
|
+
"""Check if the deformation converged after a single, small adjustment."""
|
|
1044
|
+
return iter_output.converged and iter_output.deform_obj.num_steps < 2
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
def _compute_force_ratio(pts, dphi, dV, settings: TunnelingSettings) -> float:
|
|
1048
|
+
"""Compute the ratio of transverse forces to the gradient along the path."""
|
|
1049
|
+
deform_obj = settings.deformation_class(pts, dphi, dV, **settings.deformation_init_params)
|
|
1050
|
+
F, dV_vec = deform_obj.forces()
|
|
1051
|
+
F_max = np.max(np.sqrt(np.sum(F * F, -1)))
|
|
1052
|
+
dV_max = np.max(np.sqrt(np.sum(dV_vec * dV_vec, -1)))
|
|
1053
|
+
return F_max / dV_max
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
def fullTunneling(
|
|
1057
|
+
path_pts,
|
|
1058
|
+
V,
|
|
1059
|
+
dV,
|
|
1060
|
+
maxiter=20,
|
|
1061
|
+
save_all_steps=False,
|
|
1062
|
+
verbose=False,
|
|
1063
|
+
callback=None,
|
|
1064
|
+
callback_data=None,
|
|
1065
|
+
V_spline_samples=100,
|
|
1066
|
+
tunneling_class=tunneling1D.SingleFieldInstanton,
|
|
1067
|
+
tunneling_init_params=None,
|
|
1068
|
+
tunneling_findProfile_params=None,
|
|
1069
|
+
deformation_class=Deformation_Spline,
|
|
1070
|
+
deformation_init_params=None,
|
|
1071
|
+
deformation_deform_params=None,
|
|
1072
|
+
):
|
|
1073
|
+
"""
|
|
1074
|
+
Calculate the instanton solution in multiple field dimensions.
|
|
1075
|
+
|
|
1076
|
+
This function works by looping four steps:
|
|
1077
|
+
|
|
1078
|
+
1. Fit a spline to the path given by `path_pts`.
|
|
1079
|
+
2. Calculate the one-dimensional tunneling along this path.
|
|
1080
|
+
3. Deform the path to satisfy the transverse equations of motion.
|
|
1081
|
+
4. Check for convergence, and then go back to step 1.
|
|
1082
|
+
|
|
1083
|
+
Parameters
|
|
1084
|
+
----------
|
|
1085
|
+
path_pts : array_like
|
|
1086
|
+
An array of points that constitute the initial guess for the tunneling
|
|
1087
|
+
path, with shape ``(num_points, N_dim)``. The first point should be at
|
|
1088
|
+
(or near) the lower minimum (the minimum to which the field is
|
|
1089
|
+
tunneling), and the last point should be at the metastable minimum.
|
|
1090
|
+
V, dV : callable
|
|
1091
|
+
The potential function and its gradient. Both should accept input of
|
|
1092
|
+
shape ``(num_points, N_dim)`` or ``(N_dim,)``.
|
|
1093
|
+
maxiter : int, optional
|
|
1094
|
+
Maximum number of allowed deformation / tunneling iterations.
|
|
1095
|
+
save_all_steps : bool, optional
|
|
1096
|
+
If True, additionally output every single deformation sub-step.
|
|
1097
|
+
verbose : bool, optional
|
|
1098
|
+
If True, print a message at the start of each step.
|
|
1099
|
+
callback : callable, optional
|
|
1100
|
+
User supplied function that is evaluated just prior to deforming the
|
|
1101
|
+
path. Should return True if the path should be deformed, and False if
|
|
1102
|
+
the deformation should be aborted. Should accept 4 arguments: a
|
|
1103
|
+
:class:`SplinePath` instance which describes the tunneling path, a
|
|
1104
|
+
tunneling object (instance of ``tunneling_class``), the profile found
|
|
1105
|
+
by the tunneling object, and extra callback data.
|
|
1106
|
+
callback_data : any type, optional
|
|
1107
|
+
Extra data to pass to the callback function if there is one.
|
|
1108
|
+
|
|
1109
|
+
Other Parameters
|
|
1110
|
+
----------------
|
|
1111
|
+
V_spline_samples : int, optional
|
|
1112
|
+
Passed to :class:`SplinePath`. If None, no second derivative will be
|
|
1113
|
+
passed to the tunneling class, and it will instead be evaluated using
|
|
1114
|
+
finite differences.
|
|
1115
|
+
tunneling_class : class, optional
|
|
1116
|
+
Either :class:`tunneling1D.SingleFieldInstanton` or a subclass.
|
|
1117
|
+
tunneling_init_params : dict, optional
|
|
1118
|
+
Extra parameters to pass to the tunneling class.
|
|
1119
|
+
tunneling_findProfile_params : dict, optional
|
|
1120
|
+
Extra parameters to pass to ``tunneling_class.findProfile()``.
|
|
1121
|
+
deformation_class : class, optional
|
|
1122
|
+
Either :class:`Deformation_Spline` or :class:`Deformation_Points`, or
|
|
1123
|
+
some other object that exposes the same interface.
|
|
1124
|
+
deformation_init_params : dict, optional
|
|
1125
|
+
Extra parameters to pass to the deformation class.
|
|
1126
|
+
deformation_deform_params : dict, optional
|
|
1127
|
+
Extra parameters to pass to ``deformation_class.deformPath()``.
|
|
1128
|
+
|
|
1129
|
+
Returns
|
|
1130
|
+
-------
|
|
1131
|
+
profile1D : namedtuple
|
|
1132
|
+
The return value from ``tunneling_class.findProfile()``.
|
|
1133
|
+
Phi : array_like
|
|
1134
|
+
The points that constitute the final deformed path. They are in
|
|
1135
|
+
one-to-one correspondence with the points in `profile1D`.
|
|
1136
|
+
action : float
|
|
1137
|
+
The Euclidean action of the instanton.
|
|
1138
|
+
fRatio : float
|
|
1139
|
+
A ratio of the largest transverse force on the final path relative to
|
|
1140
|
+
the largest potential gradient. This would be zero if the solution were
|
|
1141
|
+
perfect.
|
|
1142
|
+
saved_steps : list
|
|
1143
|
+
A list of lists, with each sub-list containing the saved steps for each
|
|
1144
|
+
deformation. Only written to if `save_all_steps` is True.
|
|
1145
|
+
"""
|
|
1146
|
+
settings = _build_tunneling_settings(
|
|
1147
|
+
V_spline_samples,
|
|
1148
|
+
tunneling_class,
|
|
1149
|
+
tunneling_init_params,
|
|
1150
|
+
tunneling_findProfile_params,
|
|
1151
|
+
deformation_class,
|
|
1152
|
+
deformation_init_params,
|
|
1153
|
+
deformation_deform_params,
|
|
1154
|
+
save_all_steps,
|
|
1155
|
+
verbose,
|
|
1156
|
+
callback,
|
|
1157
|
+
callback_data,
|
|
1158
|
+
)
|
|
1159
|
+
settings.deformation_init_params.setdefault("save_all_steps", save_all_steps)
|
|
1160
|
+
if settings.save_all_steps:
|
|
1161
|
+
settings.deformation_init_params["save_all_steps"] = True
|
|
1162
|
+
|
|
1163
|
+
pts = np.asanyarray(path_pts)
|
|
1164
|
+
saved_steps = []
|
|
1165
|
+
last_output: TunnelingOutputs | None = None
|
|
1166
|
+
|
|
1167
|
+
for _ in range(1, maxiter + 1):
|
|
1168
|
+
# 1. Fit the spline to the path.
|
|
1169
|
+
path = _fit_spline_path(pts, settings, V, dV, verbose)
|
|
1170
|
+
# 2. Instantiate the 1D tunnelling solver and find the profile.
|
|
1171
|
+
tobj = _create_tunneling_object(path, settings)
|
|
1172
|
+
profile1D = _find_profile(tobj, settings)
|
|
1173
|
+
phi, dphi = _prepare_profile(tobj, profile1D)
|
|
1174
|
+
# 3. Deform the path to satisfy the transverse equations of motion.
|
|
1175
|
+
path_points = path.pts(phi)
|
|
1176
|
+
iter_output = _deform_path(
|
|
1177
|
+
path,
|
|
1178
|
+
tobj,
|
|
1179
|
+
profile1D,
|
|
1180
|
+
phi,
|
|
1181
|
+
dphi,
|
|
1182
|
+
dV,
|
|
1183
|
+
path_points,
|
|
1184
|
+
settings,
|
|
1185
|
+
saved_steps,
|
|
1186
|
+
)
|
|
1187
|
+
pts = iter_output.pts
|
|
1188
|
+
last_output = iter_output
|
|
1189
|
+
# 4. Check whether the deformation has converged.
|
|
1190
|
+
if _should_stop(iter_output):
|
|
1191
|
+
break
|
|
1192
|
+
else:
|
|
1193
|
+
if settings.verbose:
|
|
1194
|
+
print("Reached maxiter in fullTunneling. No convergence.")
|
|
1195
|
+
|
|
1196
|
+
if last_output is None:
|
|
1197
|
+
raise errors.InfiniteActionError
|
|
1198
|
+
|
|
1199
|
+
f_ratio = _compute_force_ratio(last_output.pts, last_output.dphi, dV, settings)
|
|
1200
|
+
rtuple = namedtuple(
|
|
1201
|
+
"fullTunneling_rval", "profile1D Phi action fRatio saved_steps"
|
|
1202
|
+
)
|
|
1203
|
+
Phi = last_output.path.pts(last_output.profile1D.Phi)
|
|
1204
|
+
action = last_output.tobj.findAction(last_output.profile1D)
|
|
1205
|
+
return rtuple(last_output.profile1D, Phi, action, f_ratio, saved_steps)
|
|
1206
|
+
|
|
1207
|
+
|
|
1208
|
+
def _validate_tunneling_path(x0: np.ndarray, x1: np.ndarray, V_) -> None:
|
|
1209
|
+
"""Sanity-check that the path spans distinct minima and tunnelling is possible."""
|
|
1210
|
+
if np.isclose(x1, x0, rtol=1e-5, atol=1e-8).all():
|
|
1211
|
+
raise errors.PotentialError(
|
|
1212
|
+
"It was tried to calculate tunneling between two identical minima.",
|
|
1213
|
+
"no barrier",
|
|
1214
|
+
)
|
|
1215
|
+
if V_(x0) <= V_(x1):
|
|
1216
|
+
raise errors.PotentialError(
|
|
1217
|
+
"V(phi_metaMin) <= V(phi_absMin); tunneling cannot occur.",
|
|
1218
|
+
"stable, not metastable",
|
|
1219
|
+
)
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
def _execute_full_tunneling(
|
|
1223
|
+
T,
|
|
1224
|
+
V_,
|
|
1225
|
+
dV_,
|
|
1226
|
+
tdict,
|
|
1227
|
+
settings,
|
|
1228
|
+
verbose,
|
|
1229
|
+
conversionFactor,
|
|
1230
|
+
):
|
|
1231
|
+
"""Try to compute the instanton and translate known failure modes."""
|
|
1232
|
+
try:
|
|
1233
|
+
result = fullTunneling([tdict['low_vev'], tdict['high_vev']], V_, dV_, callback_data=T, verbose=verbose, **settings)
|
|
1234
|
+
return {
|
|
1235
|
+
'instanton': result,
|
|
1236
|
+
'action': result.action,
|
|
1237
|
+
'trantype': 1,
|
|
1238
|
+
}, False
|
|
1239
|
+
except errors.PotentialError as err:
|
|
1240
|
+
if err.error_type == "no barrier":
|
|
1241
|
+
if verbose:
|
|
1242
|
+
print(
|
|
1243
|
+
"Tunneling at T = ",
|
|
1244
|
+
T * conversionFactor,
|
|
1245
|
+
"GeV failed because there is no potential barrier.",
|
|
1246
|
+
)
|
|
1247
|
+
return {'action': 0, 'trantype': 2, 'instanton': None}, False
|
|
1248
|
+
if err.error_type == "stable, not metastable":
|
|
1249
|
+
if verbose:
|
|
1250
|
+
print(
|
|
1251
|
+
"Tunneling at T = ",
|
|
1252
|
+
T * conversionFactor,
|
|
1253
|
+
"GeV failed because the potential is stable and not metastable.",
|
|
1254
|
+
)
|
|
1255
|
+
return {'action': np.inf, 'trantype': 0, 'instanton': None}, False
|
|
1256
|
+
if verbose:
|
|
1257
|
+
print("Unexpected error message: ", err.args[1])
|
|
1258
|
+
raise errors.UnexpectedError(
|
|
1259
|
+
"In _tunnelFromPhaseAtT: Unexpected error message: %s" % err.args[1]
|
|
1260
|
+
) from err
|
|
1261
|
+
except errors.ZeroActionError:
|
|
1262
|
+
if verbose:
|
|
1263
|
+
print(
|
|
1264
|
+
"Tunneling at T =",
|
|
1265
|
+
T * conversionFactor,
|
|
1266
|
+
"GeV failed because the potential barrer is too small. Returning zero action, which corresponds to a positive infinity nucleation criterion.",
|
|
1267
|
+
)
|
|
1268
|
+
return {'action': 0, 'trantype': 2, 'instanton': None}, False
|
|
1269
|
+
except errors.InfiniteActionError:
|
|
1270
|
+
if verbose:
|
|
1271
|
+
print(
|
|
1272
|
+
"Tunneling at T =",
|
|
1273
|
+
T * conversionFactor,
|
|
1274
|
+
"GeV failed because the potential barrier is too high. Returning infinite action, which corresponds to a negative infinity nucleation criterion.",
|
|
1275
|
+
)
|
|
1276
|
+
return {'action': np.inf, 'trantype': 0, 'instanton': None}, False
|
|
1277
|
+
except errors.SplineError as err:
|
|
1278
|
+
if verbose:
|
|
1279
|
+
print(
|
|
1280
|
+
"Tunneling at T = ",
|
|
1281
|
+
T * conversionFactor,
|
|
1282
|
+
"GeV failed because the potential spline could not be constructed ("
|
|
1283
|
+
+ err.args[0]
|
|
1284
|
+
+ "). Treating this as no tunnelling.",
|
|
1285
|
+
)
|
|
1286
|
+
return {'action': np.inf, 'trantype': 0, 'instanton': None}, True
|
|
1287
|
+
except Exception as err:
|
|
1288
|
+
if verbose:
|
|
1289
|
+
print(
|
|
1290
|
+
"Tunneling at T =",
|
|
1291
|
+
T * conversionFactor,
|
|
1292
|
+
"GeV failed with error: ",
|
|
1293
|
+
err,
|
|
1294
|
+
)
|
|
1295
|
+
raise
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
def bounceAction(
|
|
1299
|
+
T: float,
|
|
1300
|
+
V: Callable[[np.ndarray, float], np.ndarray],
|
|
1301
|
+
dV: Callable[[np.ndarray, float], np.ndarray],
|
|
1302
|
+
outdict: dict,
|
|
1303
|
+
tdict: dict,
|
|
1304
|
+
verbose: bool = False,
|
|
1305
|
+
conversionFactor: float = 1.0,
|
|
1306
|
+
**fullTunneling_params,
|
|
1307
|
+
) -> dict:
|
|
1308
|
+
"""
|
|
1309
|
+
Compute the 1D bounce action and update the tunnelling dictionaries.
|
|
1310
|
+
|
|
1311
|
+
Parameters
|
|
1312
|
+
----------
|
|
1313
|
+
T : float
|
|
1314
|
+
Temperature at which to compute the bounce action.
|
|
1315
|
+
V : callable
|
|
1316
|
+
Potential function. Input arrays have shape ``(num_points, N_dim)`` and
|
|
1317
|
+
the output should have shape ``(num_points,)``.
|
|
1318
|
+
dV : callable
|
|
1319
|
+
Gradient of the potential with the same input shape as `V`. The output
|
|
1320
|
+
should have shape ``(num_points, N_dim)``.
|
|
1321
|
+
outdict : dict
|
|
1322
|
+
A dictionary mapping temperatures to tunnelling results. If the
|
|
1323
|
+
result for temperature `T` is already in this dictionary, it is
|
|
1324
|
+
returned immediately.
|
|
1325
|
+
tdict : dict
|
|
1326
|
+
Dictionary describing the tunnelling phase at temperature `T`. Must
|
|
1327
|
+
contain the keys ``'low_vev'`` and ``'high_vev'`` for the field values of
|
|
1328
|
+
the two minima.
|
|
1329
|
+
verbose : bool, optional
|
|
1330
|
+
If True, print diagnostic messages while computing the bounce action.
|
|
1331
|
+
conversionFactor : float, optional
|
|
1332
|
+
Factor to convert the temperature from internal units to GeV when
|
|
1333
|
+
printing messages.
|
|
1334
|
+
fullTunneling_params : dict, optional
|
|
1335
|
+
Extra parameters to pass to :func:`fullTunneling`. The function accepts
|
|
1336
|
+
them via ``**fullTunneling_params`` and forwards the dictionary.
|
|
1337
|
+
|
|
1338
|
+
Returns
|
|
1339
|
+
-------
|
|
1340
|
+
outdict : dict
|
|
1341
|
+
The updated output dictionary, with the result for temperature `T`
|
|
1342
|
+
stored under that key.
|
|
1343
|
+
"""
|
|
1344
|
+
if T in outdict:
|
|
1345
|
+
return outdict
|
|
1346
|
+
|
|
1347
|
+
def V_(x, T=T, V=V):
|
|
1348
|
+
return V(x, T)
|
|
1349
|
+
|
|
1350
|
+
def dV_(x, T=T, dV=dV):
|
|
1351
|
+
return dV(x, T)
|
|
1352
|
+
|
|
1353
|
+
x1, x0 = tdict['low_vev'], tdict['high_vev']
|
|
1354
|
+
|
|
1355
|
+
try:
|
|
1356
|
+
_validate_tunneling_path(x0, x1, V_)
|
|
1357
|
+
except errors.PotentialError as err:
|
|
1358
|
+
if verbose:
|
|
1359
|
+
print(
|
|
1360
|
+
"Tunneling at T = ",
|
|
1361
|
+
T * conversionFactor,
|
|
1362
|
+
"GeV failed during path validation: ",
|
|
1363
|
+
err.args[0],
|
|
1364
|
+
)
|
|
1365
|
+
if err.error_type == "no barrier":
|
|
1366
|
+
tdict['action'] = 0
|
|
1367
|
+
tdict['trantype'] = 2
|
|
1368
|
+
tdict['instanton'] = None
|
|
1369
|
+
elif err.error_type == "stable, not metastable":
|
|
1370
|
+
tdict['action'] = np.inf
|
|
1371
|
+
tdict['trantype'] = 0
|
|
1372
|
+
tdict['instanton'] = None
|
|
1373
|
+
else:
|
|
1374
|
+
raise errors.UnexpectedError(
|
|
1375
|
+
"In bounceAction: Unexpected error message: %s" % err.args[0]
|
|
1376
|
+
) from err
|
|
1377
|
+
outdict[T] = tdict
|
|
1378
|
+
return outdict
|
|
1379
|
+
|
|
1380
|
+
# Run the full tunnelling routine and translate the outcome into
|
|
1381
|
+
# the structure expected by the downstream percolation code.
|
|
1382
|
+
result, early_exit = _execute_full_tunneling(
|
|
1383
|
+
T,
|
|
1384
|
+
V_,
|
|
1385
|
+
dV_,
|
|
1386
|
+
tdict,
|
|
1387
|
+
fullTunneling_params,
|
|
1388
|
+
verbose,
|
|
1389
|
+
conversionFactor,
|
|
1390
|
+
)
|
|
1391
|
+
|
|
1392
|
+
tdict['instanton'] = result.get('instanton')
|
|
1393
|
+
tdict['action'] = result['action']
|
|
1394
|
+
tdict['trantype'] = result['trantype']
|
|
1395
|
+
|
|
1396
|
+
if tdict['action'] < 0:
|
|
1397
|
+
if verbose:
|
|
1398
|
+
print(
|
|
1399
|
+
"Tunneling at T = ",
|
|
1400
|
+
T * conversionFactor,
|
|
1401
|
+
"GeV failed because the action was computed as negative. This can happen if the potential barrier is extremely shallow. Returning zero action, which corresponds to a second-order transition.",
|
|
1402
|
+
)
|
|
1403
|
+
tdict['action'] = 0
|
|
1404
|
+
tdict['trantype'] = 2
|
|
1405
|
+
|
|
1406
|
+
outdict[T] = tdict
|
|
1407
|
+
|
|
1408
|
+
# When a spline failure occurs we skip downstream calculations, but still
|
|
1409
|
+
# provide the partial dictionary to avoid repeated work.
|
|
1410
|
+
if early_exit:
|
|
1411
|
+
return outdict
|
|
1412
|
+
|
|
1413
|
+
return outdict
|
|
1414
|
+
|
|
1415
|
+
|
|
1416
|
+
def bounceSolution(pot, T: float, xstart: np.ndarray, xend: np.ndarray,
|
|
1417
|
+
verbose: bool = False):
|
|
1418
|
+
"""Compute the instanton solution for tunneling from `xstart`
|
|
1419
|
+
to `xend`. Return the action and the instanton solution
|
|
1420
|
+
(bubble profile).
|
|
1421
|
+
|
|
1422
|
+
Parameters
|
|
1423
|
+
----------
|
|
1424
|
+
pot : generic_potential
|
|
1425
|
+
Effective potential
|
|
1426
|
+
T : float
|
|
1427
|
+
Temperature at which to compute the bounce action.
|
|
1428
|
+
xstart: np.ndarray
|
|
1429
|
+
high temperature minimum
|
|
1430
|
+
xend: np.ndarray
|
|
1431
|
+
low temperature minimum
|
|
1432
|
+
verbose : bool, optional
|
|
1433
|
+
If True, print diagnostic messages while computing the bounce action.
|
|
1434
|
+
|
|
1435
|
+
Returns
|
|
1436
|
+
-------
|
|
1437
|
+
profile1D : namedtuple
|
|
1438
|
+
The return value from ``tunneling_class.findProfile()``.
|
|
1439
|
+
Phi : array_like
|
|
1440
|
+
The points that constitute the final deformed path. They are in
|
|
1441
|
+
one-to-one correspondence with the points in `profile1D`.
|
|
1442
|
+
action : float
|
|
1443
|
+
The Euclidean action of the instanton.
|
|
1444
|
+
fRatio : float
|
|
1445
|
+
A ratio of the largest transverse force on the final path relative to
|
|
1446
|
+
the largest potential gradient. This would be zero if the solution were
|
|
1447
|
+
perfect.
|
|
1448
|
+
saved_steps : list
|
|
1449
|
+
A list of lists, with each sub-list containing the saved steps for each
|
|
1450
|
+
deformation. Only written to if `save_all_steps` is True.
|
|
1451
|
+
"""
|
|
1452
|
+
|
|
1453
|
+
def V_(x, T=T):
|
|
1454
|
+
return pot.Vtot(x, T, include_radiation=False, include_decoupled=False)
|
|
1455
|
+
|
|
1456
|
+
def dV_(x, T=T):
|
|
1457
|
+
return pot.gradV(x, T)
|
|
1458
|
+
|
|
1459
|
+
result = fullTunneling([xend, xstart], V_, dV_, callback_data=T,
|
|
1460
|
+
**pot.config.tracingConf.tunneling_params)
|
|
1461
|
+
|
|
1462
|
+
return result
|