lcdm_core/spec.rs
1// lcdm-core/src/spec.rs
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6/// High-level specification for the neutrino model.
7///
8/// This enum is designed to prevent double-counting between massless (radiation-like)
9/// and massive (non-cold dark matter) neutrino components.
10#[derive(Debug, Clone)]
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12pub enum NeutrinoSpec {
13 /// Effective model (recommended):
14 ///
15 /// - `masses_ev` enumerates the explicit species being modeled (may include 0.0).
16 /// - `n_eff` rescales the thermal bath so the total relativistic energy density matches
17 /// the desired effective number of species.
18 ///
19 /// In this model, the neutrino background is represented by a single effective temperature
20 /// derived from `n_eff` and the number of explicit species.
21 Effective {
22 /// Effective number of relativistic species (e.g., 3.046).
23 n_eff: f64,
24 /// Neutrino masses in eV (length N; may include 0.0 for massless species).
25 masses_ev: Vec<f64>,
26 },
27
28 /// Split model (CLASS-style):
29 ///
30 /// Massless (ultra-relativistic) and massive (ncdm) components are strictly separated:
31 /// - `n_ur` represents the massless-only contribution.
32 /// - `masses_ev` represents massive species only (should typically be strictly m > 0).
33 /// - `temp_factors` allows per-species temperature scaling relative to a base temperature.
34 Split {
35 /// Effective number of massless (UR) species (e.g., 3.046 if no massive species).
36 n_ur: f64,
37 /// Masses (eV) for the massive (ncdm) species (length N_ncdm).
38 masses_ev: Vec<f64>,
39 /// Temperature ratios T_i / T_base for each massive species (typically all 1.0).
40 temp_factors: Vec<f64>,
41 },
42}
43
44/// Accuracy presets for numerical routines (table sizes, integration steps, etc.).
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47pub enum AccuracyPreset {
48 /// Prioritize speed (smaller tables / fewer integration steps).
49 Fast,
50 /// Balanced default settings.
51 #[default]
52 Default,
53 /// Higher precision (larger tables / more integration steps).
54 Precise,
55 /// Research/paper-grade settings (very large tables / many integration steps).
56 Paper,
57}
58
59impl AccuracyPreset {
60 /// Number of tabulation points for the neutrino background table.
61 pub fn neutrino_n_points(&self) -> usize {
62 match self {
63 Self::Fast => 256,
64 Self::Default => 512,
65 Self::Precise => 1024,
66 Self::Paper => 2048,
67 }
68 }
69
70 /// Composite Simpson integration step count for neutrino integrals.
71 pub fn neutrino_integ_steps(&self) -> usize {
72 match self {
73 Self::Fast => 600,
74 Self::Default => 1200,
75 Self::Precise => 2000,
76 Self::Paper => 4000,
77 }
78 }
79
80 /// Upper cutoff for the dimensionless momentum integral (y_max).
81 pub fn neutrino_y_max(&self) -> f64 {
82 match self {
83 Self::Fast => 35.0_f64,
84 Self::Default => 45.0_f64,
85 Self::Precise => 50.0_f64,
86 Self::Paper => 60.0_f64,
87 }
88 }
89}
90
91/// Single source of truth for cosmological composition at z = 0.
92///
93/// This struct contains only **physical densities** (ω ≡ Ω h²) and derived
94/// temperatures. Any hot-path computations (E(z), distances, growth, etc.)
95/// should use values from here to avoid divergence across components.
96#[derive(Debug, Clone)]
97#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
98pub struct DerivedParams {
99 // --- Basic echo ---
100 /// Dimensionless Hubble parameter h.
101 pub h: f64,
102 /// h² (cached).
103 pub h2: f64,
104 /// CMB temperature today in Kelvin.
105 pub t_cmb0_k: f64,
106
107 // --- Neutrino temperatures ---
108 /// Base neutrino temperature:
109 /// T_ν0,base = (4/11)^(1/3) * T_cmb0
110 ///
111 /// This is the background temperature used for the Split model.
112 pub t_nu0_base_k: f64,
113 /// Effective neutrino temperature used for the Effective model.
114 ///
115 /// This scales the bath by (N_eff / N_species)^(1/4) so the total relativistic
116 /// energy density matches `n_eff`.
117 pub t_nu0_k: f64,
118
119 // --- Physical densities (z = 0) ---
120 /// Physical baryon density ω_b0.
121 pub omega_b0: f64,
122 /// Physical cold dark matter density ω_cdm0.
123 pub omega_cdm0: f64,
124 /// Total matter physical density ω_m0 = ω_b0 + ω_cdm0.
125 pub omega_m0: f64,
126
127 /// Physical curvature density ω_k0.
128 pub omega_k0: f64,
129 /// Physical dark energy density ω_de0 (Λ in ΛCDM).
130 pub omega_de0: f64,
131
132 /// Physical photon density ω_γ0.
133 pub omega_g0: f64,
134
135 // --- Neutrino components (diagnostics / split info) ---
136 /// Massless-only (UR) contribution used in the Split model; 0.0 in Effective mode.
137 pub omega_ur0: f64,
138 /// Total neutrino physical density ω_ν0 (massive + any species included via the model).
139 pub omega_nu0: f64,
140
141 // --- Sums ---
142 /// Sum of present-day physical densities for photons + UR + neutrinos.
143 ///
144 /// Note: this does not imply strict (1+z)^4 scaling for the massive part at all z.
145 pub omega_gnu0: f64,
146
147 /// Sum of all non-dark-energy components today:
148 /// ω_non_de0 = ω_γ0 + ω_ur0 + ω_ν0 + ω_m0 + ω_k0
149 pub omega_non_de0: f64,
150}
151
152impl DerivedParams {
153 /// Construct `DerivedParams` and precompute cached sums.
154 pub fn new(
155 h: f64,
156 t_cmb0: f64,
157 omega_b: f64,
158 omega_cdm: f64,
159 omega_k: f64,
160 omega_de: f64,
161 omega_g: f64,
162 omega_ur: f64,
163 omega_nu: f64,
164 t_nu0_base: f64,
165 t_nu0: f64,
166 ) -> Self {
167 let h2 = h * h;
168 let omega_m = omega_b + omega_cdm;
169 let omega_non_de = omega_g + omega_ur + omega_nu + omega_m + omega_k;
170
171 Self {
172 h,
173 h2,
174 t_cmb0_k: t_cmb0,
175 t_nu0_base_k: t_nu0_base,
176 t_nu0_k: t_nu0,
177 omega_b0: omega_b,
178 omega_cdm0: omega_cdm,
179 omega_m0: omega_m,
180 omega_k0: omega_k,
181 omega_de0: omega_de,
182 omega_g0: omega_g,
183 omega_ur0: omega_ur,
184 omega_nu0: omega_nu,
185 omega_gnu0: omega_g + omega_ur + omega_nu,
186 omega_non_de0: omega_non_de,
187 }
188 }
189}