TransitionListener 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- transitionlistener/__init__.py +21 -0
- transitionlistener/bubbledynamics.py +2250 -0
- transitionlistener/bubbledynamics_fixedstep.py +2929 -0
- transitionlistener/check_installation.py +309 -0
- transitionlistener/ckm.py +91 -0
- transitionlistener/colors.py +288 -0
- transitionlistener/config.py +185 -0
- transitionlistener/constants.py +64 -0
- transitionlistener/counterterms/__init__.py +230 -0
- transitionlistener/counterterms/cw.py +298 -0
- transitionlistener/counterterms/tensors.py +343 -0
- transitionlistener/counterterms/twohdm.py +520 -0
- transitionlistener/counterterms/twohdm_solver.py +374 -0
- transitionlistener/errors.py +315 -0
- transitionlistener/examples/bp_et.yaml +64 -0
- transitionlistener/examples/bp_lisa.yaml +70 -0
- transitionlistener/examples/bp_muares.yaml +69 -0
- transitionlistener/examples/bp_pta.yaml +64 -0
- transitionlistener/examples/example_dark_photon.yaml +73 -0
- transitionlistener/examples/example_grid.yaml +55 -0
- transitionlistener/examples/example_line.yaml +51 -0
- transitionlistener/examples/example_point.yaml +73 -0
- transitionlistener/examples/example_random_conformal.yaml +42 -0
- transitionlistener/examples/example_ultranest.yaml +39 -0
- transitionlistener/finiteT.py +468 -0
- transitionlistener/generic_potential.py +1521 -0
- transitionlistener/gridplots.py +592 -0
- transitionlistener/gwfopt.py +604 -0
- transitionlistener/helper_functions.py +843 -0
- transitionlistener/hydrodynamics.py +647 -0
- transitionlistener/interface/__init__.py +95 -0
- transitionlistener/interface/check.py +51 -0
- transitionlistener/interface/cli.py +211 -0
- transitionlistener/interface/config_loader.py +264 -0
- transitionlistener/interface/logging_utils.py +30 -0
- transitionlistener/interface/output_schema.py +296 -0
- transitionlistener/interface/pipeline.py +300 -0
- transitionlistener/interface/samplers.py +565 -0
- transitionlistener/interface/scans.py +468 -0
- transitionlistener/interface/single_point.py +581 -0
- transitionlistener/interface/state.py +61 -0
- transitionlistener/lineplots.py +597 -0
- transitionlistener/logo/TL-logo_large.png +0 -0
- transitionlistener/logo/TL-logo_small.png +0 -0
- transitionlistener/models/TL_2HDM.py +512 -0
- transitionlistener/models/TL_conformal_dark_u1.py +202 -0
- transitionlistener/models/TL_dark_U1.py +198 -0
- transitionlistener/models/TL_dark_U1_g_parameterization.py +198 -0
- transitionlistener/models/TL_dark_flipflop.py +274 -0
- transitionlistener/models/templatePotential.py +260 -0
- transitionlistener/nucleation.py +412 -0
- transitionlistener/observability.py +790 -0
- transitionlistener/particles.py +436 -0
- transitionlistener/pathDeformation.py +1462 -0
- transitionlistener/percolation_adaptive_gridbuilders.py +1385 -0
- transitionlistener/percolation_adaptive_rate.py +1049 -0
- transitionlistener/percolation_adaptivestepsize.py +1632 -0
- transitionlistener/percolation_fixedstepsize.py +618 -0
- transitionlistener/phases.py +1499 -0
- transitionlistener/plot_settings.py +121 -0
- transitionlistener/plots.py +1781 -0
- transitionlistener/runtime_options.py +278 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/bandwidths.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/density.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/freqs.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log.txt +16 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log10rhogrid.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log10rholabels.txt +30 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/pulsar_list.txt +1 -0
- transitionlistener/tab_data/HLV-O2-PLI.csv +82 -0
- transitionlistener/tab_data/HLV-O2-noise.csv +83 -0
- transitionlistener/tab_data/HLVK-design-PLI.csv +61 -0
- transitionlistener/tab_data/HLVK-design-noise.csv +83 -0
- transitionlistener/tab_data/NG15_PLI_hc.csv +31 -0
- transitionlistener/tab_data/NG15_noise_hc.csv +56 -0
- transitionlistener/tab_data/PLI-dataset.csv +327 -0
- transitionlistener/tab_data/energydensities.dat +1001 -0
- transitionlistener/tab_data/finiteT_b.dat.txt +1999 -0
- transitionlistener/tab_data/finiteT_f.dat.txt +1000 -0
- transitionlistener/tab_data/geff_SM.dat +10009 -0
- transitionlistener/tab_data/muAres_f_Hz_PLI_h2OmegaGW.csv +131 -0
- transitionlistener/tab_data/muAres_f_Hz_h2Omega_noise.csv +99 -0
- transitionlistener/tab_data/noise-dataset.csv +327 -0
- transitionlistener/tab_data/numberdensities.dat +1001 -0
- transitionlistener/tab_data/pressures.dat +1001 -0
- transitionlistener/thermodynamics.py +686 -0
- transitionlistener/transitionObservables.py +1199 -0
- transitionlistener/transitionObservables_fixedstep.py +976 -0
- transitionlistener/transitions.py +1000 -0
- transitionlistener/tunneling1D.py +910 -0
- transitionlistener-2.0.0.dist-info/METADATA +817 -0
- transitionlistener-2.0.0.dist-info/RECORD +95 -0
- transitionlistener-2.0.0.dist-info/WHEEL +4 -0
- transitionlistener-2.0.0.dist-info/entry_points.txt +2 -0
- transitionlistener-2.0.0.dist-info/licenses/LICENSE.txt +674 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Particle and mass spectrum utilities with concrete particle species.
|
|
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 __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import Callable, Iterable, List, Optional, Sequence, Tuple, Union
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
ThermalPrefactor = float | Callable[[np.ndarray], np.ndarray]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _ensure_callable(name: str, fn):
|
|
23
|
+
"""Return ``fn`` unchanged when callable and raise a targeted error otherwise."""
|
|
24
|
+
if fn is None or callable(fn):
|
|
25
|
+
return fn
|
|
26
|
+
raise TypeError(f"{name} must be callable or None.")
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class Scalar:
|
|
30
|
+
"""Structure to represent scalars"""
|
|
31
|
+
name: str
|
|
32
|
+
latex_name: str
|
|
33
|
+
dof: float
|
|
34
|
+
is_SM: bool
|
|
35
|
+
gauge_coupling: float = 0.0
|
|
36
|
+
is_physical: bool = True
|
|
37
|
+
c: float = 1.5
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class Goldstone:
|
|
41
|
+
"""Structure to represent Golstone bosons"""
|
|
42
|
+
name: str
|
|
43
|
+
latex_name: str
|
|
44
|
+
dof: float
|
|
45
|
+
is_SM: bool
|
|
46
|
+
gauge_coupling: float = 0.0
|
|
47
|
+
is_physical: bool = False
|
|
48
|
+
c: float = 1.5
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class GaugeBoson:
|
|
53
|
+
"""Structure to represent gauge bosons"""
|
|
54
|
+
name: str
|
|
55
|
+
latex_name: str
|
|
56
|
+
dof: float
|
|
57
|
+
is_SM: bool
|
|
58
|
+
gauge_coupling: float = 0.0
|
|
59
|
+
is_physical = True
|
|
60
|
+
c: float = 5.0/6.0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class Fermion:
|
|
65
|
+
"""Structure to represent fermions."""
|
|
66
|
+
name: str
|
|
67
|
+
latex_name: str
|
|
68
|
+
dof: float
|
|
69
|
+
is_SM: bool
|
|
70
|
+
c: float = 1.5
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class MassSpectrum:
|
|
74
|
+
"""Container describing the particle content and mass functions of a model.
|
|
75
|
+
|
|
76
|
+
The class keeps the legacy ``CosmoTransitions``-style tuple interface used
|
|
77
|
+
throughout TransitionListener. It stores particle metadata such as degrees
|
|
78
|
+
of freedom and renormalisation constants alongside callables returning the
|
|
79
|
+
field- and temperature-dependent mass eigenvalues.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, scalars, gaugeBosons=[], fermions=[],
|
|
83
|
+
boson_massSq_fn=None, fermion_massSq_fn=None):
|
|
84
|
+
r"""Register the bosonic and fermionic sectors of the model.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
scalars, gaugeBosons, fermions:
|
|
89
|
+
Particle descriptors providing names, degrees of freedom, and the
|
|
90
|
+
renormalization constant ``c`` used in the one-loop effective
|
|
91
|
+
potential for both bosons and fermions.
|
|
92
|
+
boson_massSq_fn:
|
|
93
|
+
Callable returning the bosonic mass-squared spectrum
|
|
94
|
+
:math:`m_i^2(\phi, T)`.
|
|
95
|
+
fermion_massSq_fn:
|
|
96
|
+
Callable returning the fermionic mass-squared spectrum
|
|
97
|
+
:math:`m_f^2(\phi)`.
|
|
98
|
+
"""
|
|
99
|
+
self.Nscalars = len(scalars)
|
|
100
|
+
self.NgaugeBosons = len(gaugeBosons)
|
|
101
|
+
self.Nbosons = self.Nscalars + self.NgaugeBosons
|
|
102
|
+
self.Nfermions = len(fermions)
|
|
103
|
+
self.bosons = scalars + gaugeBosons
|
|
104
|
+
self.fermions = fermions
|
|
105
|
+
|
|
106
|
+
# Necessary for correct DOFs
|
|
107
|
+
self.number_gauge_bosons = self.calc_N_gauge_bosons(gaugeBosons)
|
|
108
|
+
|
|
109
|
+
self.boson_massSq_fn = self.set_bmass_fn(boson_massSq_fn)
|
|
110
|
+
self.fermion_massSq_fn = self.set_fmass_fn(fermion_massSq_fn)
|
|
111
|
+
|
|
112
|
+
if fermions == []:
|
|
113
|
+
self.dof_fermions = np.array([0.0])
|
|
114
|
+
self.c_fermions = np.array([3./2.])
|
|
115
|
+
self.is_SM_fermions = np.array([False])
|
|
116
|
+
else:
|
|
117
|
+
self.dof_fermions = np.array([particle.dof for particle in self.fermions])
|
|
118
|
+
self.c_fermions = np.array([particle.c for particle in self.fermions])
|
|
119
|
+
self.is_SM_fermions = np.array([particle.is_SM for particle in self.fermions])
|
|
120
|
+
|
|
121
|
+
self.dof_bosons = np.array([particle.dof for particle in self.bosons])
|
|
122
|
+
self.c_bosons = np.array([particle.c for particle in self.bosons])
|
|
123
|
+
self.is_physical_bosons = np.array([particle.is_physical for particle in self.bosons])
|
|
124
|
+
self.is_SM_bosons = np.array([particle.is_SM for particle in self.bosons])
|
|
125
|
+
|
|
126
|
+
self.boson_gauge_couplings = np.zeros(self.Nbosons)
|
|
127
|
+
for i in range(self.Nbosons):
|
|
128
|
+
self.boson_gauge_couplings[i] = self.bosons[i].gauge_coupling
|
|
129
|
+
|
|
130
|
+
def calc_N_gauge_bosons(self, gaugeBosons):
|
|
131
|
+
"""Infer the number of vector bosons from the total bosonic degrees of freedom."""
|
|
132
|
+
dof = 0
|
|
133
|
+
for gb in gaugeBosons:
|
|
134
|
+
dof += gb.dof
|
|
135
|
+
|
|
136
|
+
nGBosons = int(dof/3)
|
|
137
|
+
resid = dof % 3
|
|
138
|
+
if resid != 0:
|
|
139
|
+
raise Exception("DOF of bosons does not add up!")
|
|
140
|
+
return nGBosons
|
|
141
|
+
|
|
142
|
+
def boson_labels(self, type: str):
|
|
143
|
+
"""Return boson names either in plain text or in LaTeX form."""
|
|
144
|
+
if type == "latex":
|
|
145
|
+
return [p.latex_name for p in self.bosons]
|
|
146
|
+
elif type == "text":
|
|
147
|
+
return [p.name for p in self.bosons]
|
|
148
|
+
else:
|
|
149
|
+
raise Exception("Wrong label type supplied.")
|
|
150
|
+
|
|
151
|
+
def fermion_labels(self, type: str):
|
|
152
|
+
"""Return fermion names either in plain text or in LaTeX form."""
|
|
153
|
+
if type == "latex":
|
|
154
|
+
return [p.latex_name for p in self.fermions]
|
|
155
|
+
elif type == "text":
|
|
156
|
+
return [p.name for p in self.fermions]
|
|
157
|
+
else:
|
|
158
|
+
raise Exception("Wrong label type supplied.")
|
|
159
|
+
|
|
160
|
+
def set_bmass_fn(self, bmass_fn):
|
|
161
|
+
"""Normalise the bosonic mass callback and supply a massless default when absent."""
|
|
162
|
+
if bmass_fn is None:
|
|
163
|
+
def fn(X, T):
|
|
164
|
+
X = np.asarray(X)
|
|
165
|
+
M2 = np.zeros(X[...,0].shape + (1, ))
|
|
166
|
+
return M2
|
|
167
|
+
return fn
|
|
168
|
+
return bmass_fn
|
|
169
|
+
|
|
170
|
+
def set_fmass_fn(self, fmass_fn):
|
|
171
|
+
"""Normalise the fermionic mass callback and supply a massless default when absent."""
|
|
172
|
+
if fmass_fn is None:
|
|
173
|
+
def fn(X):
|
|
174
|
+
X = np.asarray(X)
|
|
175
|
+
M2 = np.zeros(X[...,0].shape + (1, ))
|
|
176
|
+
return M2
|
|
177
|
+
return fn
|
|
178
|
+
return fmass_fn
|
|
179
|
+
|
|
180
|
+
def bosons_massSq(self, X: np.ndarray, T: float):
|
|
181
|
+
r"""Return the bosonic masses entering the one-loop effective potential.
|
|
182
|
+
|
|
183
|
+
The returned tuple is interpreted as ``(m_i^2, n_i, c_i, physical_i)``,
|
|
184
|
+
where :math:`m_i^2(\phi, T)` are the bosonic mass eigenvalues,
|
|
185
|
+
:math:`n_i` their degrees of freedom, and :math:`c_i` the
|
|
186
|
+
renormalisation constants used in
|
|
187
|
+
|
|
188
|
+
.. math::
|
|
189
|
+
V_1^{B}(\phi, T=0)
|
|
190
|
+
= \sum_i \frac{n_i\, m_i^4(\phi, 0)}{64\pi^2}
|
|
191
|
+
\left[\log\!\left(\frac{m_i^2(\phi, 0)}{\mu^2}\right) - c_i\right].
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
X:
|
|
196
|
+
Field configuration :math:`\phi`.
|
|
197
|
+
T:
|
|
198
|
+
Temperature in the internal TransitionListener units.
|
|
199
|
+
|
|
200
|
+
Returns
|
|
201
|
+
-------
|
|
202
|
+
tuple
|
|
203
|
+
``(masses_sq, dof, c_constants, is_physical)``.
|
|
204
|
+
"""
|
|
205
|
+
M2 = self.boson_massSq_fn(X, T)
|
|
206
|
+
return M2, self.dof_bosons, self.c_bosons, self.is_physical_bosons
|
|
207
|
+
|
|
208
|
+
def fermion_massSq(self, X: np.ndarray):
|
|
209
|
+
r"""Return the fermionic masses entering the one-loop effective potential.
|
|
210
|
+
|
|
211
|
+
The returned tuple is interpreted as ``(m_f^2, n_f)`` and is used in
|
|
212
|
+
|
|
213
|
+
.. math::
|
|
214
|
+
V_1^{F}(\phi)
|
|
215
|
+
= -\sum_f \frac{n_f\, m_f^4(\phi)}{64\pi^2}
|
|
216
|
+
\left[\log\!\left(\frac{m_f^2(\phi)}{\mu^2}\right) - \tfrac{3}{2}\right].
|
|
217
|
+
|
|
218
|
+
Parameters
|
|
219
|
+
----------
|
|
220
|
+
X:
|
|
221
|
+
Field configuration :math:`\phi`.
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
tuple
|
|
226
|
+
``(masses_sq, dof)``.
|
|
227
|
+
"""
|
|
228
|
+
M2 = self.fermion_massSq_fn(X)
|
|
229
|
+
return M2, self.dof_fermions
|
|
230
|
+
|
|
231
|
+
def get_gauge_coupling(self):
|
|
232
|
+
"""Return the per-boson gauge couplings associated with the stored spectrum."""
|
|
233
|
+
return self.boson_gauge_couplings
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@dataclass
|
|
237
|
+
class BaseParticle:
|
|
238
|
+
"""Shared particle descriptor used by the newer block-based spectrum API."""
|
|
239
|
+
|
|
240
|
+
name: str
|
|
241
|
+
latex_name: str
|
|
242
|
+
dof: float
|
|
243
|
+
is_SM: bool
|
|
244
|
+
c: float
|
|
245
|
+
gauge_coupling: float = 0.0
|
|
246
|
+
thermal_prefactor: ThermalPrefactor = None
|
|
247
|
+
hard_thermal_mass_fn: Optional[Callable[[np.ndarray, float], np.ndarray]] = None
|
|
248
|
+
metadata: dict = field(default_factory=dict)
|
|
249
|
+
|
|
250
|
+
def __post_init__(self) -> None:
|
|
251
|
+
"""Validate the basic particle metadata and normalise optional callables."""
|
|
252
|
+
if self.dof < 0:
|
|
253
|
+
raise ValueError("Particle degrees of freedom must be non-negative.")
|
|
254
|
+
self.hard_thermal_mass_fn = _ensure_callable(
|
|
255
|
+
f"hard_thermal_mass_fn for {self.name}", self.hard_thermal_mass_fn
|
|
256
|
+
)
|
|
257
|
+
if self.thermal_prefactor is not None and not callable(self.thermal_prefactor):
|
|
258
|
+
value = float(self.thermal_prefactor)
|
|
259
|
+
self.thermal_prefactor = lambda X, value=value: np.array(value, dtype=float)
|
|
260
|
+
|
|
261
|
+
def evaluate_prefactor(self, X: np.ndarray) -> Optional[np.ndarray]:
|
|
262
|
+
"""Evaluate the optional thermal prefactor at field point ``X``."""
|
|
263
|
+
if self.thermal_prefactor is None:
|
|
264
|
+
return None
|
|
265
|
+
return np.asarray(self.thermal_prefactor(np.asarray(X)), dtype=float)
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def is_BSM(self) -> bool:
|
|
269
|
+
"""Whether the particle belongs to the beyond-the-Standard-Model sector."""
|
|
270
|
+
return not self.is_SM
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def statistic(self) -> str:
|
|
274
|
+
"""Bosonic/fermionic label implemented by concrete subclasses."""
|
|
275
|
+
raise NotImplementedError
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def is_physical(self) -> bool:
|
|
279
|
+
"""Whether the particle should contribute as a physical excitation."""
|
|
280
|
+
return True
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@dataclass
|
|
284
|
+
class BosonSector:
|
|
285
|
+
"""Evaluated bosonic spectrum together with particle metadata."""
|
|
286
|
+
|
|
287
|
+
masses_sq: np.ndarray
|
|
288
|
+
dof: np.ndarray
|
|
289
|
+
c: np.ndarray
|
|
290
|
+
is_physical: np.ndarray
|
|
291
|
+
gauge_coupling: np.ndarray
|
|
292
|
+
is_bsm: np.ndarray
|
|
293
|
+
particles: Sequence[BaseParticle]
|
|
294
|
+
thermal_masses_sq: Optional[np.ndarray] = None
|
|
295
|
+
|
|
296
|
+
def as_tuple(self) -> Tuple[np.ndarray, ...]:
|
|
297
|
+
"""Return the legacy tuple representation used by older callers."""
|
|
298
|
+
return (
|
|
299
|
+
self.masses_sq,
|
|
300
|
+
self.dof,
|
|
301
|
+
self.c,
|
|
302
|
+
self.is_physical,
|
|
303
|
+
self.gauge_coupling,
|
|
304
|
+
self.is_bsm,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def latex_labels(self) -> List[str]:
|
|
309
|
+
"""LaTeX labels for each bosonic degree of freedom."""
|
|
310
|
+
return [p.latex_name for p in self.particles]
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
def text_labels(self) -> List[str]:
|
|
314
|
+
"""Plain-text labels for each bosonic degree of freedom."""
|
|
315
|
+
return [p.name for p in self.particles]
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@dataclass
|
|
319
|
+
class FermionSector:
|
|
320
|
+
"""Evaluated fermionic spectrum together with particle metadata."""
|
|
321
|
+
|
|
322
|
+
masses_sq: np.ndarray
|
|
323
|
+
dof: np.ndarray
|
|
324
|
+
is_bsm: np.ndarray
|
|
325
|
+
particles: Sequence[BaseParticle]
|
|
326
|
+
|
|
327
|
+
def as_tuple(self) -> Tuple[np.ndarray, ...]:
|
|
328
|
+
"""Return the legacy tuple representation used by older callers."""
|
|
329
|
+
return (self.masses_sq, self.dof, self.is_bsm)
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def latex_labels(self) -> List[str]:
|
|
333
|
+
"""LaTeX labels for each fermionic degree of freedom."""
|
|
334
|
+
return [p.latex_name for p in self.particles]
|
|
335
|
+
|
|
336
|
+
@property
|
|
337
|
+
def text_labels(self) -> List[str]:
|
|
338
|
+
"""Plain-text labels for each fermionic degree of freedom."""
|
|
339
|
+
return [p.name for p in self.particles]
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@dataclass
|
|
343
|
+
class SpectrumSnapshot:
|
|
344
|
+
"""Simultaneous bosonic and fermionic spectrum evaluation at a given state."""
|
|
345
|
+
|
|
346
|
+
bosons: BosonSector
|
|
347
|
+
fermions: FermionSector
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
@dataclass
|
|
351
|
+
class MassBlock:
|
|
352
|
+
"""Group of particles sharing one mass function and, optionally, one thermal correction."""
|
|
353
|
+
|
|
354
|
+
particles: Sequence[BaseParticle]
|
|
355
|
+
mass_function: Callable[[np.ndarray, float], np.ndarray]
|
|
356
|
+
hard_thermal_mass_function: Optional[Callable[[np.ndarray, float], np.ndarray]] = None
|
|
357
|
+
label: Optional[str] = None
|
|
358
|
+
|
|
359
|
+
def __post_init__(self) -> None:
|
|
360
|
+
"""Validate particle statistics and normalise the supplied callables."""
|
|
361
|
+
if not self.particles:
|
|
362
|
+
raise ValueError("MassBlock requires at least one particle.")
|
|
363
|
+
stats = {p.statistic for p in self.particles}
|
|
364
|
+
if len(stats) != 1:
|
|
365
|
+
raise ValueError("All particles in a MassBlock must share the same statistic.")
|
|
366
|
+
self.particles = list(self.particles)
|
|
367
|
+
self.mass_function = _ensure_callable("mass_function", self.mass_function)
|
|
368
|
+
self.hard_thermal_mass_function = _ensure_callable(
|
|
369
|
+
"hard_thermal_mass_function", self.hard_thermal_mass_function
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def statistic(self) -> str:
|
|
374
|
+
"""Bosonic/fermionic statistic shared by every particle in the block."""
|
|
375
|
+
return self.particles[0].statistic
|
|
376
|
+
|
|
377
|
+
def evaluate(self, X: np.ndarray, T: float) -> np.ndarray:
|
|
378
|
+
"""Evaluate the block mass function and align its trailing axis with ``particles``."""
|
|
379
|
+
masses = np.asarray(self.mass_function(X, T))
|
|
380
|
+
if masses.shape[-1] != len(self.particles):
|
|
381
|
+
if masses.shape[0] == len(self.particles):
|
|
382
|
+
masses = np.moveaxis(masses, 0, -1)
|
|
383
|
+
if masses.shape[-1] != len(self.particles):
|
|
384
|
+
raise ValueError(
|
|
385
|
+
f"Mass function for block {self.label!r} returned shape {masses.shape}, "
|
|
386
|
+
f"expected trailing dimension {len(self.particles)}."
|
|
387
|
+
)
|
|
388
|
+
return masses
|
|
389
|
+
|
|
390
|
+
def evaluate_thermal(self, X: np.ndarray, T: float) -> Optional[np.ndarray]:
|
|
391
|
+
r"""Evaluate hard thermal masses or ``c_i T^2``-style prefactors for the block.
|
|
392
|
+
|
|
393
|
+
When no explicit thermal-mass callback is provided, the function falls
|
|
394
|
+
back to per-particle prefactors and returns
|
|
395
|
+
|
|
396
|
+
.. math::
|
|
397
|
+
\Pi_i(\phi, T) = c_i(\phi)\, T^2.
|
|
398
|
+
"""
|
|
399
|
+
if self.hard_thermal_mass_function is not None:
|
|
400
|
+
thermal = np.asarray(self.hard_thermal_mass_function(X, T))
|
|
401
|
+
if thermal.shape[-1] != len(self.particles):
|
|
402
|
+
if thermal.shape[0] == len(self.particles):
|
|
403
|
+
thermal = np.moveaxis(thermal, 0, -1)
|
|
404
|
+
if thermal.shape[-1] != len(self.particles):
|
|
405
|
+
raise ValueError(
|
|
406
|
+
f"Thermal mass function for block {self.label!r} returned shape {thermal.shape}, "
|
|
407
|
+
f"expected trailing dimension {len(self.particles)}."
|
|
408
|
+
)
|
|
409
|
+
return thermal
|
|
410
|
+
|
|
411
|
+
prefactors = []
|
|
412
|
+
has_prefactor = False
|
|
413
|
+
for particle in self.particles:
|
|
414
|
+
pref = particle.evaluate_prefactor(X)
|
|
415
|
+
if pref is None:
|
|
416
|
+
pref = 0.0
|
|
417
|
+
else:
|
|
418
|
+
has_prefactor = True
|
|
419
|
+
prefactors.append(np.asarray(pref, dtype=float))
|
|
420
|
+
if not has_prefactor:
|
|
421
|
+
return None
|
|
422
|
+
pref_array = np.stack(prefactors, axis=-1)
|
|
423
|
+
T_arr = np.asarray(T, dtype=float)
|
|
424
|
+
return pref_array * (np.square(T_arr)[..., np.newaxis])
|
|
425
|
+
|
|
426
|
+
__all__ = [
|
|
427
|
+
"Scalar",
|
|
428
|
+
"Goldstone",
|
|
429
|
+
"GaugeBoson",
|
|
430
|
+
"Fermion",
|
|
431
|
+
"MassSpectrum",
|
|
432
|
+
"MassBlock",
|
|
433
|
+
"BosonSector",
|
|
434
|
+
"FermionSector",
|
|
435
|
+
"SpectrumSnapshot",
|
|
436
|
+
]
|