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,1000 @@
1
+ """This module contains routines to compute the possible transitions
2
+ between phases.
3
+
4
+ Part of TransitionListener v2.0
5
+ Documentation: https://tasillo.de/TransitionListener/
6
+
7
+ Authors:
8
+ Jonas Matuszak <jonas.matuszak@kit.edu>
9
+ Carlo Tasillo <carlo.tasillo@ific.uv.es>
10
+ """
11
+
12
+ from dataclasses import dataclass
13
+ from typing import TYPE_CHECKING
14
+
15
+ import numpy as np
16
+ from scipy import optimize
17
+
18
+ from . import pathDeformation
19
+ from . import errors
20
+
21
+ from . import console, print
22
+
23
+ from rich.table import Table
24
+ from rich.panel import Panel
25
+ from rich.columns import Columns as RichColumns
26
+ from rich.box import ROUNDED
27
+
28
+ from transitionlistener.nucleation import computeNucleationTemperature
29
+ from transitionlistener.generic_potential import generic_potential
30
+
31
+ if TYPE_CHECKING:
32
+ from transitionlistener.phases import PhaseInfo
33
+
34
+
35
+ @dataclass
36
+ class TransitionInfo():
37
+ """Object containing the information about one possible phase transition."""
38
+ Tcrit: float
39
+ """Critical temperature where the two minima are degenerate"""
40
+ high_vev: np.ndarray
41
+ """Field values in the high phase"""
42
+ high_phase: int
43
+ """Key of the high phase"""
44
+ low_vev: np.ndarray
45
+ """Field values in the low phase"""
46
+ low_phase: int
47
+ """Key of the low phase"""
48
+ type: int
49
+ """Type of transition: 1 for first order, 2 fo second order (continuous)"""
50
+ Tnuc: float = 0.0
51
+ """Nucleation temperature."""
52
+ Tperc: float = 0.0
53
+ """Percolation temperature."""
54
+ full_tunneling_info = {}
55
+ """Save the temperatures and corresponding actions from the nucleation
56
+ computation."""
57
+ action_Tnuc: float = 0.0
58
+ """Action at the nucleation."""
59
+ instanton = {}
60
+ """Instanton solution at the nucleation temperature."""
61
+ step: int = 0
62
+ """Ordering number of the possible transitions"""
63
+ total_steps: int = 0
64
+ """Total number of transitions."""
65
+ derived_params = {}
66
+ """Computed observables."""
67
+
68
+
69
+ def _transition_sort_temperature(transition: TransitionInfo) -> tuple[float, float]:
70
+ """Sort transitions by descending nucleation temperature, falling back to ``Tcrit``.
71
+
72
+ Seedless percolation can keep first-order transition candidates with
73
+ ``Tnuc=None``. Those transitions still need a stable ordering, so we fall
74
+ back to ``Tcrit`` when no finite ``Tnuc`` is available.
75
+ """
76
+ tnuc = transition.Tnuc
77
+ tcrit = transition.Tcrit
78
+ tnuc_key = float(tnuc) if tnuc is not None and np.isfinite(tnuc) else float("-inf")
79
+ tcrit_key = float(tcrit) if tcrit is not None and np.isfinite(tcrit) else float("-inf")
80
+ return tnuc_key, tcrit_key
81
+
82
+
83
+ def _format_temperature_gev(temperature: float | None, conversion_factor: float) -> str:
84
+ """Return a readable GeV string for finite temperatures and ``None`` otherwise."""
85
+ if temperature is None or not np.isfinite(temperature):
86
+ return "None"
87
+ return str(temperature * conversion_factor)
88
+
89
+
90
+ class Transitions():
91
+ """Compute the transition path from the high-T phase to the 0-T phase
92
+ for a given set of phases.
93
+
94
+ Class containing the routines to find the transitions between
95
+ all possible pairs of phases.
96
+
97
+ Parameters
98
+ ----------
99
+ phases : list["PhaseInfo"]
100
+ List of the phases as PhaseInfo objects.
101
+ pot : generic_potential
102
+ The potential.
103
+ verbose : bool, optional
104
+ Set the outputlevel.
105
+ """
106
+
107
+ def __init__(self, phases: list["PhaseInfo"], pot: generic_potential,
108
+ verbose: bool = False):
109
+ """Trace the transition network and determine nucleation temperatures.
110
+
111
+ Parameters
112
+ ----------
113
+ phases : list[PhaseInfo]
114
+ List of the phases as PhaseInfo objects.
115
+ pot : generic_potential
116
+ The potential.
117
+ verbose : bool, optional
118
+ Set the outputlevel.
119
+ """
120
+ self.phases = phases
121
+ self.pot = pot
122
+ self.V = pot.Vtot
123
+ self.dV = pot.gradV
124
+ self.d2V = pot.d2V
125
+ self.verbose = verbose
126
+
127
+ if self.phases is None or len(self.phases) == 0:
128
+ msg = "Cannot find phase transitions, because no phases were found."
129
+ raise errors.NoPhases(msg)
130
+ if len(self.phases) == 1:
131
+ msg = "Cannot find phase transitions, because only one phase was found."
132
+ raise errors.OnlyOnePhase(msg)
133
+
134
+ # assemble the tunneling parameters:
135
+ self.tunnelFromPhase_args = {"Ttol": pot.config.tracingConf.nucleation_Ttol,
136
+ "approximate_strength_threshold": pot.config.tracingConf.approx_strength_threshold,
137
+ "nuclCriterion": pot.nucleationCriterion,
138
+ "fullTunneling_params": pot.config.tracingConf.tunneling_params}
139
+
140
+ self.conversionFactor = pot.conversionFactor
141
+
142
+ if verbose:
143
+ console.print("\n[bold green]Finding phase transitions...[/bold green]")
144
+
145
+ transitionsTnuc = self.findTransitionPathFromTnuc(verbose=verbose)
146
+ self.transitions = transitionsTnuc
147
+
148
+ if verbose:
149
+ msg = (
150
+ f"\n[bold green]Found {len(transitionsTnuc)} possible "
151
+ f"phase transition(s) ...[/bold green]"
152
+ )
153
+ console.print(msg)
154
+
155
+ self.printTransitions(transitionsTnuc)
156
+
157
+ if len(transitionsTnuc) == 0:
158
+ raise errors.NoTransitionFound("No transition found.")
159
+
160
+ if all(tr.type == 2 for tr in transitionsTnuc):
161
+ raise errors.OnlySecondOrderTransitionsError(
162
+ "Only second-order transition(s) found. No first-order transitions.")
163
+
164
+
165
+ def __str__(self):
166
+ """Pretty-print the discovered transitions and return an empty string."""
167
+ self.printTransitions(self.transitions)
168
+ return ""
169
+
170
+ def __iter__(self):
171
+ """Enable iteration over the list of transitions."""
172
+ return iter(self.transitions)
173
+
174
+ def __getitem__(self, key: str):
175
+ """Allow dictionary-style access via the transition index."""
176
+ return self.transitions[key]
177
+
178
+ def __len__(self):
179
+ """Return the number of transitions that were found."""
180
+ return len(self.transitions)
181
+
182
+ def keys(self):
183
+ """Expose the valid indices for the transition list."""
184
+ return self.transitions.keys()
185
+
186
+ def findAllTransitions(self) -> dict[TransitionInfo]:
187
+ """Find all the possible transitions between coexisting phases
188
+ by computing the nucleation temperatures.
189
+
190
+ Parameters
191
+ ----------
192
+ phases : list[PhaseInfo]
193
+ V : func
194
+ dV : func
195
+
196
+ Returns
197
+ -------
198
+ dict[TransitionInfo]
199
+ List with TransitionInfo objects.
200
+ """
201
+ pot = self.pot
202
+ phases = self.phases
203
+ verbose = self.verbose
204
+ tunnelFromPhase_args = self.tunnelFromPhase_args
205
+ seedless = pot.config.percolationConf.algorithm_mode == "adaptive_step_size"
206
+
207
+ # This below could miss transitions in 2D cases where there is no critical
208
+ # temperature but the phases still coexist!
209
+ transitions = self.findTransitionPathFromTnuc(verbose=verbose)
210
+
211
+ if transitions is None:
212
+ raise errors.NoTransitionFound("No transition found.")
213
+
214
+ if verbose:
215
+ message = f"[bold green]Found {len(transitions)} possible transition(s):[/bold green]"
216
+ for tr in transitions:
217
+ message += f"\n{tr.high_phase} -> {tr.low_phase} at Tcrit = {tr.Tcrit * self.conversionFactor} GeV"
218
+ console.print()
219
+ console.print(Panel.fit(message,border_style="green"))
220
+
221
+ transitions_dict = []
222
+ TooMuchSupercooling = False
223
+ for tr in transitions:
224
+ if tr.type != 1:
225
+ # This is a second order transition. Still add it to the list
226
+ # of transitions, but don't try to find the tunneling solution.
227
+ second_order_dict = self.makeSecondOrderDict(tr)
228
+ transitions_dict.append(second_order_dict)
229
+ continue
230
+ Tmax = tr.Tcrit
231
+ if verbose:
232
+ print("\n"+"#"*50)
233
+ print(f"Investigating transition with Tcrit = {tr.Tcrit * self.conversionFactor} GeV" + \
234
+ f" from phase {tr.high_phase:} to phase {tr.low_phase:}")
235
+ high_phase = phases[tr.high_phase]
236
+ low_phase = phases[tr.low_phase]
237
+ try:
238
+ tunn = computeNucleationTemperature(self.V, self.dV, self.d2V, high_phase, low_phase,
239
+ self.conversionFactor, verbose=verbose,
240
+ allow_second_order_fallback=not seedless,
241
+ **tunnelFromPhase_args)
242
+ # sometimes a second order transition slips through the phase tracing
243
+ if tunn['trantype'] == 2:
244
+ tr.type = 2
245
+ tr.action = np.nan
246
+ transitions_dict.append(tr)
247
+ else:
248
+ tr.full_tunneling_info = tunn["full_tunneling_info"]
249
+ tr.Tnuc = tunn["Tnuc"]
250
+ tr.low_phase = tunn["low_phase"]
251
+ tr.high_phase = tunn["high_phase"]
252
+ tr.instanton = tunn["instanton"]
253
+ tr.type = tunn["trantype"]
254
+ tr.action = tunn["action"]
255
+ transitions_dict.append(tr)
256
+ except errors.NucleationError as err:
257
+ if verbose:
258
+ print("Nucleation error: ", err)
259
+ if seedless:
260
+ tr.Tnuc = None
261
+ tr.action = np.nan
262
+ tr.full_tunneling_info = {}
263
+ tr.instanton = {}
264
+ transitions_dict.append(tr)
265
+ else:
266
+ TooMuchSupercooling = True
267
+ continue
268
+ except Exception as err:
269
+ raise errors.NucleationError("An unexpected error occurred " +
270
+ "while trying to find the tunneling solution: "
271
+ + str(err))
272
+
273
+ # Check if we have found any transitions
274
+ if len(transitions_dict) == 0:
275
+ if TooMuchSupercooling:
276
+ raise errors.TooMuchSupercoolingError()
277
+ raise errors.NoTransitionFound("No transition found.")
278
+
279
+ # Check if only second-order transitions were found
280
+ if all(tr.type == 2 for tr in transitions_dict):
281
+ raise errors.OnlySecondOrderTransitionsError(
282
+ "Only second-order transition(s) found. No first-order transitions.")
283
+
284
+ # Check if there are transitions in the list, that are not possible
285
+ # because they already happened through a (series of) previous transition(s).
286
+ # For instance, if nucleation temperatures for the transitions 0 -> 1, 1 -> 2,
287
+ # and 0 -> 2 are found, but 0 -> 1 has already happened when 0 -> 2 would nucleate,
288
+ # 0 -> 2 cannot happen and we can remove the 0 -> 2 transition from the list.
289
+ # This is done by going through the list of transitions, starting from
290
+ # the highest Tnuc and checking if a given transition is would start
291
+ # from a phase, that has already disappeared:
292
+ disappeared_phases = set()
293
+ # order the transitions by Tnuc: the highest Tnuc first
294
+ transitions_dict = sorted(transitions_dict, key=_transition_sort_temperature, reverse=True)
295
+
296
+ # Have to check if the first transition is second order, if yes ->
297
+ # replace highT_phase_key with the one it second order transitions to!
298
+ highT_phase_key = [key for key in phases.keys() if phases[key].T[-1] == pot.Tmax][0]
299
+ for tr in transitions_dict:
300
+ if tr.high_phase == highT_phase_key and tr.type == 2:
301
+ highT_phase_key = tr.low_phase
302
+
303
+ for i, tr in enumerate(transitions_dict):
304
+ # Check if the first FOPT in the dictionary actually starts from
305
+ # the high-T phase of the model. If not, we throw an error.
306
+
307
+ # Note: this is only a hotfix for a specific physics bug in the vev flip flop model.
308
+ # There are cases in which this check here leads to the wrong results. What should be
309
+ # done instead is to only check one transition at a time, starting from the high-T phase.
310
+ # This will need some restructuring of the code, so for now we just
311
+ # check the first transition in the dictionary.
312
+ if pot.config.tracingConf.do_high_T_phase_check:
313
+ if i == 0:
314
+ if tr.high_phase != highT_phase_key and tr.type == 1:
315
+ raise errors.WrongHighTPhaseError(
316
+ "The first transition in the transition dictionary " +
317
+ "does not start from the high-T phase of the model. " +
318
+ "This is unexpected and indicates a bug in the code.")
319
+
320
+ # If the starting (high) phase of the transition is in the set of
321
+ # disappeared phases, we can remove this transition from the list
322
+ if tr.high_phase in disappeared_phases:
323
+ if verbose:
324
+ print(
325
+ "\n WARNING: Removing the transition "
326
+ f"{tr.high_phase} -> {tr.low_phase} at Tnuc = "
327
+ f"{_format_temperature_gev(tr.Tnuc, self.conversionFactor)} GeV from the dictionary "
328
+ "because the high phase has already disappeared in a "
329
+ "previous transition."
330
+ )
331
+ # transitions_dict[i] = None
332
+ transitions_dict.remove(transitions_dict[i])
333
+ else:
334
+ # If the high phase is not in the set of disappeared phases, we can add it to the set
335
+ disappeared_phases.add(tr.high_phase)
336
+
337
+ # Enumerate the transitions and save the order in the dictionary
338
+ for i, tr in enumerate(transitions_dict):
339
+ tr.step = i + 1
340
+ tr.total_steps = len(transitions_dict)
341
+
342
+ # Output the transitions
343
+ if verbose:
344
+ print("\nFound %d transition(s): \n" % len(transitions_dict))
345
+ self.printTransitions(transitions_dict)
346
+
347
+ return transitions_dict
348
+
349
+ def printTransitions(self, transitions: dict[TransitionInfo]) -> None:
350
+ """Pretty-printing of the transitions
351
+
352
+ Parameters
353
+ ----------
354
+ transitions : dict[TransitionInfo]
355
+ List with TransitionInfo objects.
356
+ """
357
+ tables = []
358
+ for tr in transitions:
359
+ if tr.type == 1:
360
+ table = Table(title="First-order phase transition", title_justify="left", box=ROUNDED)
361
+ table.add_column("Parameter", style="cyan", no_wrap=True)
362
+ table.add_column("Value", style="orange1")
363
+ table.add_row("Tnuc / GeV", _format_temperature_gev(tr.Tnuc, self.pot.conversionFactor))
364
+ table.add_row("high phase", str(tr.high_phase))
365
+ table.add_row("low phase", str(tr.low_phase))
366
+ table.add_row("high vev / GeV", str(tr.high_vev * self.pot.conversionFactor))
367
+ table.add_row("low vev / GeV", str(tr.low_vev * self.pot.conversionFactor))
368
+ table.add_row("action / GeV", str(tr.action_Tnuc * self.pot.conversionFactor))
369
+ tables.append(table)
370
+ elif tr.type == 2:
371
+ # This is a second-order transition
372
+ table = Table(title="Second-order phase transition", title_justify="left", box=ROUNDED)
373
+ table.add_column("Parameter", style="cyan", no_wrap=True)
374
+ table.add_column("Value", style="orange1")
375
+ table.add_row("Tcrit / GeV", _format_temperature_gev(tr.Tcrit, self.pot.conversionFactor))
376
+ table.add_row("high phase", str(tr.high_phase))
377
+ table.add_row("low phase", str(tr.low_phase))
378
+ table.add_row("high vev / GeV", str(tr.high_vev * self.pot.conversionFactor))
379
+ table.add_row("low vev / GeV", str(tr.low_vev * self.pot.conversionFactor))
380
+ tables.append(table)
381
+ console.print(RichColumns(tables))
382
+
383
+ def secondOrderTrans(self, V: callable, high_phase: "PhaseInfo",
384
+ low_phase: "PhaseInfo", verbose=False,
385
+ degeneracyTest=True) -> TransitionInfo:
386
+ """Create the TransitionInfo object for a second order transition.
387
+
388
+ Parameters
389
+ ----------
390
+ V : callable
391
+ The effective potential function.
392
+ high_phase : PhaseInfo
393
+ The phase information for the high-temperature phase
394
+ low_phase : PhaseInfo
395
+ The phase information for the low-temperature phase
396
+ verbose : bool
397
+ Set the output level.
398
+ degeneracyTest : bool, optional
399
+ If true, search for Tcrit, where the to minima are degenerate.
400
+ Note, that this causes errors for hidden second order transitions
401
+
402
+ Returns
403
+ -------
404
+ TransitionInfo:
405
+ The transition information.
406
+
407
+ Notes
408
+ -----
409
+ A second order transition is identified by finding the critical temperature
410
+ Tcrit where the two phases are degenerate in energy. At this temperature, the potential
411
+ values of the two phases are equal. If such a temperature is found and the
412
+ degeneracy check passes, a TransitionInfo object is created to represent the
413
+ second order transition.
414
+ """
415
+ # Calculate the critical temperature Tcrit, where the two phases are
416
+ # degenerate. This is done by finding the temperature where the
417
+ # potential is equal for the two phases. This is done by using
418
+ # optimize.brentq to find the root of the function
419
+ def f(T): return V(high_phase.valAt(T), T) - V(low_phase.valAt(T), T)
420
+
421
+ Tcrit = np.nan
422
+
423
+ try:
424
+ Tcrit = optimize.brentq(f, high_phase.T[0], low_phase.T[-1], disp=False)
425
+ if verbose:
426
+ print("Second order transition between phases %s and %s at Tcrit = %g GeV" \
427
+ % (high_phase.key, low_phase.key, Tcrit * self.conversionFactor))
428
+ print("high_phase.valAt(Tcrit) = ", high_phase.valAt(Tcrit) * self.conversionFactor, "GeV")
429
+ print("low_phase.valAt(Tcrit) = ", low_phase.valAt(Tcrit) * self.conversionFactor, "GeV")
430
+ print("V(high_phase.valAt(Tcrit), Tcrit) = ", V(high_phase.valAt(Tcrit), Tcrit) \
431
+ * self.conversionFactor**4, "GeV^4")
432
+ print("V(low_phase.valAt(Tcrit), Tcrit) = ", V(low_phase.valAt(Tcrit), Tcrit) \
433
+ * self.conversionFactor**4, "GeV^4\n")
434
+ except ValueError as err:
435
+ if verbose:
436
+ lower = high_phase.T[0] * self.conversionFactor
437
+ upper = low_phase.T[-1] * self.conversionFactor
438
+ print(
439
+ "The critical temperature could not be found in the range "
440
+ f"({lower}, {upper}) GeV between the phases "
441
+ f"{high_phase.key} and {low_phase.key}. This is likely a "
442
+ "problem due to the numerical precision used in phase tracing."
443
+ )
444
+ print("ValueError: ", err)
445
+ Tcrit = np.nan
446
+
447
+ # no Tcrit found:
448
+ if np.isnan(Tcrit):
449
+ Tcrit = low_phase.Tmax
450
+ transition = TransitionInfo(
451
+ Tcrit=Tcrit,
452
+ high_vev=high_phase.X[0],
453
+ high_phase=high_phase.key,
454
+ low_vev=low_phase.X[0],
455
+ low_phase=low_phase.key,
456
+ type=2,
457
+ Tnuc=Tcrit,
458
+ Tperc=Tcrit)
459
+ elif np.abs(V(high_phase.valAt(Tcrit), Tcrit) / V(low_phase.valAt(Tcrit), Tcrit) - 1) < 1e-4:
460
+ # Check if the two phases are actually degenerate at Tcrit
461
+ # This is a second order transition
462
+ if verbose:
463
+ print("The check for degeneracy at Tcrit = ", Tcrit * self.conversionFactor,
464
+ "GeV succeeded. Create crossover transition object now.")
465
+ transition = TransitionInfo(
466
+ Tcrit=Tcrit,
467
+ high_vev=high_phase.X[0],
468
+ high_phase=high_phase.key,
469
+ low_vev=low_phase.X[0],
470
+ low_phase=low_phase.key,
471
+ type=2,
472
+ Tnuc=Tcrit, # this part is technically wrong, but we need a value to sort the transitions
473
+ Tperc=Tcrit)
474
+ else:
475
+ if verbose:
476
+ print("Degeneracy check failed at Tcrit = ", Tcrit * self.conversionFactor, "GeV")
477
+ Tcrit = np.nan
478
+ transition = TransitionInfo(
479
+ Tcrit=np.nan,
480
+ high_vev=high_phase.X[0],
481
+ high_phase=high_phase.key,
482
+ low_vev=low_phase.X[0],
483
+ low_phase=low_phase.key,
484
+ type=2,
485
+ Tnuc=np.nan,
486
+ Tperc=np.nan)
487
+ return transition
488
+
489
+ def makeSecondOrderDict(self, transition: TransitionInfo) -> dict:
490
+ """
491
+ Create a dictionary describing a second-order phase transition.
492
+ """
493
+ transition_dict = TransitionInfo(
494
+ Tcrit=transition.Tcrit,
495
+ high_vev=transition.high_vev,
496
+ high_phase=transition.high_phase,
497
+ low_vev=transition.low_vev,
498
+ low_phase=transition.low_phase,
499
+ type=2,
500
+ Tnuc=np.nan,
501
+ Tperc=np.nan)
502
+ return transition_dict
503
+
504
+ def sortForSmallestAction(self, V, phases, startPhase, childPhases) -> list:
505
+ """Estimate the action by computing the action for the linearised
506
+ potential between the start phase and the child phases.
507
+ Return a sorted list of the child phases after the
508
+ smallest action approximation
509
+
510
+ Parameters
511
+ ----------
512
+
513
+ Returns
514
+ -------
515
+
516
+ .. todo:: implement
517
+
518
+ """
519
+ pass
520
+
521
+ def getUniqueTunnelingPhases(self, V : callable, phases : list["PhaseInfo"],
522
+ startPhase : "PhaseInfo",
523
+ childPhases : list["PhaseInfo"]) -> list["PhaseInfo"]:
524
+ """Check if there are equivalent tunneling paths to different phases.
525
+
526
+ This functions checks there are two phases which have the same distance
527
+ to the start phase and the same potential values. If so the
528
+ tunneling paths are considered equivalent and only one phase is returned.
529
+
530
+ Parameters
531
+ ----------
532
+ V : callable
533
+ The effective potential V(X, T)
534
+ phases : list[PhaseInfo]
535
+ childPhases : list
536
+ List of phase keys
537
+
538
+ Returns
539
+ -------
540
+ list :
541
+ List with phase keys.
542
+ """
543
+ xtol = 1e-3
544
+ Vtol = 1e-3
545
+ uniqueTunnelingPhases = []
546
+ while childPhases != []:
547
+ p1 = childPhases.pop()
548
+ DX1min = np.sqrt(np.sum(phases[p1].X[0] - phases[startPhase].X[0])**2)
549
+ DV1min = np.abs(V(phases[p1].X[0], phases[p1].T[0]) -
550
+ V(phases[startPhase].X[0], phases[startPhase].T[0]))
551
+ DX1max = np.sqrt(np.sum(phases[p1].X[-1] - phases[startPhase].X[-1])**2)
552
+ DV1max = np.abs(V(phases[p1].X[-1], phases[p1].T[-1]) -
553
+ V(phases[startPhase].X[-1], phases[startPhase].T[-1]))
554
+ equiv = False
555
+ for p2 in childPhases:
556
+ DX2min = np.sqrt(np.sum(phases[p2].X[0] - phases[startPhase].X[0])**2)
557
+ DX2max = np.sqrt(np.sum(phases[p2].X[-1] - phases[startPhase].X[-1])**2)
558
+ DV2min = np.abs(V(phases[p2].X[0], phases[p2].T[0]) -
559
+ V(phases[startPhase].X[0], phases[startPhase].T[0]))
560
+ DV2max = np.abs(V(phases[p2].X[-1], phases[p2].T[-1]) -
561
+ V(phases[startPhase].X[-1], phases[startPhase].T[-1]))
562
+ if ((abs(DV1min - DV2min) < Vtol and abs(DX1min - DX2min) < xtol) and
563
+ (abs(DV1max - DV2max) < Vtol and abs(DX1max - DX2max) < xtol)):
564
+ equiv = True
565
+ print("WARNING: Encountered 2 equivalent child phases. " +
566
+ "This implies domain walls!")
567
+ break
568
+ if not equiv:
569
+ uniqueTunnelingPhases.append(p1)
570
+
571
+ return uniqueTunnelingPhases
572
+
573
+ def findTransitionPathFromTnuc(self, verbose=False) -> list[TransitionInfo]:
574
+ """Find the transition path starting from the high-T phase.
575
+
576
+ Calculate the nucleation temperature for the transitions
577
+ starting from the high-T phase. The function constructs
578
+ a list of transitions with the highest possible nucleation
579
+ temperatures.
580
+
581
+ Parameters
582
+ ----------
583
+ verbose : bool
584
+ Set the output level.
585
+
586
+ Returns
587
+ -------
588
+ list[TranstionInfo]:
589
+ A list of of transitions sorted in decreasing temperature.
590
+ """
591
+ transitions = []
592
+ tunnelFromPhase_args = self.tunnelFromPhase_args
593
+ phases = self.phases
594
+ V = self.V
595
+ startPhase = phases[phases.rootPhase]
596
+ seedless = self.pot.config.percolationConf.algorithm_mode == "adaptive_step_size"
597
+
598
+ while True:
599
+ # check if we are in the lowest minimum
600
+ if startPhase.childPhases == []:
601
+ if startPhase.key == self.phases.T0Phase:
602
+ break
603
+ else:
604
+ raise errors.WrongT0MinimumError
605
+
606
+ childTransitions = []
607
+ # exclude equivalent tunnelings to mirror phases
608
+ childPhases = self.getUniqueTunnelingPhases(V, phases, startPhase.key, startPhase.childPhases)
609
+ for i in childPhases:
610
+ endPhase = phases[i]
611
+
612
+ # Check for overlap between phases
613
+ tmax = min(startPhase.Tmax, endPhase.Tmax)
614
+ tmin = max(startPhase.Tmin, endPhase.Tmin)
615
+
616
+ if tmin >= tmax:
617
+ if endPhase.key in startPhase.low_trans:
618
+ childTransitions.append(
619
+ self.secondOrderTrans(V, startPhase, endPhase, verbose))
620
+ startPhase = endPhase
621
+ continue # There could be other second order transitions (at slightly high temps)
622
+ else:
623
+ pass
624
+ continue
625
+
626
+ if seedless:
627
+ transition = self.seedlessFirstOrderCandidate(
628
+ V,
629
+ startPhase,
630
+ endPhase,
631
+ tmin,
632
+ tmax,
633
+ verbose,
634
+ )
635
+ if transition is not None:
636
+ childTransitions.append(transition)
637
+ continue
638
+
639
+ try:
640
+ if verbose:
641
+ print(f"\nTry tunneling from phase {startPhase.key} to phase {endPhase.key}")
642
+ tunn = computeNucleationTemperature(self.V, self.dV, self.d2V,
643
+ startPhase,
644
+ endPhase, self.conversionFactor,
645
+ verbose=verbose,
646
+ allow_second_order_fallback=not seedless,
647
+ **tunnelFromPhase_args)
648
+
649
+ # create TunnelingInfo object
650
+ tr = TransitionInfo(Tcrit=tunn['Tcrit'], Tnuc=tunn['Tnuc'],
651
+ low_phase=tunn['low_phase'],
652
+ high_phase=tunn['high_phase'],
653
+ low_vev=tunn['low_vev'],
654
+ high_vev=tunn['high_vev'],
655
+ action_Tnuc=tunn['action'],
656
+ type=tunn['trantype'])
657
+ if tr.type != 2: # Sometimes a second order transitions slips through here
658
+ tr.instanton = tunn['instanton']
659
+ childTransitions.append(tr)
660
+ except errors.NucleationError as err:
661
+ # Too much supercooling, cannot tunnel.
662
+ if verbose:
663
+ print("Nucleation error: ", err)
664
+ if seedless:
665
+ transition = TransitionInfo(
666
+ Tcrit=tmax,
667
+ Tnuc=None,
668
+ low_phase=endPhase.key,
669
+ high_phase=startPhase.key,
670
+ low_vev=endPhase.valAt(tmax),
671
+ high_vev=startPhase.valAt(tmax),
672
+ action_Tnuc=np.nan,
673
+ type=1,
674
+ )
675
+ transition.full_tunneling_info = {}
676
+ transition.instanton = {}
677
+ childTransitions.append(transition)
678
+ else:
679
+ continue
680
+ except Exception as err:
681
+ raise errors.NucleationError("Unexpected nucleation error:\n" +
682
+ str(err))
683
+
684
+ if childTransitions == []:
685
+ raise errors.TooMuchSupercoolingError
686
+
687
+ childTransitions = sorted(childTransitions, key=_transition_sort_temperature, reverse=True)
688
+ # new start phase:
689
+ startPhase = phases[childTransitions[0].low_phase]
690
+ transitions.append(childTransitions[0])
691
+
692
+ for i, tr in enumerate(transitions):
693
+ tr.step = i + 1
694
+ tr.total_steps = len(transitions)
695
+
696
+ return transitions
697
+
698
+ def seedlessFirstOrderCandidate(
699
+ self,
700
+ V: callable,
701
+ high_phase: "PhaseInfo",
702
+ low_phase: "PhaseInfo",
703
+ tmin: float,
704
+ tmax: float,
705
+ verbose: bool = False,
706
+ ) -> TransitionInfo | None:
707
+ """Build a first-order transition candidate without computing Tnuc.
708
+
709
+ Seedless adaptive step size should not spend CPU finding a nucleation-temperature seed
710
+ before percolation. It only needs a plausible high/low phase pair and
711
+ a finite overlap interval; the dynamic support-bank scouts then decide
712
+ where action evaluations are physically relevant.
713
+ """
714
+
715
+ def DV(T):
716
+ return V(high_phase.valAt(T), T) - V(low_phase.valAt(T), T)
717
+
718
+ try:
719
+ dv_min = DV(tmin)
720
+ dv_max = DV(tmax)
721
+ except Exception as err:
722
+ if verbose:
723
+ print(
724
+ "Skipping seedless candidate because the phase-overlap "
725
+ "potential difference could not be evaluated:",
726
+ err,
727
+ )
728
+ return None
729
+
730
+ if dv_min < 0:
731
+ if verbose:
732
+ print(
733
+ "Skipping seedless candidate because the starting phase is "
734
+ "already lower than the target phase at Tmin."
735
+ )
736
+ return None
737
+
738
+ candidate_temperature = tmax
739
+ if dv_max <= 0:
740
+ try:
741
+ candidate_temperature = optimize.brentq(DV, tmin, tmax, disp=False)
742
+ except ValueError:
743
+ candidate_temperature = tmax
744
+ elif high_phase.Tmax <= low_phase.Tmax:
745
+ if verbose:
746
+ print(
747
+ "Skipping seedless candidate because the starting phase is "
748
+ "higher at Tmax and also exists to at least as high a "
749
+ "temperature as the target phase."
750
+ )
751
+ return None
752
+
753
+ if verbose:
754
+ print(
755
+ "Adding seedless first-order candidate from phase",
756
+ high_phase.key,
757
+ "to phase",
758
+ low_phase.key,
759
+ "with reference T =",
760
+ candidate_temperature * self.conversionFactor,
761
+ "GeV",
762
+ )
763
+
764
+ transition = TransitionInfo(
765
+ Tcrit=candidate_temperature,
766
+ Tnuc=None,
767
+ low_phase=low_phase.key,
768
+ high_phase=high_phase.key,
769
+ low_vev=low_phase.valAt(candidate_temperature),
770
+ high_vev=high_phase.valAt(candidate_temperature),
771
+ action_Tnuc=np.nan,
772
+ type=1,
773
+ )
774
+ transition.full_tunneling_info = {}
775
+ transition.instanton = {}
776
+ return transition
777
+
778
+ def findPossibleTransitionsFromPhases(self, verbose=False) -> list[TransitionInfo]:
779
+ """Find all possible transitions between coexisting phases.
780
+
781
+ Parameters
782
+ ----------
783
+ verbose : bool
784
+ Set the output level.
785
+
786
+ Returns
787
+ -------
788
+ list[TranstionInfo]:
789
+ A list of of transitions sorted in decreasing temperature.
790
+ """
791
+
792
+ phases = self.phases
793
+ V = self.V
794
+ start_high = False
795
+ verbose = self.verbose
796
+
797
+ if verbose:
798
+ print("\nFind all possible transitions based on the existence of a critical temperature:")
799
+ transitions = []
800
+ for i in phases.keys():
801
+ for j in phases.keys():
802
+ if i == j:
803
+ continue
804
+ # Try going from i to j (phase1 -> phase2)
805
+ phase1, phase2 = phases[i], phases[j]
806
+ tmax = min(phase1.Tmax, phase2.Tmax)
807
+ tmin = max(phase1.Tmin, phase2.Tmin)
808
+
809
+ if verbose:
810
+ print("\nChecking transition from phase", i, "to phase", j,
811
+ "which coexists in the range [", tmin * self.conversionFactor,
812
+ ",", tmax * self.conversionFactor, "] GeV")
813
+
814
+ if tmin >= tmax:
815
+ # There is no overlap between the two phases, so no transition can occur. Still, if previously
816
+ # the low_trans dictionary was filled, indicating the possibility of a second-order transition,
817
+ # we can still check for that.
818
+ if verbose:
819
+ print("There's no overlap between the phases. A crossover transition might "
820
+ + "be possible if there is a link between the two.")
821
+ if phase2.key in phase1.low_trans:
822
+ transitions.append(
823
+ self.secondOrderTrans(V, phase1, phase2, verbose))
824
+ else:
825
+ pass
826
+ continue
827
+
828
+ def DV(T):
829
+ return V(phase1.valAt(T), T) - V(phase2.valAt(T), T)
830
+
831
+ DVmin, DVmax = DV(tmin), DV(tmax)
832
+
833
+ if DVmin < 0:
834
+ # phase1 is lower at tmin, so no tunneling can occur
835
+ if verbose:
836
+ print("Tunneling cannot occur, because phase", i,
837
+ "is lower than phase", j, "at tmin: DV(tmin) =",
838
+ DVmin * self.conversionFactor**4, "GeV^4")
839
+ continue
840
+
841
+ if DVmax > 0:
842
+ # phase1 is higher even at tmax. This can be weird, because a critical
843
+ # temperature in 1d potential needs to exist and be below Tmax. In 2d
844
+ # scenarios, however, this can happen without a problem. In the latter
845
+ # case, we need to make sure that this is not an unphysical transition
846
+ # 1 -> 0 though, from a phase 1 that will only become relevant at
847
+ # lower temperatures and might allow for 0 -> 1, assuming that the
848
+ # univere is in phase 0 at high temperatures. To check this, we
849
+ # require that the maximal temperature at which phase1 (the starting
850
+ # phase) is defined is higher than that of phase2 (the ending phase)
851
+ if phase1.Tmax > phase2.Tmax:
852
+ if verbose:
853
+ print("Possible transition without a critical temp. found." +
854
+ "This can happen for multidimensional potentials.")
855
+ # Note: Tcrit here is not an actual critical temperature, but
856
+ # the largest temperature where the two phases coexist.
857
+ transition = TransitionInfo(
858
+ Tcrit=tmax,
859
+ high_vev=phase1.valAt(tmax),
860
+ high_phase=phase1.key,
861
+ low_vev=phase2.valAt(tmax),
862
+ low_phase=phase2.key,
863
+ type=1)
864
+ transitions.append(transition)
865
+ else:
866
+ if verbose:
867
+ print("Tunneling cannot occur, because phase", i,
868
+ "is higher than phase", j, "at tmax: DV(tmax) =",
869
+ DVmax * self.conversionFactor**4,
870
+ "GeV^4 and the initial phase exists since higher temperatures.")
871
+ continue
872
+
873
+ Tcrit = optimize.brentq(DV, tmin, tmax, disp=False)
874
+ if verbose:
875
+ print("Found a possible transition from phase", i,
876
+ "to phase", j, "with Tcrit =",
877
+ Tcrit * self.conversionFactor, "GeV")
878
+ transition = TransitionInfo(
879
+ Tcrit=Tcrit,
880
+ high_vev=phase1.valAt(Tcrit),
881
+ high_phase=phase1.key,
882
+ low_vev=phase2.valAt(Tcrit),
883
+ low_phase=phase2.key,
884
+ type=1)
885
+ transitions.append(transition)
886
+
887
+ if start_high:
888
+ raise NotImplementedError("start_high=True not yet supported")
889
+
890
+ return sorted(transitions, key=lambda x: x.Tcrit)[::-1]
891
+
892
+ def getStartPhase(self, phases, V=None):
893
+ """
894
+ Find the key for the high-T phase.
895
+
896
+ Parameters
897
+ ----------
898
+ phases : dict
899
+ Output from :func:`traceMultiMin`.
900
+ V : callable
901
+ The potential V(x,T). Only necessary if there are
902
+ multiple phases with the same Tmax.
903
+ """
904
+ startPhases = []
905
+ startPhase = None
906
+ Tmax = None
907
+ assert len(phases) > 0
908
+ for i in list(phases.keys()):
909
+ if phases[i].T[-1] == Tmax:
910
+ # add this to the startPhases list.
911
+ startPhases.append(i)
912
+ elif Tmax is None or phases[i].T[-1] > Tmax:
913
+ startPhases = [i]
914
+ Tmax = phases[i].T[-1]
915
+ if len(startPhases) == 1 or V is None:
916
+ startPhase = startPhases[0]
917
+ else:
918
+ # more than one phase have the same maximum temperature
919
+ # Pick the stable one at high temp.
920
+ Vmin = None
921
+ for i in startPhases:
922
+ V_ = V(phases[i].X[-1], phases[i].T[-1])
923
+ if Vmin is None or V_ < Vmin:
924
+ Vmin = V_
925
+ startPhase = i
926
+ assert startPhase in phases
927
+ return startPhase
928
+
929
+ def evalNucleationCriterion(self, T: float, start_phase: "PhaseInfo",
930
+ end_phase: "PhaseInfo", V: callable, dV: callable,
931
+ phitol: float, overlapAngle: float,
932
+ nuclCriterion: callable,
933
+ fullTunneling_params: dict, verbose: bool,
934
+ outdict: dict) -> float:
935
+ """ Find the lowest action tunneling solution.
936
+
937
+ This functions stores the results in the dict `outdict` with
938
+ the temperature as the key.
939
+
940
+ Parameters
941
+ ----------
942
+ T : float
943
+ The temperature.
944
+ start_phase : PhaseInfo
945
+ The starting phase
946
+ end_phase : PhaseInfo
947
+ The end phase
948
+ V : callable
949
+ The effective potential
950
+ dV : callable
951
+ The gradient of the effective potential.
952
+
953
+ Returns
954
+ -------
955
+ float :
956
+ Evaluation of nuclCriterion(S,T)."""
957
+ try:
958
+ T = T[0] # need this when the function is run from optimize.fmin
959
+ except:
960
+ pass
961
+
962
+ # Loop through all the phases, adding acceptable minima
963
+ x0 = start_phase.valAt(T)
964
+
965
+ if T in outdict:
966
+ return nuclCriterion(outdict[T]['action'], T, start_phase, end_phase)
967
+
968
+ V0 = V(x0, T)
969
+ p = end_phase
970
+
971
+ if p.Tmin > T:
972
+ if verbose:
973
+ print("Phase %s not valid at T = %g" % (p.key, T * self.conversionFactor),
974
+ "GeV < Tmin = ", p.Tmin * self.conversionFactor, "GeV")
975
+ return np.inf
976
+
977
+ if p.Tmax < T:
978
+ if verbose:
979
+ print("Phase %s not valid at T = %g" % (p.key, T * self.conversionFactor),
980
+ "GeV > Tmax = ", p.Tmax * self.conversionFactor, "GeV")
981
+ return -np.inf
982
+
983
+ x1 = p.valAt(T)
984
+ V1 = V(x1, T)
985
+ if V1 >= V0:
986
+ if verbose:
987
+ print("Tunneling energetically not possible. Tmax might be too" +
988
+ " high or the numerical accuracy is not sufficient. This is not a problem, just a warning.")
989
+
990
+ tdict = dict(low_vev=x1, high_vev=x0, Tnuc=T,
991
+ low_phase=end_phase.key, high_phase=start_phase.key)
992
+ # Get rid of the T parameter for V and dV
993
+
994
+ outdict = pathDeformation.bounceAction(
995
+ T, V, dV, outdict, tdict, verbose=verbose,
996
+ conversionFactor=self.conversionFactor,
997
+ **fullTunneling_params)
998
+
999
+ S = outdict[T]['action']
1000
+ return nuclCriterion(S, T, start_phase, end_phase)