use std::f64::consts::PI;
use quadrature::Output;
use rayon::iter::ParallelIterator;
use rayon::prelude::IntoParallelIterator;
use self::{cdf::calculate_grf_cdf, pdf::calculate_grf_pdf};
const GRF_INTEGRATION_TARGET_ERROR: f64 = 1e-20;
mod cdf;
mod pdf;
pub struct GaussianRandomField<'b, 'c> {
mode: SpaceMode<'b, 'c>,
container: Vec<(f64, TwoPointIntegral, TwoPointIntegralDerivative)>,
}
type TwoPointIntegral = f64;
type TwoPointIntegralDerivative = TwoPointIntegral;
type Radius = f64;
pub enum SpaceMode<'borrow, 'corr> {
RealSpace(&'borrow (dyn Fn(Radius) -> f64 + 'corr + Send + Sync)),
RedshiftSpace {
real_corr: &'borrow (dyn Fn(Radius) -> f64 + 'corr + Send + Sync),
f: f64,
},
}
impl<'b, 'c> GaussianRandomField<'b, 'c> {
pub fn new(mode: SpaceMode<'b, 'c>) -> Self {
GaussianRandomField {
mode,
container: Vec::new(),
}
}
pub fn with(mut self, rs: &[f64]) -> Self {
let bar = indicatif::ProgressBar::new(rs.len() as u64);
self.container = rs
.into_par_iter()
.inspect(|_| bar.inc(1))
.map(|r| (*r, self.calculate_i(*r), self.calculate_didv(*r)))
.collect();
self
}
fn calculate_i(&self, r: f64) -> TwoPointIntegral {
match self.mode {
SpaceMode::RealSpace(corr) => {
let prefactor = 4.0 * PI;
let integrand = |s: f64| {
spherical_pair_weight_function(s, r) * corr(s) * s.powi(2)
};
let integral = quadrature::clenshaw_curtis::integrate(
integrand,
0.0,
2.0 * r,
GRF_INTEGRATION_TARGET_ERROR,
);
check_integral(&integral);
let result: f64 = prefactor * integral.integral;
result
}
SpaceMode::RedshiftSpace { real_corr, .. } => {
let prefactor = 4.0 * PI;
let integrand = |s: f64| {
spherical_pair_weight_function(s, r) * real_corr(s) * s.powi(2)
};
let integral = quadrature::clenshaw_curtis::integrate(
integrand,
0.0,
2.0 * r,
GRF_INTEGRATION_TARGET_ERROR,
);
check_integral(&integral);
let result: f64 = prefactor * integral.integral;
result
}
}
}
fn calculate_didv(&self, r: f64) -> TwoPointIntegralDerivative {
match self.mode {
SpaceMode::RealSpace(corr) => {
let prefactor = 4.0 * PI;
let integrand = |s: f64| {
spherical_pair_weight_function_dv(s, r) * corr(s) * s.powi(2)
};
let integral = quadrature::clenshaw_curtis::integrate(
integrand,
0.0,
2.0 * r,
GRF_INTEGRATION_TARGET_ERROR,
);
check_integral(&integral);
let result: f64 = prefactor * integral.integral;
result
}
SpaceMode::RedshiftSpace { real_corr, .. } => {
let prefactor = 4.0 * PI;
let integrand = |s: f64| {
spherical_pair_weight_function_dv(s, r) * real_corr(s) * s.powi(2)
};
let integral = quadrature::clenshaw_curtis::integrate(
integrand,
0.0,
2.0 * r,
GRF_INTEGRATION_TARGET_ERROR,
);
check_integral(&integral);
let result: f64 = prefactor * integral.integral;
result
}
}
}
pub fn get_cdf(&self, k: u8, nbar: f64, b: Option<f64>) -> Vec<f64> {
self.container
.iter()
.enumerate()
.map(|(_, (r, i, _))| {
calculate_grf_cdf(*r, nbar, self.map_integral(*i, b.unwrap_or(1.0)), k)
})
.collect()
}
pub fn get_pdf(&self, k: u8, nbar: f64, b: Option<f64>) -> Vec<f64> {
self.container
.iter()
.enumerate()
.map(|(_, (r, i, didv))| {
calculate_grf_pdf(
*r,
nbar,
self.map_integral(*i, b.unwrap_or(1.0)),
self.map_integral(*didv, b.unwrap_or(1.0)),
k,
)
})
.collect()
}
fn map_integral(&self, i: f64, b: f64) -> f64 {
match &self.mode {
SpaceMode::RealSpace(_) => b * b * i,
SpaceMode::RedshiftSpace { f, .. } => {
i * (b * b + (2.0 * b * f / 3.0) + (f.powi(2) / 5.0))
}
}
}
pub fn get_i_didv<'a>(&'a self) -> &[(f64, TwoPointIntegral, TwoPointIntegralDerivative)] {
&self.container
}
}
#[allow(unused)]
fn check_integral(integral: &Output) {
}
fn spherical_pair_weight_function(s: f64, r: f64) -> f64 {
PI / 12.0 * (s - 2.0 * r).powi(2) * (s + 4.0 * r)
}
fn spherical_pair_weight_function_dv(s: f64, r: f64) -> f64 {
((PI * (-2.0 * r + s).powi(2)) / 3. - (PI * (-2.0 * r + s) * (4.0 * r + s)) / 3.)
/ (4. * PI * r.powi(2))
}
#[test]
fn test_spherical_weight_at_diameter() {
assert_approx_eq::assert_approx_eq!(spherical_pair_weight_function(2.0, 1.0), 0.0, 1e-5);
}
#[test]
fn test_spherical_weight_at_radius() {
assert_approx_eq::assert_approx_eq!(
spherical_pair_weight_function(1.0, 1.0),
2.0 * PI * 0.25 / 3.0 * (3.0 - 0.5),
1e-5
);
}
#[test]
fn test_spherical_weight_at_zero() {
assert_approx_eq::assert_approx_eq!(
spherical_pair_weight_function(0.0, 1.0),
4.0 * PI / 3.0,
1e-5
);
}