use crate::errors::QlResult;
use crate::math::matrix::Matrix;
use crate::math::matrixutilities::{SalvagingAlgorithm, pseudo_sqrt};
use crate::require;
use crate::types::{Real, Size, Time};
#[derive(Clone, Debug)]
pub struct LmExponentialCorrelationModel {
corr_matrix: Matrix,
pseudo_sqrt: Matrix,
}
impl LmExponentialCorrelationModel {
pub fn new(size: Size, rho: Real) -> QlResult<Self> {
require!(size > 0, "correlation size must be positive");
require!(
rho.is_finite() && rho > 0.0,
"rho must be finite and positive"
);
require!(
size.checked_mul(size)
.and_then(|n| n.checked_mul(size_of::<Real>()))
.is_some_and(|bytes| bytes <= isize::MAX as usize),
"correlation matrix capacity is unrepresentable"
);
let mut corr_matrix = Matrix::with_size(size, size);
for i in 0..size {
for j in i..size {
let value = (-rho * (j - i) as Real).exp();
corr_matrix[(i, j)] = value;
corr_matrix[(j, i)] = value;
}
}
let pseudo_sqrt = pseudo_sqrt(&corr_matrix, SalvagingAlgorithm::Spectral);
require!(
(0..size).all(|i| pseudo_sqrt.row(i).iter().all(|v| v.is_finite())),
"correlation factorization is not finite"
);
Ok(Self {
corr_matrix,
pseudo_sqrt,
})
}
pub fn size(&self) -> Size {
self.corr_matrix.rows()
}
pub fn factors(&self) -> Size {
self.size()
}
pub fn correlation(&self, t: Time) -> QlResult<&Matrix> {
require!(t.is_finite(), "correlation time must be finite");
Ok(&self.corr_matrix)
}
pub fn correlation_ij(&self, i: Size, j: Size, t: Time) -> QlResult<Real> {
require!(
i < self.size() && j < self.size(),
"correlation indices out of range"
);
Ok(self.correlation(t)?[(i, j)])
}
pub fn pseudo_sqrt(&self, t: Time) -> QlResult<&Matrix> {
require!(t.is_finite(), "correlation time must be finite");
Ok(&self.pseudo_sqrt)
}
pub fn is_time_independent(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn distance_correlation_and_spectral_product() {
let model = LmExponentialCorrelationModel::new(5, 0.3).unwrap();
assert_eq!(model.size(), 5);
assert_eq!(model.factors(), 5);
assert!(model.is_time_independent());
let corr = model.correlation(0.0).unwrap();
let factor = model.pseudo_sqrt(-2.0).unwrap();
let reconstructed = factor * &factor.transpose();
for i in 0_usize..5 {
for j in 0..5 {
let expected = (-0.3 * i.abs_diff(j) as Real).exp();
assert!((corr[(i, j)] - expected).abs() < 1e-15);
assert!((reconstructed[(i, j)] - expected).abs() < 2e-14);
assert_eq!(model.correlation_ij(i, j, 9.0).unwrap(), corr[(i, j)]);
}
}
}
#[test]
fn extreme_positive_rho_and_single_row_are_finite() {
for rho in [Real::MIN_POSITIVE, 0.001, Real::MAX] {
for size in [1, 4] {
let model = LmExponentialCorrelationModel::new(size, rho).unwrap();
let corr = model.correlation(0.0).unwrap();
let factor = model.pseudo_sqrt(0.0).unwrap();
let product = factor * &factor.transpose();
for i in 0..size {
assert_eq!(corr[(i, i)], 1.0);
for j in 0..size {
assert!((product[(i, j)] - corr[(i, j)]).abs() < 2e-14);
}
}
}
}
}
#[test]
fn rejects_invalid_parameters_and_capacity_without_panicking() {
for rho in [0.0, -1.0, Real::NAN, Real::INFINITY, Real::NEG_INFINITY] {
assert!(LmExponentialCorrelationModel::new(2, rho).is_err());
}
assert!(LmExponentialCorrelationModel::new(0, 0.2).is_err());
assert!(LmExponentialCorrelationModel::new(usize::MAX, 0.2).is_err());
}
#[test]
fn rejects_invalid_query_time_and_indices() {
let model = LmExponentialCorrelationModel::new(2, 0.2).unwrap();
for t in [Real::NAN, Real::INFINITY, Real::NEG_INFINITY] {
assert!(model.correlation(t).is_err());
assert!(model.pseudo_sqrt(t).is_err());
assert!(model.correlation_ij(0, 0, t).is_err());
}
assert!(model.correlation_ij(2, 0, 0.0).is_err());
assert!(model.correlation_ij(0, usize::MAX, 0.0).is_err());
}
}