use crate::scale_factor::rk4_multi;
const TARGET_ABSOLUTE_ERROR: f64 = 1e-30;
const DEFAULT_DELTA_SCALE_FACTOR: f64 = 1e-4;
pub(crate) fn linear_growth_factor(omega_0: f64, omega_de: f64, z: f64) -> Result<f64, String> {
validate(omega_0, omega_de)?;
let omega_r = 1.0 - omega_0 - omega_de;
if contains(omega_0) {
if !contains(omega_de) && !contains(omega_r) {
Ok((1.0 + z).recip())
} else if contains(omega_de) && !contains(omega_r) {
let result = {
let ha = |a: f64| (omega_0 * a.powi(-3) + omega_de).sqrt();
let integrand = |a: f64| a.powi(-3) * ha(a).powi(-3);
let lower_a = 1e-6;
let upper_a = (1.0 + z).recip();
let prefactor = ha(upper_a) * 5.0 * omega_0 / 2.0;
let result = quadrature::clenshaw_curtis::integrate(
integrand,
lower_a,
upper_a,
TARGET_ABSOLUTE_ERROR,
);
prefactor * result.integral
};
Ok(result)
} else if !contains(omega_de) && contains(omega_r) {
let x = omega_0.recip() - 1.0;
let result = 1.0
+ 3.0 / x
+ 3.0 * ((1.0 + x) / x.powi(3)).sqrt() * ((1.0 + x).sqrt() - x.sqrt()).ln();
Ok(result)
} else if contains(omega_de) && contains(omega_r) {
let w = |_a: f64| -1.0;
let x = |a: f64| omega_0 * a.powi(-3) / (1.0 - omega_0 * a.powi(3));
let g_prime = |_a: f64, g_and_f: [f64; 2]| g_and_f[1];
let f_prime = |a: f64, g_and_f: [f64; 2]| {
let [g, f] = g_and_f;
-(7.0 / 2.0 - 3.0 / 2.0 * w(a) / (1.0 + x(a))) * f / a
- (1.0 - w(a)) / (1.0 + x(a)) * g / a.powi(2)
};
let derivatives =
|a: f64, g_and_f: [f64; 2]| [g_prime(a, g_and_f), f_prime(a, g_and_f)];
let mut current_g_and_f = [1.0, 0.0];
let mut current_a = 1e-5;
let target_a = (1.0 + z).recip();
while current_a < target_a {
let da = DEFAULT_DELTA_SCALE_FACTOR.min(target_a - current_a);
current_g_and_f = rk4_multi(derivatives, current_a, current_g_and_f, da);
current_a += da;
}
let [final_g, _] = current_g_and_f;
let growth_factor = final_g * (1.0 + z).recip();
Ok(growth_factor)
} else {
unreachable!("all cases explicitly outlined for clarity")
}
} else {
Ok(0.0)
}
}
fn contains(a: f64) -> bool {
a.is_sign_positive() && a.is_normal()
}
fn validate(omega_0: f64, omega_de: f64) -> Result<(), String> {
let is_valid = { omega_0 >= 0.0 && omega_de >= 0.0 && (omega_0 + omega_de) <= 1.0 };
if is_valid {
Ok(())
} else {
Err("The specified cosmological parameters are not valid. Only flat universe are supported".to_string())
}
}