1use crate::power::{PowerFn, PowerSpectrum};
2use ouroboros::self_referencing;
3use quadrature::Output;
4use std::error::Error;
5use std::f64::consts::PI;
6use std::ops::Add;
7
8pub const CORR_LOGK_MIN: f64 = -6.0;
10pub const CORR_LOGK_MAX: f64 = 6.0;
12pub const CORR_ABS_ERROR: f64 = 1e-10;
14#[self_referencing]
18pub struct CorrelationFunction {
19 z: f64,
20 params: CorrelationFunctionParameters,
21 lower_logk: f64,
22 upper_logk: f64,
23 target_error: f64,
24 #[borrows(params)]
25 #[covariant]
26 power_at_k: PowerFn<'this>,
27}
28
29impl CorrelationFunction {
30 pub fn correlation_function(&self, r: f64) -> f64 {
32 let prefactor = (2.0 * (PI).powi(2)).recip();
34
35 let integrand = |k: f64| k * self.borrow_power_at_k().power(k) * (k * r).sin() / r;
37
38 let intervals = r.clamp(1.0, 4.0) as usize;
40 let mut result = 0.0;
41 let lower_k = 1e-6 / r;
42 let upper_k = 1e4 / r;
43 let total_interval = upper_k - lower_k;
44 let subinterval_size = total_interval / intervals as f64;
45 for i in 0..intervals {
46 let interval_lower = lower_k + subinterval_size * i as f64;
47 let interval_upper = lower_k + subinterval_size * i.add(1) as f64;
48 let cf = quadrature::double_exponential::integrate(
49 integrand,
50 interval_lower,
51 interval_upper,
52 *self.borrow_target_error(),
53 );
54 check_integral(&cf);
55 result += cf.integral;
56 }
57 prefactor * result
58 }
59
60 pub fn get_correlation_function(
80 z: f64,
81 params: CorrelationFunctionParameters,
82 ) -> Result<CorrelationFunction, Box<dyn Error>> {
83 let (lower_logk, upper_logk, target_error) = {
85 if let Some(ref acc_params) = params.accuracy_params {
86 (
88 acc_params.lower_logk_bound,
89 acc_params.upper_logk_bound,
90 acc_params.target_error,
91 )
92 } else {
93 (CORR_LOGK_MIN, CORR_LOGK_MAX, CORR_ABS_ERROR)
95 }
96 };
97
98 Ok(CorrelationFunctionBuilder {
99 z,
100 params,
101 lower_logk,
102 upper_logk,
103 target_error,
104 power_at_k_builder: |params: &CorrelationFunctionParameters| {
105 params.power.power_fn(z).unwrap()
106 },
107 }
108 .build())
109 }
110
111 pub fn get_params<'a>(&'a self) -> &'a CorrelationFunctionParameters {
112 &self.borrow_params()
113 }
114 pub fn get_redshift<'a>(&'a self) -> f64 {
115 *self.borrow_z()
116 }
117}
118
119#[allow(unused)]
120fn check_integral(cf: &Output) {
121 }
124
125#[derive(Clone)]
127pub struct CorrelationFunctionParameters {
128 pub power: PowerSpectrum,
130
131 pub accuracy_params: Option<CorrFuncAccuracyParameters>,
133}
134
135#[derive(Clone)]
137pub struct CorrFuncAccuracyParameters {
138 lower_logk_bound: f64,
139 upper_logk_bound: f64,
140 target_error: f64,
141}
142
143#[cfg(test)]
144#[cfg(feature = "colossus-python")]
145mod tests {
146
147 use super::*;
148 use crate::power::{PowerSpectrum, TransferFunction};
149
150 macro_rules! assert_eq_tol {
151 ($x:expr, $y:expr, $d:expr) => {
152 let frac_delta = (($x - $y) / $y).abs();
154
155 let within = frac_delta < $d;
157
158 if !within {
159 let msg = format!(
161 "Result {:.4e} not within {:.4e} of {:.4e}. frac_delta is {:.4e}",
162 $x, $d, $y, frac_delta,
163 );
164
165 panic!("{msg}");
167 }
168 };
169 }
170
171 macro_rules! eisenstein_corr(
172 ($z:ident, $h0:ident, $om0:ident, $ob0:ident, $t0:ident) => {
173
174 concat_idents::concat_idents!(test_name = test_eisen_corr, _, $z, _, $h0, _, $om0, _, $ob0, _, $t0, {
175 #[test]
176 fn test_name() {
177
178 let z: u32 = stringify!($z)[1..].parse::<u32>().unwrap();
179 let h: u32 = stringify!($h0)[1..].parse::<u32>().unwrap();
180 let om0: u32 = stringify!($om0)[1..].parse::<u32>().unwrap();
181 let ob0: u32 = stringify!($ob0)[1..].parse::<u32>().unwrap();
182 let t0: u32 = stringify!($t0)[1..].parse::<u32>().unwrap();
183
184 let power = PowerSpectrum::new(TransferFunction::EisensteinHu {
186 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();
193
194 let params = CorrelationFunctionParameters {
196 power,
197 accuracy_params: None, };
199 let corr = CorrelationFunction::get_correlation_function(
200 z as f64,
201 params
202 ).unwrap();
203
204 let rs = [0.1, 1.0, 10.0, 100.0];
206
207 let result = rs.map(|r| corr.correlation_function(r));
209
210 let expected = {
212 use pyo3::prelude::*;
213 use pyo3::types::*;
214 Python::with_gil(|py| {
215
216 let list = PyList::new(py, &rs);
218 let locals = PyDict::new(py);
219 locals.set_item("rs", list).unwrap();
220
221 py.run(format!(r#"from colossus.cosmology import cosmology
222import warnings
223warnings.filterwarnings("ignore")
224planck18 = cosmology.setCosmology("planck18")
225params = {{
226 "H0": {0},
227 "Om0": {1},
228 "Ob0": {2},
229 "Tcmb0": {3},
230 "ns": 0.9665,
231 "sigma8": 0.8102,
232}}
233cosmology.addCosmology("test", params=params)
234cosmo = cosmology.setCosmology("test")
235x = []
236for r in rs:
237 x.append(cosmo.correlationFunction(r, z={4}))
238 "#, h as f64, om0 as f64 / 100.0, ob0 as f64 / 100.0,
239 t0 as f64 / 100.0, z).as_str(), None, Some(locals)).unwrap();
240 let x: Vec<_> = locals.get_item("x").unwrap().extract::<Vec<f64>>().unwrap();
241 x
242 })
243 };
244
245 for i in 0..result.len() {
246 let abs_diff = (result[i]-expected[i]).abs();
247 let rel_diff = ((result[i]-expected[i])/expected[i]).abs();
248 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]);
249 }
250 }
251 });
252 }
253 );
254 dry::macro_for!($H in [h50, h60, h70, h80, h90, h100] {
255 dry::macro_for!($M in [m10, m30, m50, m70, m90] {
256 dry::macro_for!($B in [b1, b2, b3] {
257 dry::macro_for!($T in [t270] {
258 dry::macro_for!($Z in [z0, z1, z2, z10] {
259 eisenstein_corr!($Z, $H, $M, $B, $T);
260 });
261 });
262 });
263 });
264 });
265
266 macro_rules! eisenstein_corr_plot(
267 ($z:ident, $h0:ident, $om0:ident, $ob0:ident, $t0:ident) => {
268
269 concat_idents::concat_idents!(test_name = test_eisen_corr, _, $z, _, $h0, _, $om0, _, $ob0, _, $t0, {
270 #[test]
271 fn test_name() {
272
273 let z: u32 = stringify!($z)[1..].parse::<u32>().unwrap();
274 let h: u32 = stringify!($h0)[1..].parse::<u32>().unwrap();
275 let om0: u32 = stringify!($om0)[1..].parse::<u32>().unwrap();
276 let ob0: u32 = stringify!($ob0)[1..].parse::<u32>().unwrap();
277 let t0: u32 = stringify!($t0)[1..].parse::<u32>().unwrap();
278
279 let power = PowerSpectrum::new(TransferFunction::EisensteinHu {
281 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();
288
289 let params = CorrelationFunctionParameters {
291 power,
292 accuracy_params: None, };
294 let corr = CorrelationFunction::get_correlation_function(
295 z as f64,
296 params
297 ).unwrap();
298
299 let rs = [0.1, 1.0, 10.0, 100.0];
301
302 let result = rs.map(|r| corr.correlation_function(r));
304
305 let expected = {
307 use pyo3::prelude::*;
308 use pyo3::types::*;
309 Python::with_gil(|py| {
310
311 let list = PyList::new(py, &rs);
313 let locals = PyDict::new(py);
314 locals.set_item("rs", list).unwrap();
315
316 py.run(format!(r#"from colossus.cosmology import cosmology
317import warnings
318warnings.filterwarnings("ignore")
319planck18 = cosmology.setCosmology("planck18")
320params = {{
321 "H0": {0},
322 "Om0": {1},
323 "Ob0": {2},
324 "Tcmb0": {3},
325 "ns": 0.9665,
326 "sigma8": 0.8102,
327}}
328cosmology.addCosmology("test", params=params)
329cosmo = cosmology.setCosmology("test")
330x = []
331for r in rs:
332 x.append(cosmo.correlationFunction(r, z={4}))
333 "#, h as f64, om0 as f64 / 100.0, ob0 as f64 / 100.0,
334 t0 as f64 / 100.0, z).as_str(), None, Some(locals)).unwrap();
335 let x: Vec<_> = locals.get_item("x").unwrap().extract::<Vec<f64>>().unwrap();
336 x
337 })
338 };
339
340 for i in 0..result.len() {
341 let abs_diff = (result[i]-expected[i]).abs();
342 let rel_diff = ((result[i]-expected[i])/expected[i]).abs();
343 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]);
344 }
345 }
346 });
347 }
348 );
349}