1use faer::{
5 Accum, Mat, MatRef, Par, Scale,
6 linalg::solvers::{PartialPivLu, Solve},
7};
8use rand::prelude::*;
9use rand_distr::Distribution;
10use rand_distr::Normal;
11use rayon::prelude::*;
12
13use crate::prelude::*;
14use bixverse_core::core::math::matrix_helpers::scale_matrix_col;
15use bixverse_core::core::math::pca_svd::randomised_svd;
16use bixverse_core::utils::faer_parallelism;
17use bixverse_core::utils::gemm::gemm;
18
19type IcaRes<T> = (Mat<T>, T);
30
31#[derive(Clone, Debug, Default)]
37pub enum IcaType {
38 #[default]
40 LogCosh,
41 Exp,
43}
44
45pub fn parse_ica_type(s: &str) -> Option<IcaType> {
55 match s.to_lowercase().as_str() {
56 "exp" => Some(IcaType::Exp),
57 "logcosh" => Some(IcaType::LogCosh),
58 _ => None,
59 }
60}
61
62#[derive(Clone, Debug)]
68pub struct IcaParams<T: BixverseFloat> {
69 pub maxit: usize,
71 pub alpha: T,
73 pub tol: T,
75 pub verbose: bool,
77}
78
79pub fn prepare_whitening<T: BixverseFloat>(
105 x: MatRef<T>,
106 fast_svd: bool,
107 seed: usize,
108 rank: usize,
109 oversampling: Option<usize>,
110 n_power_iter: Option<usize>,
111) -> Result<(Mat<T>, Mat<T>), MethodsErrors> {
112 let n = x.nrows();
113
114 let centered = scale_matrix_col(&x, false);
115
116 let centered = centered.transpose();
117
118 let n_recip = T::from_usize(n).unwrap().recip();
119 let v = Scale(n_recip) * (centered * centered.transpose());
120
121 let k = if fast_svd {
122 let svd_res = randomised_svd(v.as_ref(), rank, seed, oversampling, n_power_iter)?;
123 let s: Vec<T> = svd_res.s.iter().map(|x| x.sqrt().recip()).collect();
124 let d = faer_diagonal_from_vec(s);
125 d * svd_res.u.transpose()
126 } else {
127 let svd_res = v
128 .thin_svd()
129 .map_err(|e| CoreErrors::FaerSvdError(format!("{e:?}")))?;
130 let s = svd_res
131 .S()
132 .column_vector()
133 .iter()
134 .map(|x| x.sqrt().recip())
135 .collect::<Vec<_>>();
136 let d = faer_diagonal_from_vec(s);
137 let u = svd_res.U();
138 let u_t = u.transpose();
139 d * u_t
140 };
141
142 Ok((centered.cloned(), k))
143}
144
145fn update_mix_mat<T: BixverseFloat>(w: MatRef<T>) -> Result<Mat<T>, MethodsErrors> {
155 let svd_res = w
157 .thin_svd()
158 .map_err(|e| CoreErrors::FaerSvdError(format!("{e:?}")))?;
159
160 let s = svd_res.S();
161 let u = svd_res.U();
162 let s = s
163 .column_vector()
164 .iter()
165 .map(|x| x.recip())
166 .collect::<Vec<_>>();
167 let d = faer_diagonal_from_vec(s);
168
169 Ok(u * d * u.transpose() * w)
170}
171
172fn create_w_init<T: BixverseFloat>(n_comp: usize, seed: u64) -> Mat<T> {
184 let mut rng = StdRng::seed_from_u64(seed);
185 let normal = Normal::new(0.0, 1.0).unwrap();
186 let vec_size = n_comp.pow(2);
187 let data: Vec<T> = (0..vec_size)
188 .map(|_| T::from_f64(normal.sample(&mut rng)).unwrap())
189 .collect();
190
191 Mat::from_fn(n_comp, n_comp, |i, j| data[i + j * n_comp])
192}
193
194pub struct IcaCvData<T: BixverseFloat> {
200 pub pre_white_matrices: Vec<Mat<T>>,
202 pub k_matrices: Vec<Mat<T>>,
204}
205
206impl<T: BixverseFloat> IcaCvData<T> {
207 pub fn create_from_data(
224 x: MatRef<T>,
225 num_folds: usize,
226 seed: usize,
227 rank: Option<usize>,
228 ) -> Result<IcaCvData<T>, MethodsErrors> {
229 let no_samples = x.nrows();
230 let no_features = x.ncols();
231 let mut indices: Vec<usize> = (0..no_samples).collect();
232 let mut rng = StdRng::seed_from_u64(seed as u64);
233 indices.shuffle(&mut rng);
234
235 let svd_rank = rank.unwrap_or(no_features);
236
237 let fold_size = no_samples / num_folds;
238 let remainder = no_samples % num_folds;
239
240 let mut folds = Vec::with_capacity(num_folds);
241 let mut start = 0;
242
243 for idx in 0..num_folds {
244 let current_fold_size = if idx < remainder {
245 fold_size + 1
246 } else {
247 fold_size
248 };
249
250 let end = start + current_fold_size;
251 folds.push(indices[start..end].to_vec());
252 start = end;
253 }
254
255 let k_x_matrices: Vec<(Mat<T>, Mat<T>)> = folds
256 .par_iter()
257 .map(|test_indices| {
258 let mut is_test = vec![false; no_samples];
259 for &idx in test_indices {
260 is_test[idx] = true;
261 }
262 let train_indices: Vec<usize> = indices
263 .iter()
264 .filter(|&&idx| !is_test[idx])
265 .cloned()
266 .collect();
267 let x_i = Mat::<T>::from_fn(train_indices.len(), no_features, |new_row, j| {
268 x[(train_indices[new_row], j)]
269 });
270 prepare_whitening(x_i.as_ref(), true, seed + 1, svd_rank, None, None)
271 })
272 .collect::<Result<Vec<_>, _>>()?;
273
274 let mut pre_white_matrices = Vec::with_capacity(num_folds);
275 let mut k_matrices = Vec::with_capacity(num_folds);
276
277 for (x_i, k_i) in k_x_matrices {
278 pre_white_matrices.push(x_i);
279 k_matrices.push(k_i);
280 }
281
282 Ok(Self {
283 pre_white_matrices,
284 k_matrices,
285 })
286 }
287}
288
289fn fast_ica_core<T: BixverseFloat>(
314 par: Par,
315 x: MatRef<T>,
316 w_init: MatRef<T>,
317 tol: T,
318 maxit: usize,
319 verbose: bool,
320 contrast: impl Fn(T) -> (T, T),
321) -> Result<IcaRes<T>, MethodsErrors> {
322 let p = x.ncols();
323 let mut w = update_mix_mat(w_init)?;
324 let n_comp = w.nrows();
325 let mut lim = vec![T::from_f64(1000.0).unwrap(); maxit];
326
327 let p_recip = T::from_usize(p).unwrap().recip();
328 let ones = Mat::<T>::from_fn(p, 1, |_, _| T::one());
329
330 let mut wx = Mat::<T>::zeros(n_comp, p);
332 let mut gwx = Mat::<T>::zeros(n_comp, p);
333 let mut gwx_2 = Mat::<T>::zeros(n_comp, p);
334 let mut v1 = Mat::<T>::zeros(n_comp, n_comp);
335 let mut row_sums = Mat::<T>::zeros(n_comp, 1);
336
337 let mut it = 0;
338
339 while it < maxit && lim[it] > tol {
340 gemm(wx.as_mut(), Accum::Replace, w.as_ref(), x, T::one(), par);
341
342 for j in 0..p {
343 let wx_col = wx.col_as_slice(j);
344 let gwx_col = gwx.col_as_slice_mut(j);
345 let gwx_2_col = gwx_2.col_as_slice_mut(j);
346 for ((&u, g), g2) in wx_col.iter().zip(gwx_col).zip(gwx_2_col) {
347 (*g, *g2) = contrast(u);
348 }
349 }
350
351 gemm(
352 v1.as_mut(),
353 Accum::Replace,
354 gwx.as_ref(),
355 x.transpose(),
356 p_recip,
357 par,
358 );
359 gemm(
360 row_sums.as_mut(),
361 Accum::Replace,
362 gwx_2.as_ref(),
363 ones.as_ref(),
364 T::one(),
365 par,
366 );
367
368 let v2 = Mat::<T>::from_fn(n_comp, n_comp, |i, j| {
369 row_sums[(i, 0)] * p_recip * w[(i, j)]
370 });
371
372 let w1 = update_mix_mat((v1.as_ref() - v2.as_ref()).as_ref())?;
373
374 let tol_it = (0..n_comp)
376 .map(|i| {
377 let d = (0..n_comp).fold(T::zero(), |acc, c| acc + w1[(i, c)] * w[(i, c)]);
378 (d.abs() - T::one()).abs()
379 })
380 .fold(T::neg_infinity(), |a, b| a.max(b));
381
382 if it + 1 < maxit {
383 lim[it + 1] = tol_it
384 }
385
386 if verbose {
387 println!("Iteration: {:?}, tol: {:?}", it + 1, tol_it)
388 }
389
390 w = w1;
391
392 it += 1;
393 }
394
395 let min_tol = array_min(&lim);
396
397 Ok((w, min_tol))
398}
399
400fn logcosh_contrast<T: BixverseFloat>(alpha: T) -> impl Fn(T) -> (T, T) {
410 move |u| {
411 let g = (alpha * u).tanh();
412 (g, alpha * (T::one() - g * g))
413 }
414}
415
416fn exp_contrast<T: BixverseFloat>() -> impl Fn(T) -> (T, T) {
422 let neg_half = T::from_f64(-0.5).unwrap();
423 move |u| {
424 let e = (neg_half * u * u).exp();
425 (u * e, (T::one() - u * u) * e)
426 }
427}
428
429pub fn fast_ica_logcosh<T: BixverseFloat>(
445 x: MatRef<T>,
446 w_init: MatRef<T>,
447 tol: T,
448 alpha: T,
449 maxit: usize,
450 verbose: bool,
451) -> Result<IcaRes<T>, MethodsErrors> {
452 fast_ica_core(
453 faer_parallelism(),
454 x,
455 w_init,
456 tol,
457 maxit,
458 verbose,
459 logcosh_contrast(alpha),
460 )
461}
462
463pub fn fast_ica_exp<T: BixverseFloat>(
478 x: MatRef<T>,
479 w_init: MatRef<T>,
480 tol: T,
481 maxit: usize,
482 verbose: bool,
483) -> Result<IcaRes<T>, MethodsErrors> {
484 fast_ica_core(
485 faer_parallelism(),
486 x,
487 w_init,
488 tol,
489 maxit,
490 verbose,
491 exp_contrast(),
492 )
493}
494
495pub fn stabilised_ica_iters<T: BixverseFloat>(
521 x_pre_whiten: MatRef<T>,
522 k: MatRef<T>,
523 no_comp: usize,
524 no_iters: usize,
525 ica_type: &str,
526 ica_params: IcaParams<T>,
527 random_seed: usize,
528) -> Result<(Mat<T>, Vec<bool>), MethodsErrors> {
529 let w_inits: Vec<Mat<T>> = (0..no_iters)
531 .map(|iter| create_w_init(no_comp, (random_seed + iter) as u64))
532 .collect();
533 let k_ncol = k.ncols();
534 let k_red = k.get(0..no_comp, 0..k_ncol);
535 let x_whiten = k_red * x_pre_whiten;
536
537 let ica_type = parse_ica_type(ica_type).unwrap();
538 let iter_res: Vec<(Mat<T>, T)> = w_inits
539 .par_iter()
540 .map(|w_init| match ica_type {
541 IcaType::Exp => fast_ica_core(
542 Par::Seq,
543 x_whiten.as_ref(),
544 w_init.as_ref(),
545 ica_params.tol,
546 ica_params.maxit,
547 ica_params.verbose,
548 exp_contrast(),
549 ),
550 IcaType::LogCosh => fast_ica_core(
551 Par::Seq,
552 x_whiten.as_ref(),
553 w_init.as_ref(),
554 ica_params.tol,
555 ica_params.maxit,
556 ica_params.verbose,
557 logcosh_contrast(ica_params.alpha),
558 ),
559 })
560 .collect::<Result<Vec<(Mat<T>, T)>, MethodsErrors>>()?;
561
562 let mut convergence = Vec::new();
563 let mut a_matrices = Vec::new();
564
565 for (a, final_tol) in iter_res {
566 a_matrices.push(a);
567 convergence.push(final_tol < ica_params.tol);
568 }
569
570 let s_matrices: Vec<Mat<T>> = a_matrices
571 .par_iter()
572 .map(|a| {
573 let mut w = Mat::<T>::zeros(a.nrows(), k_red.ncols());
574 gemm(
575 w.as_mut(),
576 Accum::Replace,
577 a.as_ref(),
578 k_red,
579 T::one(),
580 Par::Seq,
581 );
582 let mut to_solve = Mat::<T>::zeros(w.nrows(), w.nrows());
583 gemm(
584 to_solve.as_mut(),
585 Accum::Replace,
586 w.as_ref(),
587 w.transpose(),
588 T::one(),
589 Par::Seq,
590 );
591 let identity = Mat::<T>::identity(to_solve.nrows(), to_solve.ncols());
592 let lu = PartialPivLu::new(to_solve.as_ref());
593 let solved = lu.solve(&identity);
594 let mut out = Mat::<T>::zeros(w.ncols(), solved.ncols());
595 gemm(
596 out.as_mut(),
597 Accum::Replace,
598 w.transpose(),
599 solved.as_ref(),
600 T::one(),
601 Par::Seq,
602 );
603 out
604 })
605 .collect();
606
607 let s_combined = colbind_matrices(&s_matrices);
608
609 Ok((s_combined, convergence))
610}
611
612#[allow(clippy::too_many_arguments)]
633pub fn stabilised_ica_cv<T: BixverseFloat>(
634 x: MatRef<T>,
635 no_comp: usize,
636 no_folds: usize,
637 no_iters: usize,
638 ica_type: &str,
639 ica_params: IcaParams<T>,
640 ica_cv_data: Option<IcaCvData<T>>,
641 seed: usize,
642) -> Result<(Mat<T>, Vec<bool>), MethodsErrors> {
643 let cv_data = match ica_cv_data {
645 Some(data) => Ok(data), None => IcaCvData::create_from_data(x, no_folds, seed, Some(no_comp)),
647 }?;
648
649 let cv_res: Vec<(Mat<T>, Vec<bool>)> = cv_data
651 .k_matrices
652 .par_iter()
653 .zip(cv_data.pre_white_matrices)
654 .map(|(k_i, x_i)| {
655 stabilised_ica_iters(
656 x_i.as_ref(),
657 k_i.as_ref(),
658 no_comp,
659 no_iters,
660 ica_type,
661 ica_params.clone(),
662 seed + 2,
663 )
664 })
665 .collect::<Result<Vec<(Mat<T>, Vec<bool>)>, MethodsErrors>>()?;
666
667 let mut s_final = Vec::new();
668 let mut converged_final = Vec::new();
669
670 for (s_i, converged_i) in cv_res {
671 s_final.push(s_i);
672 converged_final.push(converged_i);
673 }
674
675 let s_final = colbind_matrices(&s_final);
676 let converged_final = flatten_vector(converged_final);
677
678 Ok((s_final, converged_final))
679}