Skip to main content

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}