1use crate::error::{StatsError, StatsResult};
11use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2};
12use scirs2_core::numeric::{Float, FromPrimitive};
13use scirs2_linalg::{lstsq, solve};
14
15fn matmul<F: Float + std::iter::Sum>(a: &Array2<F>, b: &Array2<F>) -> StatsResult<Array2<F>> {
20 let (m, k) = a.dim();
21 let (kb, n) = b.dim();
22 if k != kb {
23 return Err(StatsError::DimensionMismatch(format!(
24 "matmul: {} vs {}",
25 k, kb
26 )));
27 }
28 let mut c = Array2::zeros((m, n));
29 for i in 0..m {
30 for j in 0..n {
31 let mut s = F::zero();
32 for l in 0..k {
33 s = s + a[[i, l]] * b[[l, j]];
34 }
35 c[[i, j]] = s;
36 }
37 }
38 Ok(c)
39}
40
41fn mat_vec<F: Float + std::iter::Sum>(a: &Array2<F>, v: &Array1<F>) -> StatsResult<Array1<F>> {
42 let (m, k) = a.dim();
43 if v.len() != k {
44 return Err(StatsError::DimensionMismatch(format!(
45 "mat_vec: {} vs {}",
46 k,
47 v.len()
48 )));
49 }
50 let mut res = Array1::zeros(m);
51 for i in 0..m {
52 let mut s = F::zero();
53 for j in 0..k {
54 s = s + a[[i, j]] * v[j];
55 }
56 res[i] = s;
57 }
58 Ok(res)
59}
60
61fn transpose<F: Float>(a: &Array2<F>) -> Array2<F> {
62 let (m, n) = a.dim();
63 let mut t = Array2::zeros((n, m));
64 for i in 0..m {
65 for j in 0..n {
66 t[[j, i]] = a[[i, j]];
67 }
68 }
69 t
70}
71
72fn gmm_estimator<F>(
75 x: &Array2<F>,
76 y: &Array1<F>,
77 z: &Array2<F>,
78 w: &Array2<F>,
79) -> StatsResult<(Array1<F>, Array1<F>)>
80where
81 F: Float
82 + std::iter::Sum
83 + std::fmt::Debug
84 + std::fmt::Display
85 + scirs2_core::numeric::NumAssign
86 + scirs2_core::numeric::One
87 + scirs2_core::ndarray::ScalarOperand
88 + FromPrimitive
89 + Send
90 + Sync
91 + 'static,
92{
93 let zx = matmul(&transpose(z), x)?; let zy = mat_vec(&transpose(z), y)?; let wzx = matmul(w, &zx)?; let wzy = mat_vec(w, &zy)?; let xtwzx = matmul(&transpose(&zx), &wzx)?; let xtwzy = mat_vec(&transpose(&zx), &wzy)?; let coeffs = solve(&xtwzx.view(), &xtwzy.view(), None)
103 .map_err(|e| StatsError::ComputationError(format!("GMM solve: {e}")))?;
104
105 let n = y.len();
107 let k = x.ncols();
108 let mut fitted = Array1::zeros(n);
109 for i in 0..n {
110 for j in 0..k {
111 fitted[i] = fitted[i] + x[[i, j]] * coeffs[j];
112 }
113 }
114 let resid: Array1<F> = y.iter().zip(fitted.iter()).map(|(&y, &f)| y - f).collect();
115 Ok((coeffs, resid))
116}
117
118#[derive(Debug, Clone)]
124pub struct ARTestResult<F> {
125 pub z_stat: F,
127 pub p_value: F,
129}
130
131#[derive(Debug, Clone)]
133pub struct SarganTestResult<F> {
134 pub j_stat: F,
136 pub df: usize,
138 pub p_value: F,
140}
141
142#[derive(Debug, Clone)]
144pub struct DynamicPanelResult<F> {
145 pub coefficients: Array1<F>,
147 pub std_errors: Array1<F>,
149 pub z_stats: Array1<F>,
151 pub sargan: SarganTestResult<F>,
153 pub ar1: ARTestResult<F>,
155 pub ar2: ARTestResult<F>,
157 pub n_obs: usize,
159 pub n_instruments: usize,
161 pub residuals: Array1<F>,
163}
164
165pub struct ArellanoBlond;
177
178impl ArellanoBlond {
179 pub fn fit<F>(
188 y: &ArrayView1<F>,
189 x: &ArrayView2<F>,
190 entity: &[usize],
191 time: &[usize],
192 n_lags: usize,
193 ) -> StatsResult<DynamicPanelResult<F>>
194 where
195 F: Float
196 + std::iter::Sum
197 + std::fmt::Debug
198 + std::fmt::Display
199 + scirs2_core::numeric::NumAssign
200 + scirs2_core::numeric::One
201 + scirs2_core::ndarray::ScalarOperand
202 + FromPrimitive
203 + Send
204 + Sync
205 + 'static,
206 {
207 if n_lags == 0 {
208 return Err(StatsError::InvalidArgument(
209 "n_lags must be >= 1".to_string(),
210 ));
211 }
212 let n = y.len();
213 let (nx, kx) = x.dim();
214 if nx != n || entity.len() != n || time.len() != n {
215 return Err(StatsError::DimensionMismatch(
216 "y, x, entity, time lengths must match".to_string(),
217 ));
218 }
219
220 let n_entities = entity.iter().copied().max().map(|m| m + 1).unwrap_or(0);
222 let t_total = time.iter().copied().max().map(|m| m + 1).unwrap_or(0);
223 if t_total < n_lags + 2 {
224 return Err(StatsError::InsufficientData(format!(
225 "Need at least {} time periods, got {}",
226 n_lags + 2,
227 t_total
228 )));
229 }
230 let t_per = n / n_entities;
232 if t_per * n_entities != n {
233 return Err(StatsError::InvalidArgument(
234 "AB-GMM: requires balanced panel".to_string(),
235 ));
236 }
237
238 let mut idx_map = vec![vec![0usize; t_total]; n_entities];
245 for i in 0..n {
246 idx_map[entity[i]][time[i]] = i;
247 }
248
249 let t_start = n_lags + 1; let nd = n_entities * (t_total - t_start); if nd < kx + n_lags + 1 {
254 return Err(StatsError::InsufficientData(format!(
255 "Too few FD observations ({}) for {} regressors",
256 nd,
257 kx + n_lags
258 )));
259 }
260
261 let mut dy_vec: Vec<F> = Vec::with_capacity(nd);
262 let mut dx_rows: Vec<Vec<F>> = Vec::with_capacity(nd); for eid in 0..n_entities {
265 for t in t_start..t_total {
266 let i_cur = idx_map[eid][t];
267 let i_prev = idx_map[eid][t - 1];
268 let dy = y[i_cur] - y[i_prev];
269 dy_vec.push(dy);
270 let mut row: Vec<F> = Vec::new();
271 for lag in 1..=n_lags {
273 if t >= lag + 1 {
274 let i_l = idx_map[eid][t - lag];
275 let i_l1 = idx_map[eid][t - lag - 1];
276 row.push(y[i_l] - y[i_l1]);
277 } else {
278 row.push(F::zero());
279 }
280 }
281 for j in 0..kx {
283 row.push(x[[i_cur, j]] - x[[i_prev, j]]);
284 }
285 dx_rows.push(row);
286 }
287 }
288
289 let k_reg = n_lags + kx; let dx_flat: Vec<F> = dx_rows.iter().flat_map(|r| r.iter().copied()).collect();
291 let dx = Array2::from_shape_vec((nd, k_reg), dx_flat)
292 .map_err(|e| StatsError::ComputationError(format!("reshape dx: {e}")))?;
293 let dy = Array1::from(dy_vec);
294
295 let max_inst_per = t_total - n_lags - 1; let n_inst_cols = max_inst_per * (max_inst_per + 1) / 2 + kx; let mut z_rows: Vec<Vec<F>> = Vec::with_capacity(nd);
306 for eid in 0..n_entities {
307 for t in t_start..t_total {
308 let mut z_row = vec![F::zero(); n_inst_cols];
309 let avail = t - n_lags; let block_start: usize = if avail > 0 {
313 (avail - 1) * avail / 2
314 } else {
315 0
316 };
317 for s in 0..avail {
318 let inst_t = t - n_lags - 1 - s; if inst_t < t_total {
321 let flat_idx = block_start + s;
322 if flat_idx < max_inst_per * (max_inst_per + 1) / 2 {
323 z_row[flat_idx] = y[idx_map[eid][inst_t]];
324 }
325 }
326 }
327 let base = max_inst_per * (max_inst_per + 1) / 2;
329 for j in 0..kx {
330 z_row[base + j] = x[[idx_map[eid][t], j]];
331 }
332 z_rows.push(z_row);
333 }
334 }
335 let z_flat: Vec<F> = z_rows.iter().flat_map(|r| r.iter().copied()).collect();
336 let z = Array2::from_shape_vec((nd, n_inst_cols), z_flat)
337 .map_err(|e| StatsError::ComputationError(format!("reshape z: {e}")))?;
338
339 let ztzt = matmul(&transpose(&z), &z)?;
343 let w1 = identity_if_singular(&ztzt)?;
344
345 let (coeffs1, resid1) = gmm_estimator(&dx, &dy, &z, &w1)?;
347
348 let mut meat = Array2::<F>::zeros((n_inst_cols, n_inst_cols));
350 for i in 0..nd {
351 let ei2 = resid1[i] * resid1[i];
352 for a in 0..n_inst_cols {
353 for b in 0..n_inst_cols {
354 meat[[a, b]] = meat[[a, b]] + z[[i, a]] * z[[i, b]] * ei2;
355 }
356 }
357 }
358 let w2 = identity_if_singular(&meat)?;
359 let (coeffs2, resid2) = gmm_estimator(&dx, &dy, &z, &w2)?;
360
361 let n_f = F::from_usize(nd).unwrap_or(F::one());
365 let ztx = matmul(&transpose(&z), &dx)?;
366 let wztx = matmul(&w2, &ztx)?;
367 let xtzwztx = matmul(&transpose(&ztx), &wztx)?;
368 let std_errors = gmm_se(&xtzwztx, &z, &dx, &resid2, &w2)?;
369
370 let z_stats: Array1<F> = coeffs2
371 .iter()
372 .zip(std_errors.iter())
373 .map(|(&c, &se)| if se > F::zero() { c / se } else { F::zero() })
374 .collect();
375
376 let sargan = sargan_test(&resid2, &z, k_reg)?;
379
380 let ar1 = ar_test(&resid2, nd, n_entities, 1);
382 let ar2 = ar_test(&resid2, nd, n_entities, 2);
383
384 Ok(DynamicPanelResult {
385 coefficients: coeffs2,
386 std_errors,
387 z_stats,
388 sargan,
389 ar1,
390 ar2,
391 n_obs: nd,
392 n_instruments: n_inst_cols,
393 residuals: resid2,
394 })
395 }
396}
397
398pub struct BlundellBond;
407
408impl BlundellBond {
409 pub fn fit<F>(
418 y: &ArrayView1<F>,
419 x: &ArrayView2<F>,
420 entity: &[usize],
421 time: &[usize],
422 n_lags: usize,
423 ) -> StatsResult<DynamicPanelResult<F>>
424 where
425 F: Float
426 + std::iter::Sum
427 + std::fmt::Debug
428 + std::fmt::Display
429 + scirs2_core::numeric::NumAssign
430 + scirs2_core::numeric::One
431 + scirs2_core::ndarray::ScalarOperand
432 + FromPrimitive
433 + Send
434 + Sync
435 + 'static,
436 {
437 let n = y.len();
438 let (nx, kx) = x.dim();
439 if nx != n || entity.len() != n || time.len() != n {
440 return Err(StatsError::DimensionMismatch(
441 "y, x, entity, time lengths must match".to_string(),
442 ));
443 }
444 let n_entities = entity.iter().copied().max().map(|m| m + 1).unwrap_or(0);
445 let t_total = time.iter().copied().max().map(|m| m + 1).unwrap_or(0);
446 if t_total < n_lags + 2 {
447 return Err(StatsError::InsufficientData(format!(
448 "Need >= {} time periods",
449 n_lags + 2
450 )));
451 }
452 let t_per = n / n_entities;
453 if t_per * n_entities != n {
454 return Err(StatsError::InvalidArgument(
455 "BB-GMM: requires balanced panel".to_string(),
456 ));
457 }
458
459 let mut idx_map = vec![vec![0usize; t_total]; n_entities];
460 for i in 0..n {
461 idx_map[entity[i]][time[i]] = i;
462 }
463
464 let t_fd_start = n_lags + 1; let nd_fd = n_entities * (t_total - t_fd_start);
466 let nd_lev = n_entities * (t_total - n_lags - 1); let n_total = nd_fd + nd_lev;
469 let k_reg = n_lags + kx;
470
471 let mut dy_vec: Vec<F> = Vec::with_capacity(n_total);
473 let mut dx_rows: Vec<Vec<F>> = Vec::with_capacity(n_total);
474 let mut z_rows: Vec<Vec<F>> = Vec::with_capacity(n_total);
475
476 let max_inst_ab = t_total - n_lags - 1;
478 let n_inst_ab = max_inst_ab * (max_inst_ab + 1) / 2;
479 let n_inst_bb = t_total - n_lags - 1; let n_inst_cols = n_inst_ab + kx + n_inst_bb + kx;
481
482 for eid in 0..n_entities {
484 for t in t_fd_start..t_total {
485 let i_cur = idx_map[eid][t];
486 let i_prev = idx_map[eid][t - 1];
487 let dy = y[i_cur] - y[i_prev];
488 dy_vec.push(dy);
489
490 let mut row: Vec<F> = Vec::new();
491 for lag in 1..=n_lags {
492 if t >= lag + 1 {
493 let il = idx_map[eid][t - lag];
494 let il1 = idx_map[eid][t - lag - 1];
495 row.push(y[il] - y[il1]);
496 } else {
497 row.push(F::zero());
498 }
499 }
500 for j in 0..kx {
501 row.push(x[[i_cur, j]] - x[[i_prev, j]]);
502 }
503 dx_rows.push(row);
504
505 let mut z_row = vec![F::zero(); n_inst_cols];
507 let avail = t - n_lags;
508 let block_start = if avail > 0 {
509 (avail - 1) * avail / 2
510 } else {
511 0
512 };
513 for s in 0..avail {
514 let inst_t = t - n_lags - 1 - s;
515 if inst_t < t_total {
516 let fi = block_start + s;
517 if fi < n_inst_ab {
518 z_row[fi] = y[idx_map[eid][inst_t]];
519 }
520 }
521 }
522 for j in 0..kx {
523 z_row[n_inst_ab + j] = x[[i_cur, j]];
524 }
525 z_rows.push(z_row);
526 }
527 }
528
529 for eid in 0..n_entities {
532 for t in (n_lags + 1)..t_total {
533 let i_cur = idx_map[eid][t];
534 let i_prev = idx_map[eid][t - 1];
535 dy_vec.push(y[i_cur]); let mut row: Vec<F> = Vec::new();
537 for lag in 1..=n_lags {
538 row.push(y[idx_map[eid][t - lag]]);
539 }
540 for j in 0..kx {
541 row.push(x[[i_cur, j]]);
542 }
543 dx_rows.push(row);
544
545 let mut z_row = vec![F::zero(); n_inst_cols];
546 if t >= n_lags + 1 {
548 let lag_idx = n_inst_ab + kx + (t - n_lags - 1).min(n_inst_bb - 1);
549 let dy_lag = y[i_prev]
550 - if t >= 2 {
551 y[idx_map[eid][t - 2]]
552 } else {
553 F::zero()
554 };
555 if lag_idx < n_inst_cols - kx {
556 z_row[lag_idx] = dy_lag;
557 }
558 }
559 for j in 0..kx {
560 z_row[n_inst_ab + kx + n_inst_bb + j] = x[[i_cur, j]];
561 }
562 z_rows.push(z_row);
563 }
564 }
565
566 let n_all = dy_vec.len();
567 let dx_flat: Vec<F> = dx_rows.iter().flat_map(|r| r.iter().copied()).collect();
568 let z_flat: Vec<F> = z_rows.iter().flat_map(|r| r.iter().copied()).collect();
569 let dx = Array2::from_shape_vec((n_all, k_reg), dx_flat)
570 .map_err(|e| StatsError::ComputationError(format!("reshape dx: {e}")))?;
571 let dy = Array1::from(dy_vec);
572 let z = Array2::from_shape_vec((n_all, n_inst_cols), z_flat)
573 .map_err(|e| StatsError::ComputationError(format!("reshape z: {e}")))?;
574
575 let ztzt = matmul(&transpose(&z), &z)?;
577 let w1 = identity_if_singular(&ztzt)?;
578 let (coeffs1, resid1) = gmm_estimator(&dx, &dy, &z, &w1)?;
579
580 let mut meat = Array2::<F>::zeros((n_inst_cols, n_inst_cols));
582 for i in 0..n_all {
583 let ei2 = resid1[i] * resid1[i];
584 for a in 0..n_inst_cols {
585 for b in 0..n_inst_cols {
586 meat[[a, b]] = meat[[a, b]] + z[[i, a]] * z[[i, b]] * ei2;
587 }
588 }
589 }
590 let w2 = identity_if_singular(&meat)?;
591 let (coeffs2, resid2) = gmm_estimator(&dx, &dy, &z, &w2)?;
592
593 let ztx = matmul(&transpose(&z), &dx)?;
594 let wztx = matmul(&w2, &ztx)?;
595 let xtzwztx = matmul(&transpose(&ztx), &wztx)?;
596 let std_errors = gmm_se(&xtzwztx, &z, &dx, &resid2, &w2)?;
597 let z_stats: Array1<F> = coeffs2
598 .iter()
599 .zip(std_errors.iter())
600 .map(|(&c, &se)| if se > F::zero() { c / se } else { F::zero() })
601 .collect();
602
603 let sargan = sargan_test(&resid2, &z, k_reg)?;
604 let ar1 = ar_test(&resid2, n_all, n_entities, 1);
605 let ar2 = ar_test(&resid2, n_all, n_entities, 2);
606
607 Ok(DynamicPanelResult {
608 coefficients: coeffs2,
609 std_errors,
610 z_stats,
611 sargan,
612 ar1,
613 ar2,
614 n_obs: n_all,
615 n_instruments: n_inst_cols,
616 residuals: resid2,
617 })
618 }
619}
620
621pub struct SarganTest;
629
630impl SarganTest {
631 pub fn test<F>(
633 residuals: &ArrayView1<F>,
634 z: &ArrayView2<F>,
635 n_regressors: usize,
636 ) -> StatsResult<SarganTestResult<F>>
637 where
638 F: Float
639 + std::iter::Sum
640 + std::fmt::Debug
641 + std::fmt::Display
642 + scirs2_core::numeric::NumAssign
643 + scirs2_core::numeric::One
644 + scirs2_core::ndarray::ScalarOperand
645 + FromPrimitive
646 + Send
647 + Sync
648 + 'static,
649 {
650 let e_owned = residuals.to_owned();
651 let z_owned = z.to_owned();
652 sargan_test(&e_owned, &z_owned, n_regressors)
653 }
654}
655
656fn sargan_test<F>(e: &Array1<F>, z: &Array2<F>, k: usize) -> StatsResult<SarganTestResult<F>>
662where
663 F: Float
664 + std::iter::Sum
665 + std::fmt::Debug
666 + std::fmt::Display
667 + scirs2_core::numeric::NumAssign
668 + scirs2_core::numeric::One
669 + scirs2_core::ndarray::ScalarOperand
670 + FromPrimitive
671 + Send
672 + Sync
673 + 'static,
674{
675 let (nd, l) = z.dim();
676 let ze = mat_vec(&transpose(z), e)?; let ztz = matmul(&transpose(z), z)?;
678 let inv_ztz_ze = solve(&ztz.view(), &ze.view(), None)
679 .map_err(|err| StatsError::ComputationError(format!("Sargan solve: {err}")))?;
680
681 let j_num: F = ze.iter().zip(inv_ztz_ze.iter()).map(|(&a, &b)| a * b).sum();
682 let n_f = F::from_usize(nd).unwrap_or(F::one());
683 let sigma2 = e.iter().map(|&r| r * r).sum::<F>() / n_f;
684 let j_stat = if sigma2 > F::zero() {
685 j_num / sigma2
686 } else {
687 F::zero()
688 };
689
690 let df = if l > k { l - k } else { 1 };
691 let p_value = chi2_upper_pvalue(j_stat, df);
692 Ok(SarganTestResult {
693 j_stat,
694 df,
695 p_value,
696 })
697}
698
699fn ar_test<F: Float + FromPrimitive>(
701 e: &Array1<F>,
702 nd: usize,
703 n_entities: usize,
704 order: usize,
705) -> ARTestResult<F> {
706 if nd <= order || n_entities == 0 {
707 return ARTestResult {
708 z_stat: F::zero(),
709 p_value: F::one(),
710 };
711 }
712 let t_per = nd / n_entities;
713 if t_per < order + 1 {
714 return ARTestResult {
715 z_stat: F::zero(),
716 p_value: F::one(),
717 };
718 }
719 let mut num = F::zero();
721 let mut denom_ee = F::zero();
722 let mut denom_elag = F::zero();
723 let mut count = 0usize;
724 for eid in 0..n_entities {
725 let base = eid * t_per;
726 for t in order..t_per {
727 let e_t = e[base + t];
728 let e_lag = e[base + t - order];
729 num = num + e_t * e_lag;
730 denom_ee = denom_ee + e_t * e_t;
731 denom_elag = denom_elag + e_lag * e_lag;
732 count += 1;
733 }
734 }
735 if count == 0 || denom_ee <= F::zero() || denom_elag <= F::zero() {
736 return ARTestResult {
737 z_stat: F::zero(),
738 p_value: F::one(),
739 };
740 }
741 let n_f = F::from_usize(count).unwrap_or(F::one());
742 let var_approx = (denom_ee * denom_elag).sqrt() / n_f;
743 let z_stat = if var_approx > F::zero() {
744 num / var_approx
745 } else {
746 F::zero()
747 };
748 let p_value = two_sided_normal_pvalue(z_stat);
749 ARTestResult { z_stat, p_value }
750}
751
752fn gmm_se<F>(
754 xtzwztx: &Array2<F>,
755 z: &Array2<F>,
756 x: &Array2<F>,
757 e: &Array1<F>,
758 w: &Array2<F>,
759) -> StatsResult<Array1<F>>
760where
761 F: Float
762 + std::iter::Sum
763 + std::fmt::Debug
764 + std::fmt::Display
765 + scirs2_core::numeric::NumAssign
766 + scirs2_core::numeric::One
767 + scirs2_core::ndarray::ScalarOperand
768 + FromPrimitive
769 + Send
770 + Sync
771 + 'static,
772{
773 let k = xtzwztx.nrows();
774 let (nd, l) = z.dim();
777 let mut s_meat = Array2::<F>::zeros((l, l));
778 for i in 0..nd {
779 let ei2 = e[i] * e[i];
780 for a in 0..l {
781 for b in 0..l {
782 s_meat[[a, b]] = s_meat[[a, b]] + z[[i, a]] * z[[i, b]] * ei2;
783 }
784 }
785 }
786 let n_f = F::from_usize(nd).unwrap_or(F::one());
789 let k_f = F::from_usize(k).unwrap_or(F::one());
790 let sigma2 = e.iter().map(|&r| r * r).sum::<F>() / (n_f - k_f);
791
792 let mut se = Array1::zeros(k);
793 for j in 0..k {
794 let mut ej = Array1::zeros(k);
795 ej[j] = F::one();
796 let vj = solve(&xtzwztx.view(), &ej.view(), None)
797 .map_err(|e2| StatsError::ComputationError(format!("gmm_se solve: {e2}")))?;
798 let var_j = vj[j] * sigma2;
799 se[j] = if var_j >= F::zero() {
800 var_j.sqrt()
801 } else {
802 F::zero()
803 };
804 }
805 Ok(se)
806}
807
808fn identity_if_singular<F>(m: &Array2<F>) -> StatsResult<Array2<F>>
810where
811 F: Float
812 + std::iter::Sum
813 + std::fmt::Debug
814 + std::fmt::Display
815 + scirs2_core::numeric::NumAssign
816 + scirs2_core::numeric::One
817 + scirs2_core::ndarray::ScalarOperand
818 + FromPrimitive
819 + Send
820 + Sync
821 + 'static,
822{
823 let k = m.nrows();
824 let mut inv = Array2::zeros((k, k));
826 let mut ok = true;
827 for j in 0..k {
828 let mut ej = Array1::zeros(k);
829 ej[j] = F::one();
830 match solve(&m.view(), &ej.view(), None) {
831 Ok(v) => {
832 for i in 0..k {
833 inv[[i, j]] = v[i];
834 }
835 }
836 Err(_) => {
837 ok = false;
838 break;
839 }
840 }
841 }
842 if ok {
843 Ok(inv)
844 } else {
845 let mut id = Array2::zeros((k, k));
847 for i in 0..k {
848 id[[i, i]] = F::one();
849 }
850 Ok(id)
851 }
852}
853
854fn chi2_upper_pvalue<F: Float + FromPrimitive>(chi2: F, df: usize) -> F {
856 if chi2 <= F::zero() {
857 return F::one();
858 }
859 let k = F::from_usize(df).unwrap_or(F::one());
860 let two = F::from_f64(2.0).unwrap_or(F::one());
861 let nine = F::from_f64(9.0).unwrap_or(F::one());
862 let factor = two / (nine * k);
863 let x = (chi2 / k).cbrt();
864 let mu = F::one() - factor;
865 let sigma = factor.sqrt();
866 let z = (x - mu) / sigma;
867 p_normal_upper(z)
868}
869
870fn two_sided_normal_pvalue<F: Float + FromPrimitive>(z: F) -> F {
872 let two = F::from_f64(2.0).unwrap_or(F::one());
873 let abs_z = if z < F::zero() { -z } else { z };
874 let p = p_normal_upper(abs_z);
875 let two_p = two * p;
876 if two_p > F::one() {
877 F::one()
878 } else {
879 two_p
880 }
881}
882
883fn p_normal_upper<F: Float + FromPrimitive>(z: F) -> F {
884 let p1 = F::from_f64(0.2316419).unwrap_or(F::zero());
885 let b1 = F::from_f64(0.319381530).unwrap_or(F::zero());
886 let b2 = F::from_f64(-0.356563782).unwrap_or(F::zero());
887 let b3 = F::from_f64(1.781477937).unwrap_or(F::zero());
888 let b4 = F::from_f64(-1.821255978).unwrap_or(F::zero());
889 let b5 = F::from_f64(1.330274429).unwrap_or(F::zero());
890 let sqrt2pi_inv = F::from_f64(0.39894228).unwrap_or(F::zero());
891 let two = F::from_f64(2.0).unwrap_or(F::one());
892
893 let abs_z = if z < F::zero() { -z } else { z };
894 let t = F::one() / (F::one() + p1 * abs_z);
895 let poly = t * (b1 + t * (b2 + t * (b3 + t * (b4 + t * b5))));
896 let phi = sqrt2pi_inv * (-(abs_z * abs_z) / two).exp();
897 let p_upper = (phi * poly).max(F::zero()).min(F::one());
898 if z >= F::zero() {
899 p_upper
900 } else {
901 F::one() - p_upper
902 }
903}
904
905#[cfg(test)]
910mod tests {
911 use super::*;
912 use scirs2_core::ndarray::{Array1, Array2};
913
914 fn make_dynamic_panel(
915 n_ent: usize,
916 t_per: usize,
917 alpha: f64,
918 beta: f64,
919 ) -> (Array1<f64>, Array2<f64>, Vec<usize>, Vec<usize>) {
920 let n = n_ent * t_per;
921 let entity: Vec<usize> = (0..n_ent)
922 .flat_map(|e| std::iter::repeat(e).take(t_per))
923 .collect();
924 let time: Vec<usize> = (0..t_per).cycle().take(n).collect();
925 let mut x_vals = vec![0.0_f64; n];
926 let mut y_vals = vec![0.0_f64; n];
927 for eid in 0..n_ent {
928 let u_i = (eid as f64) * 0.2;
929 for t in 0..t_per {
930 let idx = eid * t_per + t;
931 x_vals[idx] = (t as f64) * 0.5 + 1.0;
932 if t == 0 {
933 y_vals[idx] = u_i + x_vals[idx] * beta + 0.1;
934 } else {
935 let idx_prev = eid * t_per + t - 1;
936 y_vals[idx] =
937 alpha * y_vals[idx_prev] + beta * x_vals[idx] + u_i + 0.01 * (t as f64);
938 }
939 }
940 }
941 let x = Array2::from_shape_vec((n, 1), x_vals).unwrap();
942 let y = Array1::from(y_vals);
943 (y, x, entity, time)
944 }
945
946 #[test]
947 fn test_arellano_bond_fit() {
948 let (y, x, entity, time) = make_dynamic_panel(8, 6, 0.5, 1.0);
949 let result =
950 ArellanoBlond::fit(&y.view(), &x.view(), &entity, &time, 1).expect("AB-GMM failed");
951 assert!(result.n_obs > 0);
953 assert_eq!(result.coefficients.len(), 2); assert!(result.sargan.p_value >= 0.0);
955 }
956
957 #[test]
958 fn test_blundell_bond_fit() {
959 let (y, x, entity, time) = make_dynamic_panel(8, 6, 0.5, 1.0);
960 let result =
961 BlundellBond::fit(&y.view(), &x.view(), &entity, &time, 1).expect("BB-GMM failed");
962 assert!(result.n_obs > 0);
963 assert_eq!(result.coefficients.len(), 2);
964 }
965
966 #[test]
967 fn test_sargan_test_standalone() {
968 let (y, x, entity, time) = make_dynamic_panel(8, 6, 0.5, 1.0);
969 let result =
970 ArellanoBlond::fit(&y.view(), &x.view(), &entity, &time, 1).expect("AB-GMM failed");
971 assert!(result.sargan.j_stat >= 0.0);
973 assert!(result.sargan.p_value >= 0.0 && result.sargan.p_value <= 1.0);
974 }
975}