1use scirs2_core::ndarray::{Array1, Array2, ArrayView2, Axis};
8use sklears_core::{error::Result, types::Float};
9
10use super::simd_operations::*;
11use super::types_config::{CovarianceType, WeightInit};
12
13type GmmParams = (Array1<Float>, Array2<Float>, Vec<Array2<Float>>);
15
16pub struct EMAlgorithm {
18 pub max_iter: usize,
20 pub tol: Float,
22 pub reg_covar: Float,
24 pub covariance_type: CovarianceType,
26 pub init_params: WeightInit,
28 pub random_state: Option<u64>,
30}
31
32impl EMAlgorithm {
33 pub fn new(max_iter: usize, tol: Float, reg_covar: Float) -> Self {
35 Self {
36 max_iter,
37 tol,
38 reg_covar,
39 covariance_type: CovarianceType::Full,
40 init_params: WeightInit::KMeans,
41 random_state: None,
42 }
43 }
44
45 pub fn covariance_type(mut self, covariance_type: CovarianceType) -> Self {
47 self.covariance_type = covariance_type;
48 self
49 }
50
51 pub fn init_params(mut self, init_params: WeightInit) -> Self {
53 self.init_params = init_params;
54 self
55 }
56
57 pub fn random_state(mut self, seed: u64) -> Self {
59 self.random_state = Some(seed);
60 self
61 }
62
63 pub fn fit(&self, x: &ArrayView2<Float>, n_components: usize) -> Result<EMResult> {
65 let (mut weights, mut means, mut covariances) =
67 self.initialize_parameters(x, n_components)?;
68
69 let mut log_likelihood = Float::NEG_INFINITY;
70 let mut converged = false;
71 let mut n_iter = 0;
72
73 for iteration in 0..self.max_iter {
75 n_iter = iteration + 1;
76
77 let responsibilities = self.e_step_simd(x, &weights, &means, &covariances)?;
79
80 let (new_weights, new_means, new_covariances) =
82 self.m_step_simd(x, &responsibilities)?;
83
84 let new_log_likelihood =
86 self.compute_log_likelihood_simd(x, &new_weights, &new_means, &new_covariances)?;
87
88 if simd_check_convergence(log_likelihood, new_log_likelihood, self.tol) {
90 converged = true;
91 weights = new_weights;
92 means = new_means;
93 covariances = new_covariances;
94 log_likelihood = new_log_likelihood;
95 break;
96 }
97
98 weights = new_weights;
99 means = new_means;
100 covariances = new_covariances;
101 log_likelihood = new_log_likelihood;
102 }
103
104 for cov in &mut covariances {
106 simd_regularize_covariance(cov, self.reg_covar);
107 }
108
109 Ok(EMResult {
110 weights,
111 means,
112 covariances,
113 converged,
114 n_iter,
115 log_likelihood,
116 })
117 }
118
119 pub fn initialize_parameters(
121 &self,
122 x: &ArrayView2<Float>,
123 n_components: usize,
124 ) -> Result<GmmParams> {
125 let weights = Array1::from_elem(n_components, 1.0 / n_components as Float);
127
128 let means = self.initialize_means(x, n_components)?;
130
131 let covariances = self.initialize_covariances(x, &means, n_components)?;
133
134 Ok((weights, means, covariances))
135 }
136
137 fn initialize_means(
139 &self,
140 x: &ArrayView2<Float>,
141 n_components: usize,
142 ) -> Result<Array2<Float>> {
143 let n_samples = x.nrows();
144 let n_features = x.ncols();
145 let mut means = Array2::zeros((n_components, n_features));
146
147 match self.init_params {
148 WeightInit::KMeans => {
149 let mut selected = Vec::new();
151
152 let first_idx = if let Some(seed) = self.random_state {
154 (seed as usize) % n_samples
155 } else {
156 0
157 };
158 means.row_mut(0).assign(&x.row(first_idx));
159 selected.push(first_idx);
160
161 for i in 1..n_components {
163 let mut distances = Array1::zeros(n_samples);
164
165 for (j, sample) in x.outer_iter().enumerate() {
167 let mut min_dist = Float::INFINITY;
168 for &sel_idx in &selected {
169 let center = x.row(sel_idx);
170 let dist = simd_euclidean_distance_squared(&sample, ¢er);
171 if dist < min_dist {
172 min_dist = dist;
173 }
174 }
175 distances[j] = min_dist;
176 }
177
178 let sum_dist = distances.sum();
180 if sum_dist > 0.0 {
181 let mut cumsum = 0.0;
182 let target = self.generate_random_float(i as u64) * sum_dist;
183
184 for (j, &dist) in distances.iter().enumerate() {
185 cumsum += dist;
186 if cumsum >= target {
187 means.row_mut(i).assign(&x.row(j));
188 selected.push(j);
189 break;
190 }
191 }
192 } else {
193 let idx = i % n_samples;
195 means.row_mut(i).assign(&x.row(idx));
196 selected.push(idx);
197 }
198 }
199 }
200 WeightInit::Random => {
201 for i in 0..n_components {
203 let idx = if let Some(seed) = self.random_state {
204 ((seed as usize) + i) % n_samples
205 } else {
206 i * n_samples / n_components
207 };
208 means.row_mut(i).assign(&x.row(idx));
209 }
210 }
211 }
212
213 Ok(means)
214 }
215
216 fn initialize_covariances(
218 &self,
219 x: &ArrayView2<Float>,
220 _means: &Array2<Float>,
221 n_components: usize,
222 ) -> Result<Vec<Array2<Float>>> {
223 let n_features = x.ncols();
224 let mut covariances = Vec::new();
225
226 match self.covariance_type {
227 CovarianceType::Full => {
228 let data_var = x.var_axis(Axis(0), 0.0);
230 let scale = data_var.mean().unwrap_or(1.0);
231
232 for _ in 0..n_components {
233 let mut cov = Array2::eye(n_features) * scale;
234 simd_regularize_covariance(&mut cov, self.reg_covar);
235 covariances.push(cov);
236 }
237 }
238 CovarianceType::Diagonal => {
239 let data_var = x.var_axis(Axis(0), 0.0);
240
241 for _ in 0..n_components {
242 let mut cov = Array2::zeros((n_features, n_features));
243 for i in 0..n_features {
244 cov[[i, i]] = data_var[i].max(self.reg_covar);
245 }
246 covariances.push(cov);
247 }
248 }
249 CovarianceType::Tied => {
250 let data_cov = self.compute_sample_covariance(x);
252 let mut cov = data_cov;
253 simd_regularize_covariance(&mut cov, self.reg_covar);
254
255 for _ in 0..n_components {
256 covariances.push(cov.clone());
257 }
258 }
259 CovarianceType::Spherical => {
260 let data_var = x.var_axis(Axis(0), 0.0);
261 let avg_var = data_var.mean().unwrap_or(1.0);
262
263 for _ in 0..n_components {
264 let mut cov = Array2::zeros((n_features, n_features));
265 for i in 0..n_features {
266 cov[[i, i]] = avg_var.max(self.reg_covar);
267 }
268 covariances.push(cov);
269 }
270 }
271 }
272
273 Ok(covariances)
274 }
275
276 pub fn e_step_simd(
278 &self,
279 x: &ArrayView2<Float>,
280 weights: &Array1<Float>,
281 means: &Array2<Float>,
282 covariances: &[Array2<Float>],
283 ) -> Result<Array2<Float>> {
284 let n_samples = x.nrows();
285 let n_components = weights.len();
286 let mut responsibilities = Array2::zeros((n_samples, n_components));
287
288 for (i, sample) in x.outer_iter().enumerate() {
289 let mut log_probs = Array1::zeros(n_components);
290
291 for k in 0..n_components {
292 let mean = means.row(k);
293 let cov = &covariances[k];
294
295 let inv_diag = self.extract_diagonal_inverse(cov)?;
297 let log_det = simd_log_determinant(&cov.view());
298
299 let log_prob = weights[k].ln()
301 + simd_multivariate_normal_log_density(
302 &sample,
303 &mean,
304 &inv_diag.view(),
305 log_det,
306 );
307
308 log_probs[k] = log_prob;
309 }
310
311 let log_sum = simd_log_sum_exp(&log_probs.view());
313 for k in 0..n_components {
314 responsibilities[[i, k]] = (log_probs[k] - log_sum).exp();
315 }
316 }
317
318 Ok(responsibilities)
319 }
320
321 pub fn m_step_simd(
323 &self,
324 x: &ArrayView2<Float>,
325 responsibilities: &Array2<Float>,
326 ) -> Result<GmmParams> {
327 let n_samples = x.nrows();
328 let n_features = x.ncols();
329 let n_components = responsibilities.ncols();
330
331 let nk: Array1<Float> = responsibilities.sum_axis(Axis(0));
333
334 let weights = &nk / n_samples as Float;
336
337 let mut means = Array2::zeros((n_components, n_features));
339 for k in 0..n_components {
340 if nk[k] > 1e-12 {
341 for i in 0..n_samples {
343 means
344 .row_mut(k)
345 .scaled_add(responsibilities[[i, k]], &x.row(i));
346 }
347 means.row_mut(k).mapv_inplace(|x| x / nk[k]);
348 }
349 }
350
351 let covariances = self.update_covariances_simd(x, &means, responsibilities, &nk)?;
353
354 Ok((weights, means, covariances))
355 }
356
357 fn update_covariances_simd(
359 &self,
360 x: &ArrayView2<Float>,
361 means: &Array2<Float>,
362 responsibilities: &Array2<Float>,
363 nk: &Array1<Float>,
364 ) -> Result<Vec<Array2<Float>>> {
365 let n_samples = x.nrows();
366 let n_features = x.ncols();
367 let n_components = means.nrows();
368 let mut covariances = Vec::new();
369
370 match self.covariance_type {
371 CovarianceType::Full => {
372 for k in 0..n_components {
373 let cov = if nk[k] > 1e-12 {
374 simd_covariance_matrix(
375 &x.view(),
376 &responsibilities.column(k).view(),
377 &means.row(k).view(),
378 )
379 } else {
380 Array2::eye(n_features)
381 };
382
383 let mut regularized_cov = cov;
384 simd_regularize_covariance(&mut regularized_cov, self.reg_covar);
385 covariances.push(regularized_cov);
386 }
387 }
388 CovarianceType::Diagonal => {
389 for k in 0..n_components {
390 let diag_cov = if nk[k] > 1e-12 {
391 simd_diagonal_covariance(
392 &x.view(),
393 &responsibilities.column(k).view(),
394 &means.row(k).view(),
395 )
396 } else {
397 Array1::from_elem(n_features, self.reg_covar)
398 };
399
400 let mut cov = Array2::zeros((n_features, n_features));
401 for j in 0..n_features {
402 cov[[j, j]] = diag_cov[j].max(self.reg_covar);
403 }
404 covariances.push(cov);
405 }
406 }
407 CovarianceType::Tied => {
408 let mut shared_cov = Array2::zeros((n_features, n_features));
410
411 for k in 0..n_components {
412 if nk[k] > 1e-12 {
413 let component_cov = simd_covariance_matrix(
414 &x.view(),
415 &responsibilities.column(k).view(),
416 &means.row(k).view(),
417 );
418 shared_cov.scaled_add(nk[k] / n_samples as Float, &component_cov);
419 }
420 }
421
422 simd_regularize_covariance(&mut shared_cov, self.reg_covar);
423
424 for _ in 0..n_components {
425 covariances.push(shared_cov.clone());
426 }
427 }
428 CovarianceType::Spherical => {
429 for k in 0..n_components {
430 let avg_var = if nk[k] > 1e-12 {
431 let diag_cov = simd_diagonal_covariance(
432 &x.view(),
433 &responsibilities.column(k).view(),
434 &means.row(k).view(),
435 );
436 diag_cov
437 .mean()
438 .unwrap_or(self.reg_covar)
439 .max(self.reg_covar)
440 } else {
441 self.reg_covar
442 };
443
444 let mut cov = Array2::zeros((n_features, n_features));
445 for j in 0..n_features {
446 cov[[j, j]] = avg_var;
447 }
448 covariances.push(cov);
449 }
450 }
451 }
452
453 Ok(covariances)
454 }
455
456 pub fn compute_log_likelihood_simd(
458 &self,
459 x: &ArrayView2<Float>,
460 weights: &Array1<Float>,
461 means: &Array2<Float>,
462 covariances: &[Array2<Float>],
463 ) -> Result<Float> {
464 let mut log_likelihood = 0.0;
465
466 for sample in x.outer_iter() {
467 let mut sample_likelihood = 0.0;
468
469 for k in 0..weights.len() {
470 let mean = means.row(k);
471 let cov = &covariances[k];
472
473 let inv_diag = self.extract_diagonal_inverse(cov)?;
474 let log_det = simd_log_determinant(&cov.view());
475
476 let log_prob =
477 simd_multivariate_normal_log_density(&sample, &mean, &inv_diag.view(), log_det);
478
479 sample_likelihood += weights[k] * log_prob.exp();
480 }
481
482 if sample_likelihood > 1e-12 {
483 log_likelihood += sample_likelihood.ln();
484 }
485 }
486
487 Ok(log_likelihood)
488 }
489
490 fn extract_diagonal_inverse(&self, cov: &Array2<Float>) -> Result<Array1<Float>> {
492 let mut inv_diag = Array1::zeros(cov.nrows());
493 for i in 0..cov.nrows() {
494 let diag_val = cov[[i, i]];
495 if diag_val <= 1e-12 {
496 return Err(sklears_core::error::SklearsError::Other(
497 "Singular covariance matrix".to_string(),
498 ));
499 }
500 inv_diag[i] = 1.0 / diag_val;
501 }
502 Ok(inv_diag)
503 }
504
505 fn compute_sample_covariance(&self, x: &ArrayView2<Float>) -> Array2<Float> {
507 let n_features = x.ncols();
508 let mean = x.mean_axis(Axis(0)).expect("operation should succeed");
509 let mut cov = Array2::zeros((n_features, n_features));
510
511 for sample in x.outer_iter() {
512 let diff = &sample - &mean;
513 for i in 0..n_features {
514 for j in 0..n_features {
515 cov[[i, j]] += diff[i] * diff[j];
516 }
517 }
518 }
519
520 cov /= (x.nrows() - 1) as Float;
521 cov
522 }
523
524 fn generate_random_float(&self, seed_offset: u64) -> Float {
526 if let Some(base_seed) = self.random_state {
527 let seed = base_seed.wrapping_add(seed_offset);
528 let a = 1103515245_u64;
530 let c = 12345_u64;
531 let m = 2_u64.pow(31);
532 let next = (a.wrapping_mul(seed).wrapping_add(c)) % m;
533 next as Float / m as Float
534 } else {
535 0.5 }
537 }
538}
539
540#[derive(Debug, Clone)]
542pub struct EMResult {
543 pub weights: Array1<Float>,
545 pub means: Array2<Float>,
547 pub covariances: Vec<Array2<Float>>,
549 pub converged: bool,
551 pub n_iter: usize,
553 pub log_likelihood: Float,
555}
556
557#[allow(non_snake_case)]
558#[cfg(test)]
559mod tests {
560 use super::*;
561 use scirs2_core::ndarray::array;
562
563 #[test]
564 fn test_em_algorithm() {
565 let x = array![
566 [0.0, 0.0],
567 [0.1, 0.1],
568 [0.2, 0.0],
569 [5.0, 5.0],
570 [5.1, 5.1],
571 [5.2, 5.0],
572 ];
573
574 let em = EMAlgorithm::new(100, 1e-3, 1e-6)
575 .covariance_type(CovarianceType::Diagonal)
576 .init_params(WeightInit::KMeans);
577
578 let result = em.fit(&x.view(), 2).expect("operation should succeed");
579
580 assert_eq!(result.weights.len(), 2);
581 assert_eq!(result.means.nrows(), 2);
582 assert_eq!(result.covariances.len(), 2);
583 assert!(result.n_iter > 0);
584 assert!(result.log_likelihood.is_finite());
585 }
586
587 #[test]
588 fn test_parameter_initialization() {
589 let x = array![[0.0, 0.0], [1.0, 1.0], [2.0, 2.0], [3.0, 3.0],];
590
591 let em = EMAlgorithm::new(100, 1e-3, 1e-6)
592 .covariance_type(CovarianceType::Full)
593 .init_params(WeightInit::KMeans);
594
595 let (weights, means, covariances) = em
596 .initialize_parameters(&x.view(), 2)
597 .expect("operation should succeed");
598
599 assert_eq!(weights.len(), 2);
600 assert_eq!(means.nrows(), 2);
601 assert_eq!(means.ncols(), 2);
602 assert_eq!(covariances.len(), 2);
603
604 assert!((weights.sum() - 1.0).abs() < 1e-10);
606
607 for cov in &covariances {
609 assert_eq!(cov.nrows(), 2);
610 assert_eq!(cov.ncols(), 2);
611 for i in 0..2 {
613 assert!(cov[[i, i]] > 0.0);
614 }
615 }
616 }
617
618 #[test]
619 fn test_convergence_check() {
620 let old_ll = -100.0;
621 let new_ll = -99.999;
622 let tol = 1e-3;
623
624 assert!(simd_check_convergence(old_ll, new_ll, tol));
625
626 let new_ll_no_conv = -99.0;
627 assert!(!simd_check_convergence(old_ll, new_ll_no_conv, tol));
628 }
629}