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,185 @@
1
+ """This module contains the configurations.
2
+
3
+ Part of TransitionListener v2.0
4
+ Documentation: https://tasillo.de/TransitionListener/
5
+
6
+ Authors:
7
+ Jonas Matuszak <jonas.matuszak@kit.edu>
8
+ Carlo Tasillo <carlo.tasillo@ific.uv.es>
9
+ """
10
+
11
+ import copy
12
+
13
+
14
+ class TracingConf:
15
+ """Phase-tracing and tunneling computation parameters."""
16
+
17
+ internal_scale = 1000 # Reference energy scale (internal units); sets the magnitude of the stable minimum.
18
+ Tmax_factor = 2.5 # Tmax_factor * internal_scale = max tracing temperature.
19
+ diftol = 1 # Max field-space distance between two phases (internal units) before they merge.
20
+ Z2_cutof_factor = -5 # For Z2-symmetric potentials: reject phases with field < Z2_cutof_factor * diftol.
21
+ tracing_derivative_order = 4 # Finite-difference derivative order: 2 or 4.
22
+ tracing_field_accuracy = 1e-3 # Target field-space accuracy during tracing (1e-3 works well).
23
+ tracing_temp_accuracy = 1e-3 # Target temperature accuracy during tracing.
24
+ gen_mirror_phases = False # If True, also generate mirror-transformed phases of the potential.
25
+ nucleation_Ttol = 1e-8 # Nucleation-temperature tolerance (internal units).
26
+ do_tachyon_test = True # If True, verify the potential is stable at T = 0.
27
+ approx_strength_threshold = 1e-4 # Transitions with DV / T^4 below this are treated as second-order.
28
+ do_high_T_phase_check = True # Hotfix toggle for high-T phase checks (needed by the vev-flip-flop model).
29
+
30
+ tracing_args = {
31
+ "dtstart": 1e-4, # Initial trace step for traceMultiMin (scaled by tHigh - tLow).
32
+ "tjump": 1e-5, # Branch-jump size when tracing continues after a phase endpoint.
33
+ "dtabsMax": 20.0, # Max absolute step is largest of abs(dtstart)*dtabsMax ...
34
+ "dtfracMax": 0.25, # ... and t*dtfracMax in internal units.
35
+ "dtmin": 1e-6, # Min step (relative to dtstart) before assuming transition ends.
36
+ "deltaX_tol": 1.2, # deltaX_tol*deltaX_target = max x-error before shrinking the step.
37
+ "minratio": 1e-4, # Smallest/largest Hessian eigenvalue ratio before branch ends.
38
+ "local_min_args": { # Forwarded to phases.findApproxLocalMin() from traceMultiMin().
39
+ "n": 100, # Interpolation samples along x1->x2 for local-min detection.
40
+ "edge": 0.05, # Fraction trimmed off each end before searching.
41
+ },
42
+ }
43
+ """Arguments forwarded to the phase-tracing routines."""
44
+ tunneling_params = {
45
+ "tunneling_findProfile_params": {
46
+ "phitol": 1e-10, # Field-space tolerance for the overshoot/undershoot profile.
47
+ "xtol": 1e-10, # Radial shooting tolerance for the bounce match.
48
+ "rmin": 1e-4,
49
+ "rmax": 1e4,
50
+ "npoints": 500, # Radial support points for the bounce profile.
51
+ },
52
+ "V_spline_samples": 1000, # Points used to spline V(x) along the bounce path; None evaluates V directly.
53
+ "tunneling_init_params": {
54
+ "phi_eps": 1e-6, # Initial offset from the false vacuum.
55
+ "rscale": None, # Manual radial scale; None = auto-estimate.
56
+ },
57
+ "deformation_deform_params": {
58
+ "startstep": 2e-3,
59
+ "fRatioConv": 2e-2, # Convergence on normal/tangential force ratio along the path. Auto-tightened to 1e-2 for Ndim>=2 models in generic_potential.__init__.
60
+ "converge_0": 5.0, # Early-stop threshold for the force-ratio at the first stage.
61
+ "fRatioIncrease": 5.0,
62
+ "maxiter": 500,
63
+ "verbose": False,
64
+ },
65
+ }
66
+ """Arguments forwarded to the bounce-action / tunneling routines."""
67
+
68
+ def __init__(self):
69
+ """Give every model its own copy of the mutable default dicts."""
70
+ self.tracing_args = copy.deepcopy(TracingConf.tracing_args)
71
+ self.tunneling_params = copy.deepcopy(TracingConf.tunneling_params)
72
+
73
+
74
+ class PercolationConf:
75
+ """Settings for the percolation solver."""
76
+
77
+ # Solver mode and integral backend.
78
+ algorithm_mode = "adaptive_step_size" # "adaptive_step_size" (default) or "fixed_step_size".
79
+ integral_method = "ode" # Percolation integral backend: "ode" or "double_integral".
80
+ time_temperature_mode = "sound_speed" # dT/dt relation: "sound_speed" or "bag".
81
+
82
+ # Percolation targets. Iperc is computed as -ln(1 - f_perc) at use sites.
83
+ f_perc = 0.28957 # True-vacuum fraction at percolation.
84
+ f_start = 1e-3 # Max initial true-vacuum fraction; below this, widen the T-range.
85
+ f_final = 0.99 # Min final true-vacuum fraction for a completed percolation.
86
+
87
+ # Iteration controls shared by both percolation passes.
88
+ weight = 1 / 1.5 # Fraction of support points spent between Tnuc and Tperc (fixed_step_size mode).
89
+ maxit = 10 # Max iterations per percolation pass (step 2 and step 3 each).
90
+ rel_increment = 0.10 # Relative T-range growth when the initial range is too small.
91
+ max_boundary_ratio = 0.45 # Max share of T-range allowed on plateaus where P_true = 0 or 1.
92
+
93
+ # Support-bank budget (adaptive step size mode).
94
+ n_action_min = 15 # Min support points built before a DZW sweep.
95
+ n_action_increment = 5 # Support points added per DZW refinement iteration.
96
+ n_action_max = 60 # Hard cap on total support points across DZW iterations.
97
+ max_action_temperatures = 100 # Absolute ceiling on distinct action evaluations per run.
98
+
99
+ # fixed_step_size support count.
100
+ n_action = 30 # Fixed support count used by the fixed_step_size path.
101
+
102
+ # P(T)-jump refinement and final acceptance.
103
+ large_delta_p_refine_threshold = 0.1 # Neighbour DeltaP above this triggers extra refinement.
104
+ large_delta_p_success_threshold = 0.2 # Neighbour DeltaP above this blocks acceptance.
105
+
106
+ # Diagnostics and optional rescue paths.
107
+ jitter_GH4_threshold = 1.0 # log10(Gamma/H^4) jitter tolerance before flagging unresolved action.
108
+ jitter_rescue = True # If True, rerun with tunneltight precision on jitter detection.
109
+ n_jitter_save = 20 # Max jitter diagnostic samples saved to disk.
110
+
111
+ # Observable accuracy targets (step 2 and step 3).
112
+ acc_tperc = 1e-2 # Relative accuracy goal on Tperc.
113
+ acc_tfinal = 1e-2 # Relative accuracy goal on Tfinal.
114
+ acc_rh = 1e-2 # Relative accuracy goal on RH.
115
+
116
+ # Strength-based validity flag.
117
+ weak_threshold = 1e-4 # Below this alpha at Tperc, the percolation result is flagged invalid.
118
+
119
+
120
+ class GWConf:
121
+ """Settings for calculating the gravitational-wave signal."""
122
+
123
+ use_mean_bubble_separation = True # If True, use mean bubble separation for the GW signal scale.
124
+ wall_velocity = "LTE" # "LTE", "c", "WallGo", or a float in (0, 1].
125
+ bw_collisions = "off" # Bubble-wall collisions: "off", "full" (kappa_phi=1), or "NLO".
126
+ turbulence = "off" # Turbulence contribution: "off" or a model key.
127
+ dilution = False # If True, apply a dilution factor to the GW signal.
128
+ equilibrium_DS = True # Assume the dark sector is in thermal equilibrium with the SM.
129
+ epsilon_turbulence = 0.1 # Energy fraction redirected into turbulence (0 disables).
130
+ sound_speed = "compute" # Sound-speed strategy: "compute" (broken-phase cs) or "1/3" (bag value).
131
+ coupled_hydrodynamics = True # Couple the dark and visible sectors in the hydro solve.
132
+ check_if_T0_global_min = False # If True, require the T=0 minimum to be global (else just warn).
133
+
134
+
135
+ class Configuration:
136
+ """Container holding one independent configuration object per subsystem."""
137
+
138
+ def __init__(self):
139
+ """Start every model with independent configuration objects."""
140
+ self.tracingConf = TracingConf()
141
+ self.gwConf = GWConf()
142
+ self.percolationConf = PercolationConf()
143
+
144
+
145
+ all_observables = {
146
+ "alpha": R"$\alpha$",
147
+ "alpha_theta": R"$\alpha_{\theta}$",
148
+ "alpha_thetabar": R"$\alpha_{\bar\theta}$",
149
+ "alpha_inf": R"$\alpha_\infty$",
150
+ "alpha_eq": R"$\alpha_\mathrm{eq}$",
151
+ "betaH_S3": R"$(\beta /H)_{S_3}$",
152
+ "betaH_RH": R"$(\beta /H)_{RH}$",
153
+ "RH": R"$RH$",
154
+ "Treh_SM_GeV": R"$T^{\mathrm{reh}}_{\mathrm{SM}}$ / $\mathrm{GeV}$",
155
+ "Tperc_SM_GeV": R"$T^{\mathrm{perc}}_{\mathrm{SM}}$ / $\mathrm{GeV}$",
156
+ "g_eff_tot_reh": R"$g^{\mathrm{reh}}_{\mathrm{tot}}$",
157
+ "h_eff_tot_reh": R"$h^{\mathrm{reh}}_{\mathrm{tot}}$",
158
+ "kappa_phi": R"$\kappa_{\phi}$",
159
+ "kappa_sw": R"$\kappa_{\mathrm{sw}}$",
160
+ "kappa_turb": R"$\kappa_{\mathrm{turb}}$",
161
+ "g0": R"$g_0$",
162
+ "h0": R"$h_0$",
163
+ "v_wall": R"$v_{\mathrm{wall}}$",
164
+ "D": R"$D$",
165
+ "c_s": R"$c_\mathrm{s}$",
166
+ "c_s_sym": R"$c_\mathrm{s}^\mathrm{sym}$",
167
+ "c_s_bro": R"$c_\mathrm{s}^\mathrm{bro}$",
168
+ "step": R"$\mathrm{step}$",
169
+ "total_steps": R"$\mathrm{total\ steps}$",
170
+ "Tnuc_SM_GeV": R"$T^{\mathrm{nuc}}_{\mathrm{SM}}$ / $\mathrm{GeV}$",
171
+ "Tcrit_SM_GeV": R"$T^{\mathrm{crit}}_{\mathrm{SM}}$ / $\mathrm{GeV}$",
172
+ "Tf_SM_GeV": R"$T^{\mathrm{final}}_{\mathrm{SM}}$ / $\mathrm{GeV}$",
173
+ "xi_crit": R"$\xi_{\mathrm{c}}$",
174
+ "WARNING:too_weak_to_compute_perc": R"$\mathrm{WARNING:} \mathrm{too\ weak\ to\ compute\ perc}$",
175
+ "WARNING:no_perc_splines": R"$\mathrm{WARNING:} \mathrm{no\ perc\ splines}$",
176
+ "WARNING:betaH_small": R"$\mathrm{WARNING:} (\beta/H)_{RH} \mathrm{\ too\ small}$",
177
+ "WARNING:betaH_very_small": R"$\mathrm{WARNING:} (\beta/H)_{RH} \mathrm{\ very\ small}$",
178
+ "WARNING:betaH_mismatch": R"$\mathrm{WARNING:} (\beta/H)_{S_3} \mathrm{\ vs\ } (\beta/H)_{RH} \mathrm{\ mismatch}$",
179
+ "WARNING:betaH_nonfinite": R"$\mathrm{WARNING:} (\beta/H)_{S_3} \mathrm{\ or\ } (\beta/H)_{RH} \mathrm{\ nonfinite}$",
180
+ "WARNING:nucleationRate_nonexponential": R"$\mathrm{WARNING:} \mathrm{nucleation\ rate\ nonexponential}$",
181
+ "WARNING:spline_tnuc_unavailable": R"$\mathrm{WARNING:} T_\mathrm{nuc} \mathrm{\ spline\ unavailable}$",
182
+ "WARNING:spline_tnuc_not_reached": R"$\mathrm{WARNING:} T_\mathrm{nuc} \mathrm{\ criterion\ not\ reached}$",
183
+ "WARNING:spline_tnuc_failed": R"$\mathrm{WARNING:} T_\mathrm{nuc} \mathrm{\ spline\ failed}$",
184
+ "WARNING:not_T0_global_min": R"$\mathrm{WARNING:} T=0 \mathrm{\ not\ global\ min}$",
185
+ }
@@ -0,0 +1,64 @@
1
+ """
2
+ Particle physics and cosmological quantities used across TransitionListener.
3
+
4
+ The numerical values listed here follow the PDG conventions and are expressed
5
+ in natural units whenever possible. Additional model-specific inputs (SM
6
+ fermion masses, gauge boson pole masses, Wolfenstein CKM parameters, ...)
7
+ are collected in dedicated containers so that individual models can reuse them.
8
+
9
+ Part of TransitionListener v2.0
10
+ Documentation: https://tasillo.de/TransitionListener/
11
+
12
+ Authors:
13
+ Jonas Matuszak <jonas.matuszak@kit.edu>
14
+ Carlo Tasillo <carlo.tasillo@ific.uv.es>
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import scipy.constants as sc
20
+
21
+ yr = sc.Julian_year #: One Julian year in seconds (:math:`365.25` days).
22
+ Mpl_GeV = 1.220910e19 #: Planck mass in GeV.
23
+ Omega_gamma_h2 = 2.473e-5 #: Photon energy density today in units of :math:`h^2`.
24
+ T0_SM_GeV = 2.35253655e-13 #: CMB temperature :math:`T_0 = 2.725\,\\mathrm{K}` in GeV.
25
+ GeV_Hz = 1.0 / 6.582119e-25 #: Conversion between GeV and Hz.
26
+ H100_Hz = 100 / 3.0857e19 #: Hubble constant :math:`H_0 = 100\,\\mathrm{km/s/Mpc}` in Hz.
27
+ Lambda = 2.533e-47 #: Dark-energy density :math:`\\rho_{\\Lambda}` in GeV^4.
28
+ fyr_Hz = 1 / yr #: Frequency corresponding to one inverse year in Hz.
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Standard Model inputs
32
+ # ---------------------------------------------------------------------------
33
+
34
+ SM_FERMION_MASSES_GEV = {
35
+ "m_e": 0.510998928e-3,
36
+ "m_mu": 0.1056583715,
37
+ "m_tau": 1.77682,
38
+ "m_u": 0.1,
39
+ "m_c": 1.51,
40
+ "m_t": 172.5,
41
+ "m_d": 0.1,
42
+ "m_s": 0.1,
43
+ "m_b": 4.92,
44
+ }
45
+ """Pole masses of the Standard Model fermions in GeV.
46
+
47
+ These values serve as the central inputs
48
+ for all Yukawa couplings appearing in TransitionListener models. They are
49
+ provided as a plain dictionary so that models can access them directly or feed
50
+ them into symbolic pipelines.
51
+ """
52
+
53
+ SM_GAUGE_MASSES_GEV = {
54
+ "mW": 80.385,
55
+ "mZ": 91.1876,
56
+ }
57
+ """Pole masses of the electroweak gauge bosons in GeV."""
58
+
59
+ CW_CONSTANTS = {
60
+ "scalar": 3.0 / 2.0,
61
+ "fermion": 3.0 / 2.0,
62
+ "gauge": 5.0 / 6.0,
63
+ }
64
+ """Coleman–Weinberg subtraction constants for scalars, fermions, and vectors."""
@@ -0,0 +1,230 @@
1
+ """
2
+ Counterterm and quantum-correction utilities.
3
+
4
+ This package bundles the Coleman–Weinberg tensor helpers with the registry and
5
+ solver infrastructure used to generate and apply model-specific counterterms.
6
+
7
+ Part of TransitionListener v2.0
8
+ Documentation: https://tasillo.de/TransitionListener/
9
+
10
+ Authors:
11
+ Jonas Matuszak <jonas.matuszak@kit.edu>
12
+ Carlo Tasillo <carlo.tasillo@ific.uv.es>
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass
18
+ from pathlib import Path
19
+ from typing import Any, Dict, Mapping, Tuple, Type
20
+
21
+ import importlib
22
+ import json
23
+ import pkgutil
24
+
25
+ from . import cw, tensors
26
+
27
+ __all__ = [
28
+ "cw",
29
+ "tensors",
30
+ "CounterTerms",
31
+ "CountertermGenerator",
32
+ "register_generator",
33
+ "get_generator",
34
+ "generate_counterterms",
35
+ ]
36
+
37
+
38
+ @dataclass(slots=True)
39
+ class CounterTerms:
40
+ """
41
+ Generic container for renormalisation counterterms.
42
+
43
+ Instead of hard-coding a particular model layout, the container stores an
44
+ arbitrary mapping from identifiers to floating-point values and exposes it
45
+ via attribute, item, and dictionary-style access. This keeps the 2HDM
46
+ interface backwards compatible while allowing other models to choose their
47
+ own naming convention.
48
+ """
49
+
50
+ values: Dict[str, float]
51
+
52
+ def __post_init__(self) -> None:
53
+ """Copy the supplied mapping so later mutations do not leak back to the caller."""
54
+ self.values = dict(self.values)
55
+
56
+ def __getattr__(self, name: str) -> float:
57
+ """Expose stored counterterms through attribute access."""
58
+ try:
59
+ return self.values[name]
60
+ except KeyError as exc:
61
+ raise AttributeError(f"Unknown counterterm '{name}'.") from exc
62
+
63
+ def __getitem__(self, key: str) -> float:
64
+ """Expose stored counterterms through dictionary-style indexing."""
65
+ return self.values[key]
66
+
67
+ def get(self, key: str, default: float | None = None) -> float | None:
68
+ """Return ``values[key]`` when present and ``default`` otherwise."""
69
+ return self.values.get(key, default)
70
+
71
+ def as_dict(self) -> Dict[str, float]:
72
+ """Return a shallow copy of the stored counterterms."""
73
+ return dict(self.values)
74
+
75
+ @classmethod
76
+ def from_dict(cls, mapping: Mapping[str, float]) -> "CounterTerms":
77
+ """Build a :class:`CounterTerms` instance from a dictionary."""
78
+ return cls(dict(mapping))
79
+
80
+ @classmethod
81
+ def from_kwargs(cls, **kwargs: float) -> "CounterTerms":
82
+ """Build a :class:`CounterTerms` instance from keyword arguments."""
83
+ return cls(dict(kwargs))
84
+
85
+
86
+ class CountertermGenerator:
87
+ """
88
+ Base class for SymPy-based counterterm dataset generators.
89
+
90
+ Subclasses must implement :meth:`build_dataset`, returning the metadata and
91
+ tensor dictionary that will be serialised to JSON.
92
+ """
93
+
94
+ model_key: str = "BaseModel"
95
+ output_filename: str = "counterterms.json"
96
+
97
+ def output_path(self) -> Path:
98
+ """Return the canonical location of the generated tensor JSON file."""
99
+ project_root = Path(__file__).resolve().parents[3]
100
+ return project_root / "models" / "generated" / self.output_filename
101
+
102
+ def build_dataset(self) -> Tuple[dict, dict]:
103
+ """Return ``(metadata, tensors)`` describing the generated counterterm dataset."""
104
+ raise NotImplementedError
105
+
106
+ def _convert_sympy(self, data: Any) -> Any:
107
+ """Recursively turn SymPy objects into JSON-serialisable structures."""
108
+ import sympy as sp
109
+
110
+ if isinstance(data, dict):
111
+ converted = {}
112
+ for key, value in data.items():
113
+ if isinstance(key, tuple):
114
+ key_str = str(tuple(key))
115
+ else:
116
+ key_str = str(key)
117
+ converted[key_str] = self._convert_sympy(value)
118
+ return converted
119
+ if isinstance(data, list):
120
+ return [self._convert_sympy(item) for item in data]
121
+ if isinstance(data, tuple):
122
+ return tuple(self._convert_sympy(item) for item in data)
123
+ if isinstance(data, sp.Basic):
124
+ return sp.srepr(data)
125
+ return data
126
+
127
+ def to_json(self) -> dict:
128
+ """Build the JSON payload with metadata and tensor expressions."""
129
+ metadata, tensors = self.build_dataset()
130
+ return {"metadata": metadata, "tensors": self._convert_sympy(tensors)}
131
+
132
+
133
+ _GENERATOR_REGISTRY: Dict[str, Type[CountertermGenerator]] = {}
134
+ _GENERATORS_LOADED = False
135
+
136
+
137
+ def _ensure_generators_loaded() -> None:
138
+ """Import all counterterm plug-ins so they can register themselves."""
139
+
140
+ global _GENERATORS_LOADED
141
+ if _GENERATORS_LOADED:
142
+ return
143
+
144
+ package_path = Path(__file__).resolve().parent
145
+ package_name = __name__
146
+ for module_info in pkgutil.iter_modules([str(package_path)]):
147
+ name = module_info.name
148
+ if name.startswith("_") or name == "__pycache__":
149
+ continue
150
+ importlib.import_module(f"{package_name}.{name}")
151
+
152
+ _GENERATORS_LOADED = True
153
+
154
+
155
+ def register_generator(generator_cls: Type[CountertermGenerator]) -> None:
156
+ """
157
+ Register a :class:`CountertermGenerator` subclass under its model key (and alias).
158
+
159
+ Parameters
160
+ ----------
161
+ generator_cls:
162
+ Concrete generator class providing ``model_key`` and optional ``alias``.
163
+ """
164
+
165
+ for key in (generator_cls.model_key, getattr(generator_cls, "alias", None)):
166
+ if key:
167
+ _GENERATOR_REGISTRY[key] = generator_cls
168
+
169
+
170
+ def get_generator(model_key: str) -> CountertermGenerator:
171
+ """
172
+ Retrieve the generator instance associated with ``model_key``.
173
+
174
+ Parameters
175
+ ----------
176
+ model_key:
177
+ Identifier supplied either as a fully-qualified module path or alias.
178
+
179
+ Raises
180
+ ------
181
+ KeyError
182
+ If no generator was registered for the provided key.
183
+ """
184
+
185
+ _ensure_generators_loaded()
186
+
187
+ try:
188
+ generator_cls = _GENERATOR_REGISTRY[model_key]
189
+ except KeyError as exc:
190
+ raise KeyError(
191
+ f"No counterterm generator registered for '{model_key}'."
192
+ ) from exc
193
+ return generator_cls()
194
+
195
+
196
+ def generate_counterterms(
197
+ model_key: str,
198
+ *,
199
+ force: bool = False,
200
+ output: Path | None = None,
201
+ ) -> Path:
202
+ """
203
+ Generate (or reuse) the curvature tensor dataset for the given model.
204
+
205
+ Parameters
206
+ ----------
207
+ model_key:
208
+ Identifier resolving to a registered generator class.
209
+ force:
210
+ If ``True`` the JSON file is recreated even when a previous version exists.
211
+ output:
212
+ Optional override for the output path. When omitted, the generator default
213
+ (``models/generated/<name>.json``) is used.
214
+
215
+ Returns
216
+ -------
217
+ Path
218
+ Filesystem path of the (re)generated JSON file.
219
+ """
220
+
221
+ generator = get_generator(model_key)
222
+ out_path = output or generator.output_path()
223
+ out_path.parent.mkdir(parents=True, exist_ok=True)
224
+ if out_path.exists() and not force:
225
+ return out_path
226
+
227
+ payload = generator.to_json()
228
+ with out_path.open("w", encoding="utf-8") as fh:
229
+ json.dump(payload, fh, indent=2)
230
+ return out_path