use crate::power::{PowerFn, PowerSpectrum};
use ouroboros::self_referencing;
use quadrature::Output;
use std::error::Error;
use std::f64::consts::PI;
use std::ops::Add;
pub const CORR_LOGK_MIN: f64 = -6.0;
pub const CORR_LOGK_MAX: f64 = 6.0;
pub const CORR_ABS_ERROR: f64 = 1e-10;
#[self_referencing]
pub struct CorrelationFunction {
z: f64,
params: CorrelationFunctionParameters,
lower_logk: f64,
upper_logk: f64,
target_error: f64,
#[borrows(params)]
#[covariant]
power_at_k: PowerFn<'this>,
}
impl CorrelationFunction {
pub fn correlation_function(&self, r: f64) -> f64 {
let prefactor = (2.0 * (PI).powi(2)).recip();
let integrand = |k: f64| k * self.borrow_power_at_k().power(k) * (k * r).sin() / r;
let intervals = r.clamp(1.0, 4.0) as usize;
let mut result = 0.0;
let lower_k = 1e-6 / r;
let upper_k = 1e4 / r;
let total_interval = upper_k - lower_k;
let subinterval_size = total_interval / intervals as f64;
for i in 0..intervals {
let interval_lower = lower_k + subinterval_size * i as f64;
let interval_upper = lower_k + subinterval_size * i.add(1) as f64;
let cf = quadrature::double_exponential::integrate(
integrand,
interval_lower,
interval_upper,
*self.borrow_target_error(),
);
check_integral(&cf);
result += cf.integral;
}
prefactor * result
}
pub fn get_correlation_function(
z: f64,
params: CorrelationFunctionParameters,
) -> Result<CorrelationFunction, Box<dyn Error>> {
let (lower_logk, upper_logk, target_error) = {
if let Some(ref acc_params) = params.accuracy_params {
(
acc_params.lower_logk_bound,
acc_params.upper_logk_bound,
acc_params.target_error,
)
} else {
(CORR_LOGK_MIN, CORR_LOGK_MAX, CORR_ABS_ERROR)
}
};
Ok(CorrelationFunctionBuilder {
z,
params,
lower_logk,
upper_logk,
target_error,
power_at_k_builder: |params: &CorrelationFunctionParameters| {
params.power.power_fn(z).unwrap()
},
}
.build())
}
pub fn get_params<'a>(&'a self) -> &'a CorrelationFunctionParameters {
&self.borrow_params()
}
pub fn get_redshift<'a>(&'a self) -> f64 {
*self.borrow_z()
}
}
#[allow(unused)]
fn check_integral(cf: &Output) {
}
#[derive(Clone)]
pub struct CorrelationFunctionParameters {
pub power: PowerSpectrum,
pub accuracy_params: Option<CorrFuncAccuracyParameters>,
}
#[derive(Clone)]
pub struct CorrFuncAccuracyParameters {
lower_logk_bound: f64,
upper_logk_bound: f64,
target_error: f64,
}
#[cfg(test)]
#[cfg(feature = "colossus-python")]
mod tests {
use super::*;
use crate::power::{PowerSpectrum, TransferFunction};
macro_rules! assert_eq_tol {
($x:expr, $y:expr, $d:expr) => {
let frac_delta = (($x - $y) / $y).abs();
let within = frac_delta < $d;
if !within {
let msg = format!(
"Result {:.4e} not within {:.4e} of {:.4e}. frac_delta is {:.4e}",
$x, $d, $y, frac_delta,
);
panic!("{msg}");
}
};
}
macro_rules! eisenstein_corr(
($z:ident, $h0:ident, $om0:ident, $ob0:ident, $t0:ident) => {
concat_idents::concat_idents!(test_name = test_eisen_corr, _, $z, _, $h0, _, $om0, _, $ob0, _, $t0, {
#[test]
fn test_name() {
let z: u32 = stringify!($z)[1..].parse::<u32>().unwrap();
let h: u32 = stringify!($h0)[1..].parse::<u32>().unwrap();
let om0: u32 = stringify!($om0)[1..].parse::<u32>().unwrap();
let ob0: u32 = stringify!($ob0)[1..].parse::<u32>().unwrap();
let t0: u32 = stringify!($t0)[1..].parse::<u32>().unwrap();
let power = PowerSpectrum::new(TransferFunction::EisensteinHu {
h: h as f64 / 100.0, omega_matter_0: om0 as f64 / 100.0, omega_baryon_0: ob0 as f64 / 100.0, temp_cmb0: t0 as f64 / 100.0, ns: 0.9665, sigma_8: 0.8102, }).unwrap();
let params = CorrelationFunctionParameters {
power,
accuracy_params: None, };
let corr = CorrelationFunction::get_correlation_function(
z as f64,
params
).unwrap();
let rs = [0.1, 1.0, 10.0, 100.0];
let result = rs.map(|r| corr.correlation_function(r));
let expected = {
use pyo3::prelude::*;
use pyo3::types::*;
Python::with_gil(|py| {
let list = PyList::new(py, &rs);
let locals = PyDict::new(py);
locals.set_item("rs", list).unwrap();
py.run(format!(r#"from colossus.cosmology import cosmology
import warnings
warnings.filterwarnings("ignore")
planck18 = cosmology.setCosmology("planck18")
params = {{
"H0": {0},
"Om0": {1},
"Ob0": {2},
"Tcmb0": {3},
"ns": 0.9665,
"sigma8": 0.8102,
}}
cosmology.addCosmology("test", params=params)
cosmo = cosmology.setCosmology("test")
x = []
for r in rs:
x.append(cosmo.correlationFunction(r, z={4}))
"#, h as f64, om0 as f64 / 100.0, ob0 as f64 / 100.0,
t0 as f64 / 100.0, z).as_str(), None, Some(locals)).unwrap();
let x: Vec<_> = locals.get_item("x").unwrap().extract::<Vec<f64>>().unwrap();
x
})
};
for i in 0..result.len() {
let abs_diff = (result[i]-expected[i]).abs();
let rel_diff = ((result[i]-expected[i])/expected[i]).abs();
assert!(abs_diff < 1e-2 || rel_diff < 5e-2, "result {:.3e} has abs_diff = {abs_diff:.3e}; rel_diff = {rel_diff:.3e} wrt expectation {:.3e}", result[i], expected[i]);
}
}
});
}
);
dry::macro_for!($H in [h50, h60, h70, h80, h90, h100] {
dry::macro_for!($M in [m10, m30, m50, m70, m90] {
dry::macro_for!($B in [b1, b2, b3] {
dry::macro_for!($T in [t270] {
dry::macro_for!($Z in [z0, z1, z2, z10] {
eisenstein_corr!($Z, $H, $M, $B, $T);
});
});
});
});
});
macro_rules! eisenstein_corr_plot(
($z:ident, $h0:ident, $om0:ident, $ob0:ident, $t0:ident) => {
concat_idents::concat_idents!(test_name = test_eisen_corr, _, $z, _, $h0, _, $om0, _, $ob0, _, $t0, {
#[test]
fn test_name() {
let z: u32 = stringify!($z)[1..].parse::<u32>().unwrap();
let h: u32 = stringify!($h0)[1..].parse::<u32>().unwrap();
let om0: u32 = stringify!($om0)[1..].parse::<u32>().unwrap();
let ob0: u32 = stringify!($ob0)[1..].parse::<u32>().unwrap();
let t0: u32 = stringify!($t0)[1..].parse::<u32>().unwrap();
let power = PowerSpectrum::new(TransferFunction::EisensteinHu {
h: h as f64 / 100.0, omega_matter_0: om0 as f64 / 100.0, omega_baryon_0: ob0 as f64 / 100.0, temp_cmb0: t0 as f64 / 100.0, ns: 0.9665, sigma_8: 0.8102, }).unwrap();
let params = CorrelationFunctionParameters {
power,
accuracy_params: None, };
let corr = CorrelationFunction::get_correlation_function(
z as f64,
params
).unwrap();
let rs = [0.1, 1.0, 10.0, 100.0];
let result = rs.map(|r| corr.correlation_function(r));
let expected = {
use pyo3::prelude::*;
use pyo3::types::*;
Python::with_gil(|py| {
let list = PyList::new(py, &rs);
let locals = PyDict::new(py);
locals.set_item("rs", list).unwrap();
py.run(format!(r#"from colossus.cosmology import cosmology
import warnings
warnings.filterwarnings("ignore")
planck18 = cosmology.setCosmology("planck18")
params = {{
"H0": {0},
"Om0": {1},
"Ob0": {2},
"Tcmb0": {3},
"ns": 0.9665,
"sigma8": 0.8102,
}}
cosmology.addCosmology("test", params=params)
cosmo = cosmology.setCosmology("test")
x = []
for r in rs:
x.append(cosmo.correlationFunction(r, z={4}))
"#, h as f64, om0 as f64 / 100.0, ob0 as f64 / 100.0,
t0 as f64 / 100.0, z).as_str(), None, Some(locals)).unwrap();
let x: Vec<_> = locals.get_item("x").unwrap().extract::<Vec<f64>>().unwrap();
x
})
};
for i in 0..result.len() {
let abs_diff = (result[i]-expected[i]).abs();
let rel_diff = ((result[i]-expected[i])/expected[i]).abs();
assert!(abs_diff < 1e-2 || rel_diff < 5e-2, "result {:.3e} has abs_diff = {abs_diff:.3e}; rel_diff = {rel_diff:.3e} wrt expectation {:.3e}", result[i], expected[i]);
}
}
});
}
);
}