use crate::error::GreenersError;
use ndarray::{Array1, Array2};
use std::fmt;
#[derive(Debug)]
pub struct TvpResult {
pub beta_smoothed: Array2<f64>,
pub beta_se: Array2<f64>,
pub sigma_epsilon: f64,
pub sigma_eta: f64,
pub log_likelihood: f64,
pub n_obs: usize,
pub_k: usize,
pub variable_names: Option<Vec<String>>,
}
impl TvpResult {
pub fn k(&self) -> usize {
self.pub_k
}
}
impl fmt::Display for TvpResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "\n{:=^78}", " Time-Varying Parameter Regression ")?;
writeln!(
f,
"Model: y_t = x_t'beta_t + eps, beta_t = beta_{{t-1}} + eta"
)?;
writeln!(f, "{:<20} {:>12}", "Observations:", self.n_obs)?;
writeln!(f, "{:<20} {:>12}", "Regressors:", self.pub_k)?;
writeln!(f, "{:<20} {:>12.6}", "sigma_epsilon:", self.sigma_epsilon)?;
writeln!(f, "{:<20} {:>12.6}", "sigma_eta:", self.sigma_eta)?;
writeln!(f, "{:<20} {:>12.6}", "Log-likelihood:", self.log_likelihood)?;
let t_mid = self.n_obs / 2;
writeln!(f, "\n{:-^78}", "")?;
writeln!(f, " Smoothed beta estimates (selected periods):")?;
writeln!(
f,
"{:<8} {:>14} {:>14} {:>14}",
"Period", "beta_0", "beta_1", "..."
)?;
writeln!(f, "{:-^78}", "")?;
for &t in &[0, t_mid, self.n_obs - 1] {
let mut row = format!("{:<8} ", t + 1);
for j in 0..self.pub_k.min(3) {
row.push_str(&format!("{:>14.6} ", self.beta_smoothed[(t, j)]));
}
writeln!(f, "{row}")?;
}
write!(f, "{:=^78}", "")
}
}
pub struct TVP;
impl TVP {
pub fn fit(
y: &Array1<f64>,
x: &Array2<f64>,
variable_names: Option<Vec<String>>,
) -> Result<TvpResult, GreenersError> {
let t = y.len();
let k = x.ncols();
if x.nrows() != t {
return Err(GreenersError::ShapeMismatch(
"TVP: y and x must have same number of rows".into(),
));
}
let mut best_ratio = 0.01_f64;
let mut best_ll = f64::NEG_INFINITY;
let mut best_sigma_eps = 1.0_f64;
let n_grid = 50;
for i in 0..n_grid {
let ratio = 0.001 + 0.5 * i as f64 / (n_grid - 1) as f64;
let (ll, sigma_eps) = Self::kalman_loglik(y, x, ratio)?;
if ll > best_ll {
best_ll = ll;
best_ratio = ratio;
best_sigma_eps = sigma_eps;
}
}
let golden = 0.6180339887498949;
let mut a = (best_ratio - 0.05).max(0.0001);
let mut b = best_ratio + 0.05;
let mut c = b - golden * (b - a);
let mut d = a + golden * (b - a);
let (mut fc, _sig_c) = Self::kalman_loglik(y, x, c)?;
let (mut fd, _sig_d) = Self::kalman_loglik(y, x, d)?;
for _ in 0..40 {
if fc > fd {
b = d;
d = c;
fd = fc;
c = b - golden * (b - a);
let (ll, se) = Self::kalman_loglik(y, x, c)?;
fc = ll;
best_sigma_eps = se;
} else {
a = c;
c = d;
fc = fd;
d = a + golden * (b - a);
let (ll, se) = Self::kalman_loglik(y, x, d)?;
fd = ll;
best_sigma_eps = se;
}
}
best_ratio = if fc > fd { c } else { d };
best_ll = if fc > fd { fc } else { fd };
let sigma_epsilon = best_sigma_eps;
let sigma_eta = best_ratio * sigma_epsilon;
let (beta_smoothed, beta_se) = Self::kalman_smoother(y, x, sigma_epsilon, sigma_eta)?;
Ok(TvpResult {
beta_smoothed,
beta_se,
sigma_epsilon,
sigma_eta,
log_likelihood: best_ll,
n_obs: t,
pub_k: k,
variable_names,
})
}
fn kalman_loglik(
y: &Array1<f64>,
x: &Array2<f64>,
ratio: f64,
) -> Result<(f64, f64), GreenersError> {
let t = y.len();
let k = x.ncols();
let mut beta_pred = Array1::zeros(k); let mut p_pred: Array2<f64> = Array2::eye(k) * 100.0;
let mut ll = 0.0_f64;
let mut sse_scaled = 0.0_f64;
for t_i in 0..t {
let x_t = x.row(t_i);
let y_pred = x_t.dot(&beta_pred);
let e = y[t_i] - y_pred;
let f = x_t.dot(&p_pred.dot(&x_t)) + 1.0;
if f.abs() < 1e-15 {
continue;
}
let k_gain = p_pred.dot(&x_t) / f;
beta_pred = &beta_pred + &k_gain * e;
p_pred = &p_pred - &k_gain * &x_t * &p_pred; p_pred = p_pred + Array2::<f64>::eye(k) * ratio * ratio;
ll += -0.5 * (f.ln() + e * e / f);
sse_scaled += e * e / f;
}
let sigma_eps2 = sse_scaled / t as f64;
ll += -(t as f64) / 2.0 * (sigma_eps2.ln() + 1.0);
Ok((ll, sigma_eps2.sqrt()))
}
fn kalman_smoother(
y: &Array1<f64>,
x: &Array2<f64>,
sigma_eps: f64,
sigma_eta: f64,
) -> Result<(Array2<f64>, Array2<f64>), GreenersError> {
let t = y.len();
let k = x.ncols();
let mut beta_filt: Vec<Array1<f64>> = Vec::with_capacity(t);
let mut p_filt: Vec<Array2<f64>> = Vec::with_capacity(t);
let mut beta_pred: Array1<f64> = Array1::zeros(k);
let mut p_pred: Array2<f64> = Array2::eye(k) * 100.0;
for t_i in 0..t {
let x_t = x.row(t_i);
let y_pred = x_t.dot(&beta_pred);
let e = y[t_i] - y_pred;
let f = x_t.dot(&p_pred.dot(&x_t)) * sigma_eps * sigma_eps + 1e-10;
let k_gain = p_pred.dot(&x_t) / f * sigma_eps * sigma_eps;
beta_pred = beta_pred + &k_gain * e;
p_pred = &p_pred - &k_gain * &x_t * &p_pred;
p_pred = p_pred + Array2::<f64>::eye(k) * sigma_eta * sigma_eta;
beta_filt.push(beta_pred.clone());
p_filt.push(p_pred.clone());
}
let mut beta_smoothed = Array2::zeros((t, k));
let mut beta_se = Array2::zeros((t, k));
for j in 0..k {
beta_smoothed[(t - 1, j)] = beta_filt[t - 1][j];
beta_se[(t - 1, j)] = p_filt[t - 1][(j, j)].sqrt();
}
for t_i in (0..t - 1).rev() {
for j in 0..k {
beta_smoothed[(t_i, j)] = beta_filt[t_i][j];
beta_se[(t_i, j)] = p_filt[t_i][(j, j)].sqrt();
}
}
Ok((beta_smoothed, beta_se))
}
}