use crate::error::GreenersError;
use ndarray as nd;
use ndarray::{Array1, Array2};
use ndarray_linalg::Inverse;
use statrs::distribution::{ChiSquared, ContinuousCDF};
use std::fmt;
#[derive(Debug)]
pub struct GmmResult {
pub params: Array1<f64>,
pub std_errors: Array1<f64>,
pub t_values: Array1<f64>,
pub p_values: Array1<f64>,
pub j_stat: f64, pub j_p_value: f64, pub n_obs: usize,
pub df_model: usize,
pub df_overid: usize, }
impl fmt::Display for GmmResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "\n{:=^78}", " GMM (Two-Step Efficient) Results ")?;
writeln!(
f,
"{:<20} {:>15} || {:<20} {:>15.4}",
"Dep. Variable:", "y", "J-Statistic:", self.j_stat
)?;
writeln!(
f,
"{:<20} {:>15} || {:<20} {:>15.4}",
"Estimator:", "GMM", "Prob(J-Stat):", self.j_p_value
)?;
writeln!(
f,
"{:<20} {:>15} || {:<20} {:>15}",
"No. Observations:", self.n_obs, "DF Overid:", self.df_overid
)?;
writeln!(f, "\n{:-^78}", "")?;
writeln!(
f,
"{:<10} | {:>10} | {:>10} | {:>8} | {:>8}",
"Variable", "coef", "std err", "z", "P>|z|"
)?;
writeln!(f, "{:-^78}", "")?;
for i in 0..self.params.len() {
writeln!(
f,
"x{:<9} | {:>10.4} | {:>10.4} | {:>8.3} | {:>8.3}",
i, self.params[i], self.std_errors[i], self.t_values[i], self.p_values[i]
)?;
}
writeln!(f, "{:=^78}", "")
}
}
pub struct GMM;
impl GMM {
pub fn fit(
y: &Array1<f64>,
x: &Array2<f64>,
z: &Array2<f64>,
) -> Result<GmmResult, GreenersError> {
let n = x.nrows();
let k = x.ncols(); let l = z.ncols();
if l < k {
return Err(GreenersError::ShapeMismatch(format!(
"GMM requires L >= K (Instruments >= Params). Got L={}, K={}",
l, k
)));
}
let z_t = z.t();
let s_zx = z_t.dot(x) / (n as f64);
let s_zy = z_t.dot(y) / (n as f64);
let s_zz = z_t.dot(z) / (n as f64);
let w1 = s_zz.inv()?;
let s_zx_t = s_zx.t();
let lhs_1 = s_zx_t.dot(&w1).dot(&s_zx);
let rhs_1 = s_zx_t.dot(&w1).dot(&s_zy);
let beta1 = lhs_1.inv()?.dot(&rhs_1);
let pred1 = x.dot(&beta1);
let resid1 = y - &pred1;
let u_sq = resid1.mapv(|r| r.powi(2));
let mut z_weighted = z.clone();
for (i, mut row) in z_weighted.axis_iter_mut(nd::Axis(0)).enumerate() {
row *= u_sq[i];
}
let s_matrix = z_t.dot(&z_weighted) / (n as f64);
let w_opt = match s_matrix.inv() {
Ok(mat) => mat,
Err(_) => return Err(GreenersError::OptimizationFailed), };
let lhs_final = s_zx_t.dot(&w_opt).dot(&s_zx);
let rhs_final = s_zx_t.dot(&w_opt).dot(&s_zy);
let var_beta_matrix = lhs_final.inv()? / (n as f64);
let beta_final = var_beta_matrix.dot(&rhs_final) * (n as f64);
let std_errors = var_beta_matrix.diag().mapv(f64::sqrt);
let t_values = &beta_final / &std_errors;
let dist = statrs::distribution::Normal::new(0.0, 1.0).unwrap();
let p_values = t_values.mapv(|z| 2.0 * (1.0 - dist.cdf(z.abs())));
let pred_final = x.dot(&beta_final);
let resid_final = y - &pred_final;
let g_bar = z_t.dot(&resid_final) / (n as f64);
let j_stat = (n as f64) * g_bar.t().dot(&w_opt).dot(&g_bar);
let df_overid = l - k;
let j_p_value = if df_overid > 0 {
let chi2 =
ChiSquared::new(df_overid as f64).map_err(|_| GreenersError::OptimizationFailed)?;
1.0 - chi2.cdf(j_stat)
} else {
f64::NAN };
Ok(GmmResult {
params: beta_final,
std_errors,
t_values,
p_values,
j_stat,
j_p_value,
n_obs: n,
df_model: k,
df_overid,
})
}
}