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,309 @@
1
+ """Installation self-test for TransitionListener.
2
+
3
+ Called by ``tl --check`` (via src/transitionlistener/interface/check.py).
4
+ Can also be run directly: ``python tests/check_installation.py``.
5
+
6
+ The quick checks (~300 ms) use only code that ships inside the package.
7
+ They are followed automatically by an end-to-end physics smoke test
8
+ (~1-2 min) that runs ``example_point.yaml`` and validates key physical
9
+ outputs.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import math
15
+ import os
16
+ import re
17
+ import shutil
18
+ import subprocess
19
+ import sys
20
+ import tempfile
21
+ import time
22
+ from pathlib import Path
23
+
24
+
25
+ def _result(label: str, ok: bool, detail: str = "") -> None:
26
+ from transitionlistener import console
27
+ status = "[bold green]PASS[/bold green]" if ok else "[bold red]FAIL[/bold red]"
28
+ line = f" {status} {label}"
29
+ if detail:
30
+ line += f" ({detail})"
31
+ console.print(line)
32
+
33
+
34
+ def _find_examples_root() -> Path | None:
35
+ """Return the directory that contains both examples/ and models/.
36
+
37
+ Works for repo clones (this file lives at tests/check_installation.py,
38
+ so the repo root is one level up) and for pip installs where hatchling
39
+ has bundled examples/ as transitionlistener/examples/.
40
+ """
41
+ # Repo clone: tests/check_installation.py → repo root is parents[1]
42
+ candidate = Path(__file__).resolve().parents[1]
43
+ if (candidate / "examples" / "example_point.yaml").exists():
44
+ return candidate
45
+ # pip install: hatchling bundled examples/ as transitionlistener/examples/
46
+ try:
47
+ import importlib.resources as ir
48
+ pkg_root = Path(str(ir.files("transitionlistener")))
49
+ if (pkg_root / "examples" / "example_point.yaml").exists():
50
+ return pkg_root
51
+ except Exception:
52
+ pass
53
+ return None
54
+
55
+
56
+ def _run_smoke(console, failures: int) -> int:
57
+ """Run the end-to-end physics smoke test. Returns updated failure count."""
58
+ from rich.panel import Panel
59
+
60
+ console.print()
61
+ console.print(Panel.fit("[bold]End-to-end physics check[/bold]", border_style="cyan"))
62
+ console.print()
63
+
64
+ repo_root = _find_examples_root()
65
+ if repo_root is None:
66
+ console.print(" [bold yellow]SKIP[/bold yellow] smoke test (examples/ directory not found)")
67
+ return failures
68
+
69
+ yaml_src = repo_root / "examples" / "example_point.yaml"
70
+ model_src = repo_root / "models" / "TL_conformal_dark_u1.py"
71
+
72
+ with tempfile.TemporaryDirectory() as _tmp:
73
+ tmpdir = Path(_tmp)
74
+ (tmpdir / "examples").mkdir()
75
+ (tmpdir / "models").mkdir()
76
+ shutil.copy(yaml_src, tmpdir / "examples" / "example_point.yaml")
77
+ shutil.copy(model_src, tmpdir / "models" / "TL_conformal_dark_u1.py")
78
+
79
+ env = os.environ.copy()
80
+ env.update({"OMP_NUM_THREADS": "1", "OPENBLAS_NUM_THREADS": "1", "MKL_NUM_THREADS": "1"})
81
+
82
+ console.print(" Running example_point.yaml [dim](conformal U(1), SinglePoint — takes ~1-2 min)[/dim]")
83
+ t0 = time.perf_counter()
84
+ # Prefer the `tl` script from the same env as sys.executable.
85
+ tl_script = Path(sys.executable).parent / "tl"
86
+ if not tl_script.exists():
87
+ tl_script = Path(sys.executable).parent / "tl.exe" # Windows
88
+ cmd = (
89
+ [str(tl_script), "-c", "examples/example_point.yaml", "-j", "1"]
90
+ if tl_script.exists()
91
+ else [sys.executable, "-c",
92
+ "from transitionlistener.interface import main; main()",
93
+ "-c", "examples/example_point.yaml", "-j", "1"]
94
+ )
95
+ try:
96
+ proc = subprocess.run(
97
+ cmd,
98
+ cwd=tmpdir,
99
+ env=env,
100
+ capture_output=True,
101
+ text=True,
102
+ timeout=300,
103
+ )
104
+ except subprocess.TimeoutExpired:
105
+ _result("smoke test", False, "timed out after 300 s")
106
+ return failures + 1
107
+ elapsed = time.perf_counter() - t0
108
+
109
+ if proc.returncode != 0:
110
+ _result("smoke test", False, f"exit code {proc.returncode}")
111
+ console.print(f" [dim]{proc.stderr[-500:]}[/dim]")
112
+ return failures + 1
113
+
114
+ out_file = tmpdir / "scans" / "example_point" / "1_All_params.txt"
115
+ if not out_file.exists():
116
+ _result("smoke test", False, "output file not created")
117
+ return failures + 1
118
+
119
+ # Parse key-value output table
120
+ values: dict[str, float] = {}
121
+ for line in out_file.read_text().splitlines():
122
+ m = re.match(r"^\s*(\S+)\s+(\S.*?)\s*$", line)
123
+ if m:
124
+ try:
125
+ values[m.group(1)] = float(m.group(2))
126
+ except ValueError:
127
+ pass
128
+
129
+ EXPECTED = {
130
+ "Tperc_SM_GeV": (1.0e-3, 5.0e-2),
131
+ "alpha": (1.0e+1, 1.0e+4),
132
+ "RH": (1.0e-6, 1.0e+0),
133
+ }
134
+
135
+ smoke_ok = True
136
+ for key, (lo, hi) in EXPECTED.items():
137
+ if key not in values:
138
+ _result(f" {key}", False, "missing from output")
139
+ smoke_ok = False
140
+ failures += 1
141
+ elif not math.isfinite(values[key]) or not (lo <= values[key] <= hi):
142
+ _result(f" {key}", False, f"{values[key]:.3e} outside [{lo:.0e}, {hi:.0e}]")
143
+ smoke_ok = False
144
+ failures += 1
145
+ else:
146
+ _result(f" {key}", True, f"{values[key]:.3e}")
147
+
148
+ _result("smoke test (example_point.yaml)", smoke_ok, f"{elapsed:.0f} s")
149
+ if not smoke_ok:
150
+ failures += 1
151
+
152
+ return failures
153
+
154
+
155
+ def run_checks() -> int:
156
+ """Run all installation checks. Returns 0 on success, 1 on failure."""
157
+ from transitionlistener import console
158
+ from rich.panel import Panel
159
+
160
+ console.print(Panel.fit("[bold]TransitionListener installation check[/bold]", border_style="cyan"))
161
+ console.print()
162
+
163
+ failures = 0
164
+
165
+ # ------------------------------------------------------------------
166
+ # 1. Core scientific imports
167
+ # ------------------------------------------------------------------
168
+ for name in ("numpy", "scipy", "matplotlib", "pandas", "rich", "tqdm", "yaml"):
169
+ t0 = time.perf_counter()
170
+ try:
171
+ __import__(name)
172
+ _result(f"import {name}", True, f"{(time.perf_counter()-t0)*1e3:.0f} ms")
173
+ except ImportError as exc:
174
+ _result(f"import {name}", False, str(exc))
175
+ failures += 1
176
+
177
+ # ------------------------------------------------------------------
178
+ # 2. Thermodynamics (reads tab_data shipped with the package)
179
+ # ------------------------------------------------------------------
180
+ try:
181
+ t0 = time.perf_counter()
182
+ from transitionlistener.thermodynamics import e_geffSM, p_geffSM, s_geffSM
183
+ import numpy as np
184
+ T, CF = 10.0, 1.0
185
+ e = (math.pi**2 / 30) * e_geffSM(T, CF) * T**4
186
+ p = (math.pi**2 / 90) * p_geffSM(T, CF) * T**4
187
+ s = (2 * math.pi**2 / 45) * s_geffSM(T, CF) * T**3
188
+ residual = abs(s - (e + p) / T)
189
+ ok = residual < 1e-4
190
+ _result("thermodynamics (e+p=sT)", ok, f"residual={residual:.2e}, {(time.perf_counter()-t0)*1e3:.0f} ms")
191
+ if not ok:
192
+ failures += 1
193
+ except Exception as exc:
194
+ _result("thermodynamics", False, str(exc))
195
+ failures += 1
196
+
197
+ # ------------------------------------------------------------------
198
+ # 3. Percolation integral (ODE solver, no model file needed)
199
+ # ------------------------------------------------------------------
200
+ try:
201
+ t0 = time.perf_counter()
202
+ import numpy as np
203
+ from transitionlistener.bubbledynamics import percIntegralODE, percIntegral
204
+ T = np.geomspace(50.0, 0.5, 80)
205
+ s_over_t = 120.0 * (T / T[0]) ** 0.5 + 5.0
206
+ S = s_over_t * T
207
+ H = T**2 / 1e6
208
+ I_ode = percIntegralODE(T, H, S, vw=1.0)
209
+ ok = I_ode.shape == T.shape and float(I_ode[-1]) > 0.0
210
+ _result("percolation ODE integral", ok, f"{(time.perf_counter()-t0)*1e3:.0f} ms")
211
+ if not ok:
212
+ failures += 1
213
+ except Exception as exc:
214
+ _result("percolation ODE integral", False, str(exc))
215
+ failures += 1
216
+
217
+ # ------------------------------------------------------------------
218
+ # 4. Bounce action (asymmetric double-well: V = (x^2-1)^2/4 - eps*x)
219
+ # Minima at x≈±1; tiny eps=0.01 makes x≈+1 the true vacuum.
220
+ # Barrier at x≈0 sits well above both minima.
221
+ # ------------------------------------------------------------------
222
+ try:
223
+ t0 = time.perf_counter()
224
+ import numpy as np
225
+ from transitionlistener import pathDeformation
226
+
227
+ def V(x):
228
+ phi = np.asarray(x, dtype=float).ravel()
229
+ vals = 0.25 * (phi**2 - 1.0)**2 - 0.01 * phi
230
+ return float(vals[0]) if vals.size == 1 else vals
231
+
232
+ def dV(x):
233
+ x = np.asarray(x, dtype=float)
234
+ phi = x.ravel()
235
+ return (phi**3 - phi - 0.01).reshape(x.shape)
236
+
237
+ x_true = np.array([1.0])
238
+ x_false = np.array([-1.0])
239
+ tobj = pathDeformation.fullTunneling(
240
+ [x_true, x_false], V, dV,
241
+ callback_data=1.0,
242
+ )
243
+ finite = math.isfinite(float(tobj.action))
244
+ _result("bounce action (synthetic 1D)", finite, f"S3={tobj.action:.4f}, {(time.perf_counter()-t0)*1e3:.0f} ms")
245
+ if not finite:
246
+ failures += 1
247
+ except Exception as exc:
248
+ _result("bounce action (synthetic 1D)", False, str(exc))
249
+ failures += 1
250
+
251
+ # ------------------------------------------------------------------
252
+ # 5. Optional: MPI (informational — never fails)
253
+ # ------------------------------------------------------------------
254
+ try:
255
+ t0 = time.perf_counter()
256
+ from mpi4py import MPI # noqa: F401
257
+ size = MPI.COMM_WORLD.Get_size()
258
+ console.print(f" [bold cyan]INFO[/bold cyan] mpi4py [green]found[/green] (MPI size={size}, {(time.perf_counter()-t0)*1e3:.0f} ms)")
259
+ except Exception:
260
+ console.print(" [bold cyan]INFO[/bold cyan] mpi4py [yellow]not installed[/yellow] (MPI parallelism disabled)")
261
+
262
+ # ------------------------------------------------------------------
263
+ # 6. Optional: ptarcade / enterprise (informational — never fails)
264
+ # ------------------------------------------------------------------
265
+ try:
266
+ t0 = time.perf_counter()
267
+ import ptarcade # noqa: F401
268
+ from enterprise.signals.parameter import Uniform # noqa: F401
269
+ console.print(f" [bold cyan]INFO[/bold cyan] ptarcade + enterprise [green]found[/green] ({(time.perf_counter()-t0)*1e3:.0f} ms)")
270
+ except Exception:
271
+ console.print(" [bold cyan]INFO[/bold cyan] ptarcade + enterprise [yellow]not installed[/yellow] (PTA likelihood disabled)")
272
+
273
+ # ------------------------------------------------------------------
274
+ # Quick-check summary
275
+ # ------------------------------------------------------------------
276
+ console.print()
277
+ if failures == 0:
278
+ console.print(Panel.fit(
279
+ f"[bold green]Quick checks passed[/bold green] "
280
+ f"(Python {sys.version.split()[0]}, TL on {sys.platform})",
281
+ border_style="green",
282
+ ))
283
+ else:
284
+ console.print(Panel.fit(
285
+ f"[bold red]{failures} quick check(s) failed[/bold red] — see above",
286
+ border_style="red",
287
+ ))
288
+
289
+ # ------------------------------------------------------------------
290
+ # 7. End-to-end physics smoke test (automatic follow-up)
291
+ # ------------------------------------------------------------------
292
+ failures = _run_smoke(console, failures)
293
+
294
+ # ------------------------------------------------------------------
295
+ # Final summary
296
+ # ------------------------------------------------------------------
297
+ console.print()
298
+ if failures == 0:
299
+ console.print(Panel.fit("[bold green]All checks passed[/bold green]", border_style="green"))
300
+ else:
301
+ console.print(Panel.fit(
302
+ f"[bold red]{failures} check(s) failed[/bold red] — see above",
303
+ border_style="red",
304
+ ))
305
+ return 0 if failures == 0 else 1
306
+
307
+
308
+ if __name__ == "__main__":
309
+ raise SystemExit(run_checks())
@@ -0,0 +1,91 @@
1
+ r"""
2
+ Cabibbo–Kobayashi–Maskawa matrix helpers.
3
+
4
+ The functions defined here provide ready-to-use parametrisations of the CKM
5
+ matrix that can be shared between models. The default implementation follows
6
+ the Wolfenstein expansion,
7
+
8
+ .. math::
9
+ V_{\mathrm{CKM}} \approx
10
+ \begin{pmatrix}
11
+ 1 - \tfrac{1}{2}\lambda^2 & \lambda &
12
+ A \lambda^3 (\rho - i \eta) \\
13
+ -\lambda & 1 - \tfrac{1}{2}\lambda^2 & A \lambda^2 \\
14
+ A \lambda^3 \left(1 - \rho - i \eta\right) &
15
+ -A \lambda^2 & 1
16
+ \end{pmatrix},
17
+
18
+ with higher-order terms resummed to guarantee unitarity. The phases are
19
+ returned as complex numbers so that downstream code can keep track of CP
20
+ violating effects without additional bookkeeping.
21
+
22
+ Part of TransitionListener v2.0
23
+ Documentation: https://tasillo.de/TransitionListener/
24
+
25
+ Authors:
26
+ Jonas Matuszak <jonas.matuszak@kit.edu>
27
+ Carlo Tasillo <carlo.tasillo@ific.uv.es>
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import cmath
33
+ import math
34
+ from typing import Dict
35
+
36
+ CKMElementMap = Dict[str, complex]
37
+
38
+
39
+ def wolfenstein_ckm(
40
+ lambda_w: float = 0.22537,
41
+ A: float = 0.814,
42
+ rho: float = 0.117,
43
+ eta: float = 0.353,
44
+ ) -> CKMElementMap:
45
+ r"""
46
+ Return the CKM matrix elements in the Wolfenstein parametrisation.
47
+
48
+ Parameters
49
+ ----------
50
+ lambda_w:
51
+ Cabibbo angle expansion parameter :math:`\\lambda`.
52
+ A:
53
+ Overall strength of the 2–3 mixing.
54
+ rho:
55
+ Real part of the complex phase.
56
+ eta:
57
+ Imaginary part of the complex phase.
58
+
59
+ Returns
60
+ -------
61
+ dict[str, complex]
62
+ Mapping ``Vxy -> V_{xy}`` covering all nine CKM entries.
63
+ """
64
+
65
+ theta12 = math.asin(lambda_w)
66
+ theta23 = math.asin(A * lambda_w**2)
67
+ complex_phase = A * (lambda_w**3) * (rho + 1j * eta)
68
+ delta = cmath.phase(complex_phase)
69
+ theta13 = math.asin(abs(complex_phase))
70
+
71
+ Vud = math.cos(theta12) * math.cos(theta13)
72
+ Vus = math.sin(theta12) * math.cos(theta13)
73
+ Vub = math.sin(theta13) * cmath.exp(-1j * delta)
74
+ Vcd = -math.sin(theta12) * math.cos(theta23) - math.cos(theta12) * math.sin(theta23) * math.sin(theta13) * cmath.exp(1j * delta)
75
+ Vcs = math.cos(theta12) * math.cos(theta23) - math.sin(theta12) * math.sin(theta23) * math.sin(theta13) * cmath.exp(1j * delta)
76
+ Vcb = math.sin(theta23) * math.cos(theta13)
77
+ Vtd = math.sin(theta12) * math.sin(theta23) - math.cos(theta12) * math.cos(theta23) * math.sin(theta13) * cmath.exp(1j * delta)
78
+ Vts = -math.cos(theta12) * math.sin(theta23) - math.sin(theta12) * math.cos(theta23) * math.sin(theta13) * cmath.exp(1j * delta)
79
+ Vtb = math.cos(theta23) * math.cos(theta13)
80
+
81
+ return {
82
+ "Vud": Vud,
83
+ "Vus": Vus,
84
+ "Vub": Vub,
85
+ "Vcd": Vcd,
86
+ "Vcs": Vcs,
87
+ "Vcb": Vcb,
88
+ "Vtd": Vtd,
89
+ "Vts": Vts,
90
+ "Vtb": Vtb,
91
+ }
@@ -0,0 +1,288 @@
1
+ """Color palettes and Matplotlib helpers used across TransitionListener plots.
2
+
3
+ The constants exposed here encode the corporate identity color palettes that the
4
+ project uses for plots in the documentation as well as publication-quality
5
+ figures. A couple of helper utilities are provided to make it easy to size
6
+ figures consistently and to configure Matplotlib's global font settings.
7
+
8
+ Part of TransitionListener v2.0
9
+ Documentation: https://tasillo.de/TransitionListener/
10
+
11
+ Authors:
12
+ Jonas Matuszak <jonas.matuszak@kit.edu>
13
+ Carlo Tasillo <carlo.tasillo@ific.uv.es>
14
+ """
15
+
16
+ import matplotlib.pyplot as plt
17
+ from matplotlib.colors import LinearSegmentedColormap
18
+ from cycler import cycler
19
+
20
+ RWTHblau = (0 / 255, 84 / 255, 159 / 255)
21
+ RWTHblau75 = (0 / 255, 84 / 255, 159 / 255, 0.75)
22
+ RWTHblau50 = (0 / 255, 84 / 255, 159 / 255, 0.5)
23
+ RWTHblau25 = (0 / 255, 84 / 255, 159 / 255, 0.25)
24
+ RWTHblau10 = (0 / 255, 84 / 255, 159 / 255, 0.1)
25
+ RWTHblau20 = (0 / 255, 84 / 255, 159 / 255, 0.2)
26
+ RWTHblau40 = (0 / 255, 84 / 255, 159 / 255, 0.4)
27
+ RWTHblau30 = (0 / 255, 84 / 255, 159 / 255, 0.3)
28
+ RWTHblau60 = (0 / 255, 84 / 255, 159 / 255, 0.6)
29
+ RWTHschwarz = (0 / 255, 0 / 255, 0 / 255)
30
+ RWTHmagenta = (227 / 255, 0 / 255, 102 / 255)
31
+ RWTHgelb = (255 / 255, 237 / 255, 0 / 255)
32
+ RWTHpetrol = (0 / 255, 97 / 255, 101 / 255)
33
+ RWTHpetrol25 = (0 / 255, 97 / 255, 101 / 255, 0.25)
34
+ RWTHtuerkis = (0 / 255, 152 / 255, 161 / 255)
35
+ RWTHgruen = (87 / 255, 171 / 255, 39 / 255)
36
+ RWTHgruen10 = (87 / 255, 171 / 255, 39 / 255, 0.1)
37
+ RWTHgruen25 = (87 / 255, 171 / 255, 39 / 255, 0.25)
38
+ RWTHmaigruen = (189 / 255, 205 / 255, 0 / 255)
39
+ RWTHmaigruen25 = (189 / 255, 205 / 255, 0 / 255, 0.25)
40
+ RWTHmaigruen50 = (189 / 255, 205 / 255, 0 / 255, 0.5)
41
+ RWTHorange = (246 / 255, 168 / 255, 0 / 255)
42
+ RWTHorange25 = (246 / 255, 168 / 255, 0 / 255, 0.25)
43
+ RWTHorange50 = (246 / 255, 168 / 255, 0 / 255, 0.5)
44
+ RWTHrot = (204 / 255, 7 / 255, 30 / 255)
45
+ RWTHrot25 = (204 / 255, 7 / 255, 30 / 255, 0.25)
46
+ RWTHrot50 = (204 / 255, 7 / 255, 30 / 255, 0.5)
47
+ RWTHbordeaux = (161 / 255, 16 / 255, 53 / 255)
48
+ RWTHbordeaux50 = (161 / 255, 16 / 255, 53 / 255, 0.5)
49
+ RWTHbordeaux25 = (161 / 255, 16 / 255, 53 / 255, 0.25)
50
+ RWTHbordeaux10 = (161 / 255, 16 / 255, 53 / 255, 0.1)
51
+ RWTHviolett = (97 / 255, 33 / 255, 88 / 255)
52
+ RWTHviolett50 = (97 / 255, 33 / 255, 88 / 255, 0.5)
53
+ RWTHlila = (122 / 255, 111 / 255, 172 / 255)
54
+
55
+ DESYcyan = (0 / 255, 159 / 255, 223 / 255)
56
+ DESYcyan50 = (0 / 255, 159 / 255, 223 / 255, 0.5)
57
+ DESYcyan25 = (0 / 255, 159 / 255, 223 / 255, 0.25)
58
+ DESYcyan75 = (0 / 255, 159 / 255, 223 / 255, 0.75)
59
+ DESYcyan10 = (0 / 255, 159 / 255, 223 / 255, 0.10)
60
+ DESYrot = (230 / 255, 65 / 255, 35 / 255)
61
+ DESYrot50 = (230 / 255, 65 / 255, 35 / 255, 0.5)
62
+ DESYdunkelrot = (153 / 255, 0 / 255, 0 / 255)
63
+ DESYorange = (241 / 255, 143 / 255, 31 / 255)
64
+ DESYorange25 = (241 / 255, 143 / 255, 31 / 255, 0.25)
65
+ DESYdunkelblau = (0 / 255, 74 / 255, 110 / 255)
66
+ DESYaubergine = (139 / 255, 60 / 255, 126 / 255)
67
+ DESYaubergine50 = (139 / 255, 60 / 255, 126 / 255, 0.5)
68
+ DESYviolett = (146 / 255, 124 / 255, 184 / 255)
69
+ DESYviolett50 = (146 / 255, 124 / 255, 184 / 255, 0.5)
70
+ DESYlila = (82 / 255, 78 / 255, 156 / 255)
71
+ DESYlila50 = (82 / 255, 78 / 255, 156 / 255, 0.5)
72
+ DESYhellgruen = (122 / 255, 158 / 255, 31 / 255)
73
+ DESYhellgruen50 = (122 / 255, 158 / 255, 31 / 255, 0.5)
74
+ DESYtuerkis = (45 / 255, 164 / 255, 164 / 255)
75
+ DESYmagenta = (208 / 255, 0 / 255, 111 / 255)
76
+ DESYolive = (158 / 255, 150 / 255, 0 / 255)
77
+ DESYhellbraun = (170 / 255, 132 / 255, 106 / 255)
78
+ DESYdunkelbraun = (133 / 255, 88 / 255, 62 / 255)
79
+ DESYdunkelolive = (89 / 255, 98 / 255, 29 / 255)
80
+ DESYpetrol = (0 / 255, 106 / 255, 133 / 255)
81
+ DESYgruen = (0 / 255, 168 / 255, 62 / 255)
82
+ DESYgelb = (230 / 255, 169 / 255, 15 / 255)
83
+
84
+ HumboldtGreen = (129 / 255, 238 / 255, 169 / 255)
85
+
86
+ invisible = (255 / 255, 255 / 255, 255 / 255, 0)
87
+
88
+ colors = [DESYcyan, DESYcyan25]
89
+ cmapDESYcyan = LinearSegmentedColormap.from_list("mycmap", colors)
90
+
91
+ colors = [DESYorange, DESYorange25]
92
+ cmapDESYorange = LinearSegmentedColormap.from_list("mycmap", colors)
93
+
94
+ colors = [DESYcyan25, DESYcyan]
95
+ cmapNG = LinearSegmentedColormap.from_list("mycmap", colors)
96
+
97
+ colors = [DESYorange25, DESYorange]
98
+ cmapIPTA = LinearSegmentedColormap.from_list("mycmap", colors)
99
+
100
+ NG = DESYcyan
101
+ IPTA = DESYorange
102
+
103
+
104
+ colors = [DESYorange, DESYcyan]
105
+ cmapDESYcyanorange = LinearSegmentedColormap.from_list("mycmap", colors)
106
+
107
+ colors = [
108
+ DESYpetrol,
109
+ DESYtuerkis,
110
+ DESYgruen,
111
+ DESYhellgruen,
112
+ DESYorange,
113
+ DESYrot,
114
+ DESYdunkelrot,
115
+ DESYviolett,
116
+ DESYlila,
117
+ ]
118
+ cmapDESYrainbow = LinearSegmentedColormap.from_list("DESYrainbow", colors)
119
+
120
+ colors = [
121
+ DESYorange,
122
+ HumboldtGreen
123
+ ]
124
+ cmapTL = LinearSegmentedColormap.from_list("TL", colors)
125
+
126
+ colors = [
127
+ DESYorange,
128
+ HumboldtGreen,
129
+ DESYcyan
130
+ ]
131
+ cmapTL2 = LinearSegmentedColormap.from_list("TL2", colors)
132
+
133
+ colors = [RWTHblau10, RWTHblau]
134
+ cmapRWTHblau = LinearSegmentedColormap.from_list("mycmap", colors)
135
+
136
+ colors = [RWTHblau25, RWTHblau]
137
+ cmapRWTHblau2 = LinearSegmentedColormap.from_list("mycmap", colors)
138
+
139
+ colors = [RWTHblau50, RWTHblau]
140
+ cmapRWTHblau3 = LinearSegmentedColormap.from_list("mycmap", colors)
141
+
142
+ colors = [RWTHgruen10, RWTHgruen]
143
+ cmapRWTHgruen = LinearSegmentedColormap.from_list("mycmap", colors)
144
+
145
+ colors = [DESYhellgruen, invisible]
146
+ cmapDESYhellgruen = LinearSegmentedColormap.from_list("mycmap", colors)
147
+
148
+ colors = [RWTHbordeaux10, RWTHbordeaux]
149
+ cmapRWTHbordeaux = LinearSegmentedColormap.from_list("mycmap", colors)
150
+
151
+ colors = [
152
+ RWTHpetrol,
153
+ RWTHtuerkis,
154
+ RWTHgruen,
155
+ RWTHmaigruen,
156
+ RWTHorange,
157
+ RWTHrot,
158
+ RWTHbordeaux,
159
+ RWTHviolett,
160
+ RWTHlila,
161
+ ]
162
+ cmapRWTHrainbow = LinearSegmentedColormap.from_list("mycmap", colors)
163
+
164
+ colors = [
165
+ RWTHtuerkis,
166
+ RWTHgruen,
167
+ RWTHmaigruen,
168
+ RWTHorange,
169
+ RWTHrot,
170
+ RWTHbordeaux,
171
+ RWTHviolett,
172
+ ]
173
+ cmapRWTHrainbow2 = LinearSegmentedColormap.from_list("mycmap", colors)
174
+
175
+ colors = [RWTHgruen, RWTHmaigruen, RWTHorange, RWTHrot, RWTHbordeaux, RWTHviolett]
176
+ cmapRWTHrainbow3 = LinearSegmentedColormap.from_list("mycmap", colors)
177
+
178
+ colors = [RWTHblau, RWTHrot]
179
+ cmapRWTHtemp = LinearSegmentedColormap.from_list("mycmap", colors)
180
+
181
+ colors = ["white", "white"]
182
+ cmapWhite = LinearSegmentedColormap.from_list("mycmap", colors)
183
+
184
+ colors = [
185
+ (255 / 255, 255 / 255, 255 / 255, 0),
186
+ (200 / 255, 200 / 255 / 255, 200 / 255, 0.1),
187
+ (150 / 255, 150 / 255, 150 / 255, 0.4),
188
+ (100 / 255, 100 / 255, 100 / 255, 0.5),
189
+ (50 / 255, 50 / 255, 50 / 255, 0.6),
190
+ (0, 0, 0, 0.75),
191
+ ]
192
+ cmapGreyshade = LinearSegmentedColormap.from_list("mycmap", colors)
193
+
194
+
195
+ def set_size(width: float | str, fraction : float = 1, subplots: tuple[int, int] = (1, 1)) -> tuple[float, float]:
196
+ """Return figure dimensions that avoid downstream scaling artefacts.
197
+
198
+ Parameters
199
+ ----------
200
+ width: float | str
201
+ Either the Matplotlib figure width in points or a preset identifier
202
+ such as ``\"JCAP\"`` or ``\"beamer\"`` that maps to a known text width.
203
+ fraction: float, optional
204
+ Fraction of the available width the figure should occupy. Defaults to
205
+ ``1`` which uses the full width.
206
+ subplots: tuple[int, int], optional
207
+ Tuple describing the subplot layout (rows, columns). The information is
208
+ used to slightly enlarge multi-panel figures so that axis labels do not
209
+ overlap.
210
+
211
+ Returns
212
+ -------
213
+ fig_dim: tuple[float, float]
214
+ Figure width and height in inches.
215
+ """
216
+ if width == "JCAP":
217
+ width_pt = 440.0
218
+ elif width == "beamer":
219
+ width_pt = 307.28987
220
+ else:
221
+ width_pt = width
222
+
223
+ # Width of figure (in pts)
224
+ fig_width_pt = width_pt * fraction
225
+
226
+ # Convert from pt to inches
227
+ inches_per_pt = 1 / 72.27
228
+
229
+ # Golden ratio to set aesthetic figure height
230
+ # https://disq.us/p/2940ij3
231
+ golden_ratio = (5**0.5 - 1) / 2
232
+
233
+ # Figure width in inches
234
+ fig_width_in = fig_width_pt * inches_per_pt
235
+
236
+ # Figure height in inches
237
+ if subplots == (1, 3):
238
+ fig_height_in = fig_width_in * golden_ratio / 2 * 1.2
239
+ elif subplots != (1, 1):
240
+ fig_height_in = (
241
+ fig_width_in * golden_ratio * 1.2
242
+ ) # to include title, some extra space
243
+ else: # (1,1) case
244
+ fig_height_in = fig_width_in * golden_ratio
245
+
246
+ fig_dim = (fig_width_in, fig_height_in)
247
+
248
+ return fig_dim
249
+
250
+
251
+ def set_font():
252
+ """Update Matplotlib's global ``rcParams`` with the project's stylistic defaults.
253
+
254
+ The configuration mirrors the publication layout used in the documentation:
255
+ LaTeX-rendered text, a sans-serif font family, harmonised font sizes for titles
256
+ and axis labels, and a colour cycle based on the DESY palette defined in this
257
+ module. Calling this function is idempotent and affects figures created
258
+ afterwards.
259
+ """
260
+ plt.rcParams.update(
261
+ {
262
+ "text.usetex": True,
263
+ "font.family": "sans-serif",
264
+ "font.sans-serif": ["DejaVu Sans", "Arial", "Liberation Sans"],
265
+ "mathtext.fontset": "dejavusans",
266
+ "axes.labelsize": 10,
267
+ "font.size": 10,
268
+ "axes.titlesize": 10,
269
+ "figure.titlesize": 10,
270
+ "legend.fontsize": 8,
271
+ "xtick.labelsize": 8,
272
+ "ytick.labelsize": 8,
273
+ "lines.markersize": 5,
274
+ "axes.prop_cycle": cycler(
275
+ color=[
276
+ DESYcyan,
277
+ DESYorange,
278
+ DESYmagenta,
279
+ DESYgruen,
280
+ DESYrot,
281
+ DESYdunkelblau,
282
+ DESYviolett,
283
+ DESYhellgruen,
284
+ DESYgelb,
285
+ ]
286
+ ),
287
+ }
288
+ )