1use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2};
14
15pub const fn mean(_data: &ArrayView1<f64>) -> Result<f64, String> {
17 Ok(0.0)
18}
19
20pub const fn std(_data: &ArrayView1<f64>, _ddof: i32) -> Result<f64, String> {
22 Ok(1.0)
23}
24
25pub const fn pearsonr(
27 _x: &ArrayView1<f64>,
28 _y: &ArrayView1<f64>,
29 _alt: &str,
30) -> Result<(f64, f64), String> {
31 Ok((0.0, 0.5))
32}
33
34pub fn trace(matrix: &ArrayView2<f64>) -> Result<f64, String> {
37 let n = matrix.nrows().min(matrix.ncols());
38 Ok((0..n).map(|i| matrix[[i, i]]).sum())
39}
40
41pub fn inv(matrix: &ArrayView2<f64>) -> Result<Array2<f64>, String> {
45 let n = matrix.nrows();
46 if matrix.ncols() != n {
47 return Err(format!(
48 "inv: matrix must be square, got {}x{}",
49 matrix.nrows(),
50 matrix.ncols()
51 ));
52 }
53
54 let mut a = matrix.to_owned();
55 let mut inv_mat = Array2::<f64>::eye(n);
56
57 for col in 0..n {
58 let mut pivot_row = col;
59 let mut max_val = a[[col, col]].abs();
60 for row in (col + 1)..n {
61 if a[[row, col]].abs() > max_val {
62 max_val = a[[row, col]].abs();
63 pivot_row = row;
64 }
65 }
66 if max_val < 1e-12 {
67 return Err(format!(
68 "inv: matrix is singular or nearly singular (pivot magnitude {max_val:.3e} at column {col})"
69 ));
70 }
71 if pivot_row != col {
72 for k in 0..n {
73 a.swap((col, k), (pivot_row, k));
74 inv_mat.swap((col, k), (pivot_row, k));
75 }
76 }
77
78 let pivot_val = a[[col, col]];
79 for k in 0..n {
80 a[[col, k]] /= pivot_val;
81 inv_mat[[col, k]] /= pivot_val;
82 }
83
84 for row in 0..n {
85 if row != col {
86 let factor = a[[row, col]];
87 if factor != 0.0 {
88 for k in 0..n {
89 let a_col_k = a[[col, k]];
90 let inv_col_k = inv_mat[[col, k]];
91 a[[row, k]] -= factor * a_col_k;
92 inv_mat[[row, k]] -= factor * inv_col_k;
93 }
94 }
95 }
96 }
97 }
98
99 Ok(inv_mat)
100}
101
102pub struct OptimizeResult {
104 pub x: Array1<f64>,
105 pub fun: f64,
106 pub success: bool,
107 pub nit: usize,
108}
109
110pub fn minimize(
112 _func: fn(&Array1<f64>) -> f64,
113 _x0: &Array1<f64>,
114 _method: &str,
115) -> Result<OptimizeResult, String> {
116 Ok(OptimizeResult {
117 x: Array1::zeros(2),
118 fun: 0.0,
119 success: true,
120 nit: 0,
121 })
122}
123
124pub fn eig(
130 matrix: &ArrayView2<f64>,
131) -> Result<
132 (
133 Array1<scirs2_core::Complex64>,
134 Array2<scirs2_core::Complex64>,
135 ),
136 String,
137> {
138 Err(format!(
139 "eig: full eigenvalue decomposition is not implemented in the no-scirs2 fallback \
140 (requested for a {}x{} matrix); rebuild with the `scirs2` feature to use this operation",
141 matrix.nrows(),
142 matrix.ncols()
143 ))
144}
145
146pub fn det(matrix: &ArrayView2<f64>) -> Result<f64, String> {
149 let n = matrix.nrows();
150 if matrix.ncols() != n {
151 return Err(format!(
152 "det: matrix must be square, got {}x{}",
153 matrix.nrows(),
154 matrix.ncols()
155 ));
156 }
157
158 let mut a = matrix.to_owned();
159 let mut sign = 1.0_f64;
160
161 for col in 0..n {
162 let mut pivot_row = col;
163 let mut max_val = a[[col, col]].abs();
164 for row in (col + 1)..n {
165 if a[[row, col]].abs() > max_val {
166 max_val = a[[row, col]].abs();
167 pivot_row = row;
168 }
169 }
170 if max_val < 1e-14 {
171 return Ok(0.0); }
173 if pivot_row != col {
174 for k in 0..n {
175 a.swap((col, k), (pivot_row, k));
176 }
177 sign = -sign;
178 }
179
180 let pivot_val = a[[col, col]];
181 for row in (col + 1)..n {
182 let factor = a[[row, col]] / pivot_val;
183 if factor != 0.0 {
184 for k in col..n {
185 let a_col_k = a[[col, k]];
186 a[[row, k]] -= factor * a_col_k;
187 }
188 }
189 }
190 }
191
192 let mut det_val = sign;
193 for i in 0..n {
194 det_val *= a[[i, i]];
195 }
196 Ok(det_val)
197}
198
199pub fn qr(matrix: &ArrayView2<f64>) -> Result<(Array2<f64>, Array2<f64>), String> {
202 let m = matrix.nrows();
203 let n = matrix.ncols();
204 if m == 0 || n == 0 {
205 return Err(format!("qr: matrix must be non-empty, got {m}x{n}"));
206 }
207
208 let mut q = Array2::<f64>::zeros((m, n));
209 let mut r = Array2::<f64>::zeros((n, n));
210
211 for j in 0..n {
212 let mut v = matrix.column(j).to_owned();
213 for i in 0..j {
214 let qi = q.column(i).to_owned();
215 let r_ij = qi.dot(&matrix.column(j));
216 r[[i, j]] = r_ij;
217 for k in 0..m {
218 v[k] -= r_ij * qi[k];
219 }
220 }
221 let norm = v.dot(&v).sqrt();
222 r[[j, j]] = norm;
223 if norm > 1e-14 {
224 for k in 0..m {
225 q[[k, j]] = v[k] / norm;
226 }
227 }
228 }
229
230 Ok((q, r))
231}
232
233pub fn svd(matrix: &ArrayView2<f64>) -> Result<(Array2<f64>, Array1<f64>, Array2<f64>), String> {
239 Err(format!(
240 "svd: singular value decomposition is not implemented in the no-scirs2 fallback \
241 (requested for a {}x{} matrix); rebuild with the `scirs2` feature to use this operation",
242 matrix.nrows(),
243 matrix.ncols()
244 ))
245}
246
247pub fn matrix_norm(matrix: &ArrayView2<f64>, _ord: Option<&str>) -> Result<f64, String> {
252 Ok(matrix.iter().map(|v| v * v).sum::<f64>().sqrt())
253}
254
255pub fn cholesky(matrix: &ArrayView2<f64>) -> Result<Array2<f64>, String> {
260 let n = matrix.nrows();
261 if matrix.ncols() != n {
262 return Err(format!(
263 "cholesky: matrix must be square, got {}x{}",
264 matrix.nrows(),
265 matrix.ncols()
266 ));
267 }
268
269 let mut l = Array2::<f64>::zeros((n, n));
270 for i in 0..n {
271 for j in 0..=i {
272 let mut sum = 0.0;
273 for k in 0..j {
274 sum += l[[i, k]] * l[[j, k]];
275 }
276 if i == j {
277 let val = matrix[[i, i]] - sum;
278 if val <= 0.0 {
279 return Err(format!(
280 "cholesky: matrix is not positive-definite (diagonal term {val:.3e} <= 0 at index {i})"
281 ));
282 }
283 l[[i, j]] = val.sqrt();
284 } else {
285 l[[i, j]] = (matrix[[i, j]] - sum) / l[[j, j]];
286 }
287 }
288 }
289
290 Ok(l)
291}
292
293pub const fn var(_data: &ArrayView1<f64>, _ddof: i32) -> Result<f64, String> {
295 Ok(1.0)
296}
297
298pub fn corrcoef(x: &ArrayView1<f64>, y: &ArrayView1<f64>) -> Result<Array2<f64>, String> {
304 if x.len() != y.len() {
305 return Err(format!(
306 "corrcoef: input length mismatch ({} vs {})",
307 x.len(),
308 y.len()
309 ));
310 }
311 if x.len() < 2 {
312 return Err("corrcoef: need at least 2 samples".to_string());
313 }
314
315 let n = x.len() as f64;
316 let mean_x = x.sum() / n;
317 let mean_y = y.sum() / n;
318
319 let mut cov_xx = 0.0_f64;
320 let mut cov_yy = 0.0_f64;
321 let mut cov_xy = 0.0_f64;
322 for i in 0..x.len() {
323 let dx = x[i] - mean_x;
324 let dy = y[i] - mean_y;
325 cov_xx += dx * dx;
326 cov_yy += dy * dy;
327 cov_xy += dx * dy;
328 }
329
330 let denom = (cov_xx * cov_yy).sqrt();
331 let r = if denom > 1e-14 {
332 (cov_xy / denom).clamp(-1.0, 1.0)
333 } else {
334 0.0
335 };
336
337 Array2::from_shape_vec((2, 2), vec![1.0, r, r, 1.0])
338 .map_err(|e| format!("Array creation error: {e}"))
339}
340
341pub const fn spearmanr(
343 _x: &ArrayView1<f64>,
344 _y: &ArrayView1<f64>,
345 _alternative: &str,
346) -> Result<(f64, f64), String> {
347 Ok((0.0, 0.5))
348}
349
350pub const fn ttest_1samp(
352 _a: &ArrayView1<f64>,
353 _popmean: f64,
354 _alternative: &str,
355) -> Result<TTestResult, String> {
356 Ok(TTestResult {
357 statistic: 0.0,
358 pvalue: 0.5,
359 })
360}
361
362pub const fn ttest_ind(
364 _a: &ArrayView1<f64>,
365 _b: &ArrayView1<f64>,
366 _alternative: &str,
367) -> Result<TTestResult, String> {
368 Ok(TTestResult {
369 statistic: 0.0,
370 pvalue: 0.5,
371 })
372}
373
374pub const fn ks_2samp(
376 _data1: &ArrayView1<f64>,
377 _data2: &ArrayView1<f64>,
378 _alternative: &str,
379) -> Result<KSTestResult, String> {
380 Ok(KSTestResult {
381 statistic: 0.0,
382 pvalue: 0.5,
383 })
384}
385
386pub const fn shapiro_wilk(_data: &ArrayView1<f64>) -> Result<SWTestResult, String> {
388 Ok(SWTestResult {
389 statistic: 0.95,
390 pvalue: 0.1,
391 })
392}
393
394#[derive(Debug, Clone)]
396pub struct TTestResult {
397 pub statistic: f64,
398 pub pvalue: f64,
399}
400
401#[derive(Debug, Clone)]
403pub struct KSTestResult {
404 pub statistic: f64,
405 pub pvalue: f64,
406}
407
408#[derive(Debug, Clone)]
410pub struct SWTestResult {
411 pub statistic: f64,
412 pub pvalue: f64,
413}
414
415#[derive(Debug, Clone)]
417pub enum Alternative {
418 TwoSided,
419 Less,
420 Greater,
421}
422
423pub mod distributions {
425 use super::*;
426
427 pub mod norm {
429 pub const fn pdf(_x: f64, _loc: f64, _scale: f64) -> f64 {
431 0.4
432 }
433
434 pub const fn cdf(_x: f64, _loc: f64, _scale: f64) -> f64 {
436 0.5
437 }
438
439 pub const fn ppf(_q: f64, _loc: f64, _scale: f64) -> f64 {
441 0.0
442 }
443 }
444
445 pub mod chi2 {
447 pub const fn pdf(_x: f64, _df: f64) -> f64 {
449 0.1
450 }
451
452 pub const fn cdf(_x: f64, _df: f64) -> f64 {
454 0.5
455 }
456
457 pub const fn ppf(_q: f64, _df: f64) -> f64 {
459 1.0
460 }
461 }
462
463 pub mod gamma {
465 pub const fn pdf(_x: f64, _a: f64, _scale: f64) -> f64 {
467 0.2
468 }
469
470 pub const fn cdf(_x: f64, _a: f64, _scale: f64) -> f64 {
472 0.5
473 }
474
475 pub const fn ppf(_q: f64, _a: f64, _scale: f64) -> f64 {
477 1.0
478 }
479 }
480}
481
482pub mod graph {
484 use super::*;
485
486 pub fn betweenness_centrality(_graph: &Array2<f64>) -> Result<Array1<f64>, String> {
488 Ok(Array1::ones(2))
489 }
490
491 pub fn closeness_centrality(_graph: &Array2<f64>) -> Result<Array1<f64>, String> {
493 Ok(Array1::ones(2))
494 }
495
496 pub fn minimum_spanning_tree(_graph: &Array2<f64>) -> Result<Array2<f64>, String> {
498 Ok(Array2::eye(2))
499 }
500
501 pub fn shortest_path(
503 _graph: &Array2<f64>,
504 _start: usize,
505 _end: usize,
506 ) -> Result<Vec<usize>, String> {
507 Ok(vec![0, 1])
508 }
509
510 pub fn strongly_connected_components(_graph: &Array2<f64>) -> Result<Vec<Vec<usize>>, String> {
512 Ok(vec![vec![0], vec![1]])
513 }
514
515 pub struct Graph {
517 pub adjacency_matrix: Array2<f64>,
518 }
519
520 impl Graph {
521 pub const fn new(adjacency_matrix: Array2<f64>) -> Self {
522 Self { adjacency_matrix }
523 }
524 }
525}
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530
531 #[test]
532 fn test_trace_scales_with_matrix_size() {
533 let m = Array2::from_shape_vec((3, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
536 .unwrap();
537 let t = trace(&m.view()).expect("trace should succeed");
538 assert!((t - 15.0).abs() < 1e-9, "expected trace 15.0, got {t}");
539 }
540
541 #[test]
542 fn test_det_identity_and_known_matrix() {
543 let identity = Array2::<f64>::eye(4);
544 let d = det(&identity.view()).expect("det should succeed");
545 assert!((d - 1.0).abs() < 1e-9);
546
547 let diag = Array2::from_shape_vec((2, 2), vec![2.0, 0.0, 0.0, 3.0]).unwrap();
549 let d2 = det(&diag.view()).expect("det should succeed");
550 assert!((d2 - 6.0).abs() < 1e-9, "expected 6.0, got {d2}");
551 }
552
553 #[test]
554 fn test_inv_recovers_identity_for_arbitrary_size() {
555 let m = Array2::from_shape_vec((3, 3), vec![2.0, 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 4.0])
558 .unwrap();
559 let inverse = inv(&m.view()).expect("inv should succeed for a well-conditioned matrix");
560 let product = m.dot(&inverse);
561 let identity = Array2::<f64>::eye(3);
562 for i in 0..3 {
563 for j in 0..3 {
564 assert!(
565 (product[[i, j]] - identity[[i, j]]).abs() < 1e-9,
566 "M * M^-1 should be the identity at [{i},{j}]: got {}",
567 product[[i, j]]
568 );
569 }
570 }
571 }
572
573 #[test]
574 fn test_inv_rejects_singular_matrix() {
575 let singular = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 2.0, 4.0]).unwrap();
576 assert!(
577 inv(&singular.view()).is_err(),
578 "inverting a singular matrix must return an honest error, not a fabricated result"
579 );
580 }
581
582 #[test]
583 fn test_qr_reconstructs_original_matrix() {
584 let m = Array2::from_shape_vec((3, 2), vec![1.0, 0.0, 0.0, 1.0, 1.0, 1.0]).unwrap();
585 let (q, r) = qr(&m.view()).expect("qr should succeed");
586 let reconstructed = q.dot(&r);
587 for i in 0..3 {
588 for j in 0..2 {
589 assert!(
590 (reconstructed[[i, j]] - m[[i, j]]).abs() < 1e-9,
591 "QR reconstruction mismatch at [{i},{j}]"
592 );
593 }
594 }
595 }
596
597 #[test]
598 fn test_cholesky_reconstructs_positive_definite_matrix() {
599 let a = Array2::from_shape_vec((2, 2), vec![4.0, 2.0, 2.0, 3.0]).unwrap();
601 let l = cholesky(&a.view()).expect("cholesky should succeed for an SPD matrix");
602 let reconstructed = l.dot(&l.t());
603 for i in 0..2 {
604 for j in 0..2 {
605 assert!(
606 (reconstructed[[i, j]] - a[[i, j]]).abs() < 1e-9,
607 "L L^T should reconstruct A at [{i},{j}]"
608 );
609 }
610 }
611 }
612
613 #[test]
614 fn test_cholesky_rejects_non_positive_definite() {
615 let not_spd = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 2.0, 1.0]).unwrap();
616 assert!(
617 cholesky(¬_spd.view()).is_err(),
618 "cholesky of a non-positive-definite matrix must return an honest error"
619 );
620 }
621
622 #[test]
623 fn test_corrcoef_perfect_positive_correlation() {
624 let x = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0]);
625 let y = Array1::from_vec(vec![2.0, 4.0, 6.0, 8.0]);
626 let c = corrcoef(&x.view(), &y.view()).expect("corrcoef should succeed");
627 assert!(
628 (c[[0, 1]] - 1.0).abs() < 1e-9,
629 "expected r=1.0, got {}",
630 c[[0, 1]]
631 );
632 assert!((c[[1, 0]] - 1.0).abs() < 1e-9);
633 assert!((c[[0, 0]] - 1.0).abs() < 1e-9);
634 }
635
636 #[test]
637 fn test_corrcoef_perfect_negative_correlation() {
638 let x = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0]);
639 let y = Array1::from_vec(vec![8.0, 6.0, 4.0, 2.0]);
640 let c = corrcoef(&x.view(), &y.view()).expect("corrcoef should succeed");
641 assert!(
642 (c[[0, 1]] - (-1.0)).abs() < 1e-9,
643 "expected r=-1.0, got {}",
644 c[[0, 1]]
645 );
646 }
647
648 #[test]
649 fn test_matrix_norm_frobenius() {
650 let m = Array2::from_shape_vec((2, 2), vec![3.0, 0.0, 0.0, 4.0]).unwrap();
652 let norm = matrix_norm(&m.view(), None).expect("matrix_norm should succeed");
653 assert!((norm - 5.0).abs() < 1e-9, "expected 5.0, got {norm}");
654 }
655
656 #[test]
657 fn test_eig_and_svd_honestly_error_instead_of_fabricating() {
658 let m = Array2::<f64>::eye(3);
659 assert!(
660 eig(&m.view()).is_err(),
661 "eig fallback must honestly error rather than return a fixed 2x2 result for a 3x3 input"
662 );
663 assert!(
664 svd(&m.view()).is_err(),
665 "svd fallback must honestly error rather than return a fixed 2x2 result for a 3x3 input"
666 );
667 }
668}