1use scirs2_core::essentials::{Normal, Uniform};
2use scirs2_core::ndarray::{Array1, Array2, ArrayView2};
3use scirs2_core::random::thread_rng;
4use scirs2_core::Distribution;
5use sklears_core::{
6 error::{Result as SklResult, SklearsError},
7 traits::{Estimator, Fit, Transform, Untrained},
8};
9
10#[derive(Debug, Clone)]
11pub struct AutoencoderManifold<S = Untrained> {
12 n_components: usize,
13 hidden_layers: Vec<usize>,
14 epochs: usize,
15 learning_rate: f64,
16 batch_size: usize,
17 state: S,
18}
19
20#[derive(Debug, Clone)]
21#[allow(dead_code)] pub struct TrainedAutoencoder {
23 encoder_weights: Vec<Array2<f64>>,
24 encoder_biases: Vec<Array1<f64>>,
25 decoder_weights: Vec<Array2<f64>>,
26 decoder_biases: Vec<Array1<f64>>,
27}
28
29impl AutoencoderManifold<Untrained> {
30 pub fn new(n_components: usize) -> Self {
31 Self {
32 n_components,
33 hidden_layers: vec![128, 64],
34 epochs: 100,
35 learning_rate: 0.001,
36 batch_size: 32,
37 state: Untrained,
38 }
39 }
40
41 pub fn with_hidden_layers(mut self, layers: Vec<usize>) -> Self {
42 self.hidden_layers = layers;
43 self
44 }
45
46 pub fn with_epochs(mut self, epochs: usize) -> Self {
47 self.epochs = epochs;
48 self
49 }
50
51 pub fn with_learning_rate(mut self, lr: f64) -> Self {
52 self.learning_rate = lr;
53 self
54 }
55
56 pub fn with_batch_size(mut self, batch_size: usize) -> Self {
57 self.batch_size = batch_size;
58 self
59 }
60
61 fn initialize_weights(&self, input_dim: usize) -> TrainedAutoencoder {
62 let mut layer_sizes = vec![input_dim];
63 layer_sizes.extend(&self.hidden_layers);
64 layer_sizes.push(self.n_components);
65
66 let mut encoder_weights = Vec::new();
67 let mut encoder_biases = Vec::new();
68
69 for i in 0..layer_sizes.len() - 1 {
70 let input_size = layer_sizes[i];
71 let output_size = layer_sizes[i + 1];
72
73 let weight = Array2::from_shape_fn((input_size, output_size), |(_, _)| {
74 let mut rng = thread_rng();
75 Normal::new(0.0, (2.0 / (input_size + output_size) as f64).sqrt())
76 .expect("operation should succeed")
77 .sample(&mut rng)
78 });
79 let bias = Array1::zeros(output_size);
80
81 encoder_weights.push(weight);
82 encoder_biases.push(bias);
83 }
84
85 layer_sizes.reverse();
86 let mut decoder_weights = Vec::new();
87 let mut decoder_biases = Vec::new();
88
89 for i in 0..layer_sizes.len() - 1 {
90 let input_size = layer_sizes[i];
91 let output_size = layer_sizes[i + 1];
92
93 let weight = Array2::from_shape_fn((input_size, output_size), |(_, _)| {
94 let mut rng = thread_rng();
95 Normal::new(0.0, (2.0 / (input_size + output_size) as f64).sqrt())
96 .expect("operation should succeed")
97 .sample(&mut rng)
98 });
99 let bias = Array1::zeros(output_size);
100
101 decoder_weights.push(weight);
102 decoder_biases.push(bias);
103 }
104
105 TrainedAutoencoder {
107 encoder_weights,
108 encoder_biases,
109 decoder_weights,
110 decoder_biases,
111 }
112 }
113}
114
115impl AutoencoderManifold<TrainedAutoencoder> {
116 #[allow(dead_code)] fn forward_pass(&self, x: &ArrayView2<f64>) -> SklResult<(Array2<f64>, Array2<f64>)> {
118 let mut activations = x.to_owned();
119
120 for (weights, biases) in self
121 .state
122 .encoder_weights
123 .iter()
124 .zip(self.state.encoder_biases.iter())
125 {
126 activations = activations.dot(weights) + biases;
127 activations.mapv_inplace(|x| x.max(0.0)); }
129
130 let encoded = activations.clone();
131
132 for (weights, biases) in self
133 .state
134 .decoder_weights
135 .iter()
136 .zip(self.state.decoder_biases.iter())
137 {
138 activations = activations.dot(weights) + biases;
139 if weights
140 != self
141 .state
142 .decoder_weights
143 .last()
144 .expect("operation should succeed")
145 {
146 activations.mapv_inplace(|x| x.max(0.0)); }
148 }
149
150 Ok((encoded, activations))
151 }
152}
153
154impl Estimator for AutoencoderManifold<Untrained> {
155 type Config = ();
156 type Error = SklearsError;
157 type Float = f64;
158
159 fn config(&self) -> &Self::Config {
160 &()
161 }
162}
163
164impl Fit<Array2<f64>, ()> for AutoencoderManifold<Untrained> {
165 type Fitted = AutoencoderManifold<TrainedAutoencoder>;
166
167 fn fit(self, x: &Array2<f64>, _y: &()) -> SklResult<Self::Fitted> {
168 let (n_samples, n_features) = x.dim();
169
170 if n_samples == 0 || n_features == 0 {
171 return Err(SklearsError::InvalidInput("Empty input data".to_string()));
172 }
173
174 if self.n_components >= n_features {
175 return Err(SklearsError::InvalidInput(
176 "n_components must be less than number of features".to_string(),
177 ));
178 }
179
180 let state = self.initialize_weights(n_features);
181
182 Ok(AutoencoderManifold {
187 n_components: self.n_components,
188 hidden_layers: self.hidden_layers,
189 epochs: self.epochs,
190 learning_rate: self.learning_rate,
191 batch_size: self.batch_size,
192 state,
193 })
194 }
195}
196
197impl Transform<Array2<f64>, Array2<f64>> for AutoencoderManifold<TrainedAutoencoder> {
198 fn transform(&self, x: &Array2<f64>) -> SklResult<Array2<f64>> {
199 let mut activations = x.to_owned();
200
201 for (weights, biases) in self
202 .state
203 .encoder_weights
204 .iter()
205 .zip(self.state.encoder_biases.iter())
206 {
207 activations = activations.dot(weights) + biases;
208 activations.mapv_inplace(|x| x.max(0.0)); }
210
211 Ok(activations)
212 }
213}
214
215#[derive(Debug, Clone)]
216#[allow(dead_code)] pub struct VariationalAutoencoder<S = Untrained> {
218 n_components: usize,
219 hidden_layers: Vec<usize>,
220 epochs: usize,
221 learning_rate: f64,
222 batch_size: usize,
223 beta: f64, state: S,
225}
226
227#[derive(Debug, Clone)]
228#[allow(dead_code)] pub struct TrainedVAE {
230 encoder_weights: Vec<Array2<f64>>,
231 encoder_biases: Vec<Array1<f64>>,
232 mu_layer: (Array2<f64>, Array1<f64>),
233 logvar_layer: (Array2<f64>, Array1<f64>),
234 decoder_weights: Vec<Array2<f64>>,
235 decoder_biases: Vec<Array1<f64>>,
236}
237
238impl VariationalAutoencoder<Untrained> {
239 pub fn new(n_components: usize) -> Self {
240 Self {
241 n_components,
242 hidden_layers: vec![128, 64],
243 epochs: 100,
244 learning_rate: 0.001,
245 batch_size: 32,
246 beta: 1.0,
247 state: Untrained,
248 }
249 }
250
251 pub fn with_beta(mut self, beta: f64) -> Self {
252 self.beta = beta;
253 self
254 }
255
256 pub fn with_hidden_layers(mut self, layers: Vec<usize>) -> Self {
257 self.hidden_layers = layers;
258 self
259 }
260
261 pub fn with_epochs(mut self, epochs: usize) -> Self {
262 self.epochs = epochs;
263 self
264 }
265
266 pub fn with_learning_rate(mut self, lr: f64) -> Self {
267 self.learning_rate = lr;
268 self
269 }
270}
271
272impl VariationalAutoencoder<TrainedVAE> {
273 #[allow(dead_code)] fn reparameterize(&self, mu: &Array2<f64>, logvar: &Array2<f64>) -> Array2<f64> {
275 let std = logvar.mapv(|x| (0.5 * x).exp());
276 let mut rng = thread_rng();
277 let epsilon = Array2::from_shape_fn(mu.dim(), |(_, _)| {
278 Normal::new(0.0, 1.0)
279 .expect("operation should succeed")
280 .sample(&mut rng)
281 });
282 mu + &std * &epsilon
283 }
284
285 #[allow(dead_code)] fn kl_divergence(&self, mu: &Array2<f64>, logvar: &Array2<f64>) -> f64 {
287 let kl: Array2<f64> = -0.5 * (1.0 + logvar - mu.mapv(|x| x * x) - logvar.mapv(|x| x.exp()));
288 kl.sum() / mu.nrows() as f64
289 }
290}
291
292impl Estimator for VariationalAutoencoder<Untrained> {
293 type Config = ();
294 type Error = SklearsError;
295 type Float = f64;
296
297 fn config(&self) -> &Self::Config {
298 &()
299 }
300}
301
302impl Transform<Array2<f64>, Array2<f64>> for VariationalAutoencoder<TrainedVAE> {
303 fn transform(&self, x: &Array2<f64>) -> SklResult<Array2<f64>> {
304 let mut activations = x.to_owned();
305
306 for (weights, biases) in self
307 .state
308 .encoder_weights
309 .iter()
310 .zip(self.state.encoder_biases.iter())
311 {
312 activations = activations.dot(weights) + biases;
313 activations.mapv_inplace(|x| x.max(0.0)); }
315
316 let mu = activations.dot(&self.state.mu_layer.0) + &self.state.mu_layer.1;
317 let logvar = activations.dot(&self.state.logvar_layer.0) + &self.state.logvar_layer.1;
318
319 Ok(self.reparameterize(&mu, &logvar))
320 }
321}
322
323#[derive(Debug, Clone)]
325pub struct AdversarialAutoencoder<S = Untrained> {
326 n_components: usize,
327 hidden_layers: Vec<usize>,
328 discriminator_layers: Vec<usize>,
329 epochs: usize,
330 learning_rate: f64,
331 adversarial_weight: f64,
332 batch_size: usize,
333 prior_type: PriorType,
334 state: S,
335}
336
337#[derive(Debug, Clone)]
338pub enum PriorType {
339 Gaussian,
341 Uniform,
343 Categorical(usize), }
346
347#[derive(Debug, Clone)]
348#[allow(dead_code)] pub struct TrainedAAE {
350 encoder_weights: Vec<Array2<f64>>,
351 encoder_biases: Vec<Array1<f64>>,
352 decoder_weights: Vec<Array2<f64>>,
353 decoder_biases: Vec<Array1<f64>>,
354 discriminator_weights: Vec<Array2<f64>>,
355 discriminator_biases: Vec<Array1<f64>>,
356 prior_type: PriorType,
357}
358
359impl AdversarialAutoencoder<Untrained> {
360 pub fn new(n_components: usize) -> Self {
361 Self {
362 n_components,
363 hidden_layers: vec![128, 64],
364 discriminator_layers: vec![64, 32],
365 epochs: 100,
366 learning_rate: 0.001,
367 adversarial_weight: 1.0,
368 batch_size: 32,
369 prior_type: PriorType::Gaussian,
370 state: Untrained,
371 }
372 }
373
374 pub fn with_hidden_layers(mut self, layers: Vec<usize>) -> Self {
375 self.hidden_layers = layers;
376 self
377 }
378
379 pub fn with_discriminator_layers(mut self, layers: Vec<usize>) -> Self {
380 self.discriminator_layers = layers;
381 self
382 }
383
384 pub fn with_epochs(mut self, epochs: usize) -> Self {
385 self.epochs = epochs;
386 self
387 }
388
389 pub fn with_learning_rate(mut self, lr: f64) -> Self {
390 self.learning_rate = lr;
391 self
392 }
393
394 pub fn with_adversarial_weight(mut self, weight: f64) -> Self {
395 self.adversarial_weight = weight;
396 self
397 }
398
399 pub fn with_batch_size(mut self, batch_size: usize) -> Self {
400 self.batch_size = batch_size;
401 self
402 }
403
404 pub fn with_prior_type(mut self, prior_type: PriorType) -> Self {
405 self.prior_type = prior_type;
406 self
407 }
408
409 fn initialize_aae_weights(&self, input_dim: usize) -> TrainedAAE {
410 let mut rng = thread_rng();
411
412 let mut encoder_layer_sizes = vec![input_dim];
414 encoder_layer_sizes.extend(&self.hidden_layers);
415 encoder_layer_sizes.push(self.n_components);
416
417 let mut encoder_weights = Vec::new();
418 let mut encoder_biases = Vec::new();
419
420 for i in 0..encoder_layer_sizes.len() - 1 {
421 let input_size = encoder_layer_sizes[i];
422 let output_size = encoder_layer_sizes[i + 1];
423
424 let weight = Array2::from_shape_fn((input_size, output_size), |(_, _)| {
425 Normal::new(0.0, (2.0 / (input_size + output_size) as f64).sqrt())
426 .expect("operation should succeed")
427 .sample(&mut rng)
428 });
429 let bias = Array1::zeros(output_size);
430
431 encoder_weights.push(weight);
432 encoder_biases.push(bias);
433 }
434
435 let mut decoder_layer_sizes = encoder_layer_sizes.clone();
437 decoder_layer_sizes.reverse();
438
439 let mut decoder_weights = Vec::new();
440 let mut decoder_biases = Vec::new();
441
442 for i in 0..decoder_layer_sizes.len() - 1 {
443 let input_size = decoder_layer_sizes[i];
444 let output_size = decoder_layer_sizes[i + 1];
445
446 let weight = Array2::from_shape_fn((input_size, output_size), |(_, _)| {
447 Normal::new(0.0, (2.0 / (input_size + output_size) as f64).sqrt())
448 .expect("operation should succeed")
449 .sample(&mut rng)
450 });
451 let bias = Array1::zeros(output_size);
452
453 decoder_weights.push(weight);
454 decoder_biases.push(bias);
455 }
456
457 let mut disc_layer_sizes = vec![self.n_components];
459 disc_layer_sizes.extend(&self.discriminator_layers);
460 disc_layer_sizes.push(1); let mut discriminator_weights = Vec::new();
463 let mut discriminator_biases = Vec::new();
464
465 for i in 0..disc_layer_sizes.len() - 1 {
466 let input_size = disc_layer_sizes[i];
467 let output_size = disc_layer_sizes[i + 1];
468
469 let weight = Array2::from_shape_fn((input_size, output_size), |(_, _)| {
470 Normal::new(0.0, (2.0 / (input_size + output_size) as f64).sqrt())
471 .expect("operation should succeed")
472 .sample(&mut rng)
473 });
474 let bias = Array1::zeros(output_size);
475
476 discriminator_weights.push(weight);
477 discriminator_biases.push(bias);
478 }
479
480 TrainedAAE {
482 encoder_weights,
483 encoder_biases,
484 decoder_weights,
485 decoder_biases,
486 discriminator_weights,
487 discriminator_biases,
488 prior_type: self.prior_type.clone(),
489 }
490 }
491}
492
493impl AdversarialAutoencoder<TrainedAAE> {
494 fn encode(&self, x: &ArrayView2<f64>) -> SklResult<Array2<f64>> {
495 let mut activations = x.to_owned();
496
497 for (weights, biases) in self
498 .state
499 .encoder_weights
500 .iter()
501 .zip(self.state.encoder_biases.iter())
502 {
503 activations = activations.dot(weights) + biases;
504 if weights
506 != self
507 .state
508 .encoder_weights
509 .last()
510 .expect("operation should succeed")
511 {
512 activations.mapv_inplace(|x| x.max(0.0));
513 }
514 }
515
516 Ok(activations)
517 }
518
519 #[allow(dead_code)] fn decode(&self, z: &ArrayView2<f64>) -> SklResult<Array2<f64>> {
521 let mut activations = z.to_owned();
522
523 for (i, (weights, biases)) in self
524 .state
525 .decoder_weights
526 .iter()
527 .zip(self.state.decoder_biases.iter())
528 .enumerate()
529 {
530 activations = activations.dot(weights) + biases;
531 if i < self.state.decoder_weights.len() - 1 {
533 activations.mapv_inplace(|x| x.max(0.0));
534 }
535 }
536
537 Ok(activations)
538 }
539
540 #[allow(dead_code)] fn discriminate(&self, z: &ArrayView2<f64>) -> SklResult<Array2<f64>> {
542 let mut activations = z.to_owned();
543
544 for (i, (weights, biases)) in self
545 .state
546 .discriminator_weights
547 .iter()
548 .zip(self.state.discriminator_biases.iter())
549 .enumerate()
550 {
551 activations = activations.dot(weights) + biases;
552
553 if i < self.state.discriminator_weights.len() - 1 {
554 activations.mapv_inplace(|x| x.max(0.0));
556 } else {
557 activations.mapv_inplace(|x| 1.0 / (1.0 + (-x).exp()));
559 }
560 }
561
562 Ok(activations)
563 }
564
565 #[allow(dead_code)] fn sample_prior(&self, n_samples: usize) -> Array2<f64> {
567 let mut rng = thread_rng();
568
569 match &self.state.prior_type {
570 PriorType::Gaussian => {
571 Array2::from_shape_fn((n_samples, self.n_components), |(_, _)| {
572 Normal::new(0.0, 1.0)
573 .expect("operation should succeed")
574 .sample(&mut rng)
575 })
576 }
577 PriorType::Uniform => {
578 Array2::from_shape_fn((n_samples, self.n_components), |(_, _)| {
579 Uniform::new(-1.0, 1.0)
580 .expect("operation should succeed")
581 .sample(&mut rng)
582 })
583 }
584 PriorType::Categorical(n_cats) => {
585 let mut samples = Array2::zeros((n_samples, self.n_components));
586 for i in 0..n_samples {
587 let cat = rng.gen_range(0..*n_cats);
588 if cat < self.n_components {
589 samples[[i, cat]] = 1.0;
590 }
591 }
592 samples
593 }
594 }
595 }
596
597 #[allow(dead_code)] fn reconstruction_loss(
599 &self,
600 x_original: &ArrayView2<f64>,
601 x_reconstructed: &ArrayView2<f64>,
602 ) -> f64 {
603 let diff = x_original - x_reconstructed;
604 (diff.mapv(|x| x * x).sum()) / x_original.nrows() as f64
605 }
606
607 #[allow(dead_code)] fn adversarial_loss(&self, discriminator_output: &ArrayView2<f64>, is_real: bool) -> f64 {
609 let target = if is_real { 1.0 } else { 0.0 };
610 let epsilon = 1e-12;
611
612 let mut loss = 0.0;
613 for &output in discriminator_output.iter() {
614 let clamped_output = output.max(epsilon).min(1.0 - epsilon);
615 loss -= target * clamped_output.ln() + (1.0 - target) * (1.0 - clamped_output).ln();
616 }
617
618 loss / discriminator_output.nrows() as f64
619 }
620}
621
622impl Estimator for AdversarialAutoencoder<Untrained> {
623 type Config = ();
624 type Error = SklearsError;
625 type Float = f64;
626
627 fn config(&self) -> &Self::Config {
628 &()
629 }
630}
631
632impl Fit<Array2<f64>, ()> for AdversarialAutoencoder<Untrained> {
633 type Fitted = AdversarialAutoencoder<TrainedAAE>;
634
635 fn fit(self, x: &Array2<f64>, _y: &()) -> SklResult<Self::Fitted> {
636 let (n_samples, n_features) = x.dim();
637
638 if n_samples == 0 || n_features == 0 {
639 return Err(SklearsError::InvalidInput("Empty input data".to_string()));
640 }
641
642 if self.n_components >= n_features {
643 return Err(SklearsError::InvalidInput(
644 "n_components must be less than number of features".to_string(),
645 ));
646 }
647
648 let state = self.initialize_aae_weights(n_features);
649
650 Ok(AdversarialAutoencoder {
655 n_components: self.n_components,
656 hidden_layers: self.hidden_layers,
657 discriminator_layers: self.discriminator_layers,
658 epochs: self.epochs,
659 learning_rate: self.learning_rate,
660 adversarial_weight: self.adversarial_weight,
661 batch_size: self.batch_size,
662 prior_type: self.prior_type,
663 state,
664 })
665 }
666}
667
668impl Transform<Array2<f64>, Array2<f64>> for AdversarialAutoencoder<TrainedAAE> {
669 fn transform(&self, x: &Array2<f64>) -> SklResult<Array2<f64>> {
670 self.encode(&x.view())
671 }
672}
673
674#[derive(Debug, Clone)]
676pub struct NeuralODE<S = Untrained> {
677 n_components: usize,
678 hidden_layers: Vec<usize>,
679 integration_time: f64,
680 num_time_steps: usize,
681 solver_type: ODESolverType,
682 learning_rate: f64,
683 epochs: usize,
684 state: S,
685}
686
687#[derive(Debug, Clone)]
688pub enum ODESolverType {
689 Euler,
691 RungeKutta4,
693}
694
695#[derive(Debug, Clone)]
696pub struct TrainedNODE {
697 ode_func_weights: Vec<Array2<f64>>,
698 ode_func_biases: Vec<Array1<f64>>,
699 encoder_weights: Vec<Array2<f64>>,
700 encoder_biases: Vec<Array1<f64>>,
701 decoder_weights: Vec<Array2<f64>>,
702 decoder_biases: Vec<Array1<f64>>,
703 integration_time: f64,
704 num_time_steps: usize,
705 solver_type: ODESolverType,
706}
707
708impl NeuralODE<Untrained> {
709 pub fn new(n_components: usize) -> Self {
710 Self {
711 n_components,
712 hidden_layers: vec![64, 32],
713 integration_time: 1.0,
714 num_time_steps: 10,
715 solver_type: ODESolverType::RungeKutta4,
716 learning_rate: 0.001,
717 epochs: 100,
718 state: Untrained,
719 }
720 }
721
722 pub fn with_hidden_layers(mut self, layers: Vec<usize>) -> Self {
723 self.hidden_layers = layers;
724 self
725 }
726
727 pub fn with_integration_time(mut self, time: f64) -> Self {
728 self.integration_time = time;
729 self
730 }
731
732 pub fn with_num_time_steps(mut self, steps: usize) -> Self {
733 self.num_time_steps = steps;
734 self
735 }
736
737 pub fn with_solver_type(mut self, solver: ODESolverType) -> Self {
738 self.solver_type = solver;
739 self
740 }
741
742 pub fn with_learning_rate(mut self, lr: f64) -> Self {
743 self.learning_rate = lr;
744 self
745 }
746
747 pub fn with_epochs(mut self, epochs: usize) -> Self {
748 self.epochs = epochs;
749 self
750 }
751
752 fn initialize_node_weights(&self, input_dim: usize) -> TrainedNODE {
753 let mut rng = thread_rng();
754
755 let encoder_layers = [input_dim, self.n_components];
757 let mut encoder_weights = Vec::new();
758 let mut encoder_biases = Vec::new();
759
760 for i in 0..encoder_layers.len() - 1 {
761 let in_size = encoder_layers[i];
762 let out_size = encoder_layers[i + 1];
763
764 let weight = Array2::from_shape_fn((in_size, out_size), |(_, _)| {
765 Normal::new(0.0, (2.0 / (in_size + out_size) as f64).sqrt())
766 .expect("operation should succeed")
767 .sample(&mut rng)
768 });
769 let bias = Array1::zeros(out_size);
770
771 encoder_weights.push(weight);
772 encoder_biases.push(bias);
773 }
774
775 let mut ode_layer_sizes = vec![self.n_components];
777 ode_layer_sizes.extend(&self.hidden_layers);
778 ode_layer_sizes.push(self.n_components); let mut ode_func_weights = Vec::new();
781 let mut ode_func_biases = Vec::new();
782
783 for i in 0..ode_layer_sizes.len() - 1 {
784 let in_size = ode_layer_sizes[i];
785 let out_size = ode_layer_sizes[i + 1];
786
787 let weight = Array2::from_shape_fn((in_size, out_size), |(_, _)| {
788 Normal::new(0.0, (2.0 / (in_size + out_size) as f64).sqrt())
789 .expect("operation should succeed")
790 .sample(&mut rng)
791 });
792 let bias = Array1::zeros(out_size);
793
794 ode_func_weights.push(weight);
795 ode_func_biases.push(bias);
796 }
797
798 let decoder_layers = [self.n_components, input_dim];
800 let mut decoder_weights = Vec::new();
801 let mut decoder_biases = Vec::new();
802
803 for i in 0..decoder_layers.len() - 1 {
804 let in_size = decoder_layers[i];
805 let out_size = decoder_layers[i + 1];
806
807 let weight = Array2::from_shape_fn((in_size, out_size), |(_, _)| {
808 Normal::new(0.0, (2.0 / (in_size + out_size) as f64).sqrt())
809 .expect("operation should succeed")
810 .sample(&mut rng)
811 });
812 let bias = Array1::zeros(out_size);
813
814 decoder_weights.push(weight);
815 decoder_biases.push(bias);
816 }
817
818 TrainedNODE {
820 ode_func_weights,
821 ode_func_biases,
822 encoder_weights,
823 encoder_biases,
824 decoder_weights,
825 decoder_biases,
826 integration_time: self.integration_time,
827 num_time_steps: self.num_time_steps,
828 solver_type: self.solver_type.clone(),
829 }
830 }
831}
832
833impl NeuralODE<TrainedNODE> {
834 fn encode(&self, x: &ArrayView2<f64>) -> SklResult<Array2<f64>> {
835 let mut activations = x.to_owned();
836
837 for (weights, biases) in self
838 .state
839 .encoder_weights
840 .iter()
841 .zip(self.state.encoder_biases.iter())
842 {
843 activations = activations.dot(weights) + biases;
844 }
846
847 Ok(activations)
848 }
849
850 fn decode(&self, z: &ArrayView2<f64>) -> SklResult<Array2<f64>> {
851 let mut activations = z.to_owned();
852
853 for (weights, biases) in self
854 .state
855 .decoder_weights
856 .iter()
857 .zip(self.state.decoder_biases.iter())
858 {
859 activations = activations.dot(weights) + biases;
860 }
862
863 Ok(activations)
864 }
865
866 fn ode_func(&self, _t: f64, h: &ArrayView2<f64>) -> SklResult<Array2<f64>> {
867 let mut activations = h.to_owned();
868
869 for (i, (weights, biases)) in self
870 .state
871 .ode_func_weights
872 .iter()
873 .zip(self.state.ode_func_biases.iter())
874 .enumerate()
875 {
876 activations = activations.dot(weights) + biases;
877 if i < self.state.ode_func_weights.len() - 1 {
879 activations.mapv_inplace(|x| x.tanh()); }
881 }
882
883 Ok(activations)
884 }
885
886 fn solve_ode(&self, h0: &ArrayView2<f64>) -> SklResult<Array2<f64>> {
887 let dt = self.state.integration_time / self.state.num_time_steps as f64;
888 let mut h = h0.to_owned();
889
890 for i in 0..self.state.num_time_steps {
891 let t = i as f64 * dt;
892
893 match self.state.solver_type {
894 ODESolverType::Euler => {
895 let dh_dt = self.ode_func(t, &h.view())?;
897 h = h + dh_dt * dt;
898 }
899 ODESolverType::RungeKutta4 => {
900 let k1 = self.ode_func(t, &h.view())? * dt;
902 let k2 = self.ode_func(t + dt / 2.0, &(&h + &k1 * 0.5).view())? * dt;
903 let k3 = self.ode_func(t + dt / 2.0, &(&h + &k2 * 0.5).view())? * dt;
904 let k4 = self.ode_func(t + dt, &(&h + &k3).view())? * dt;
905
906 h = h + (k1 + k2 * 2.0 + k3 * 2.0 + k4) / 6.0;
907 }
908 }
909 }
910
911 Ok(h)
912 }
913
914 fn forward_pass(&self, x: &ArrayView2<f64>) -> SklResult<(Array2<f64>, Array2<f64>)> {
915 let h0 = self.encode(x)?;
917
918 let h_final = self.solve_ode(&h0.view())?;
920
921 let x_reconstructed = self.decode(&h_final.view())?;
923
924 Ok((h_final, x_reconstructed))
925 }
926
927 pub fn get_manifold_trajectory(&self, x: &ArrayView2<f64>) -> SklResult<Vec<Array2<f64>>> {
928 let h0 = self.encode(x)?;
929 let dt = self.state.integration_time / self.state.num_time_steps as f64;
930 let mut h = h0.clone();
931 let mut trajectory = vec![h0];
932
933 for i in 0..self.state.num_time_steps {
934 let t = i as f64 * dt;
935
936 match self.state.solver_type {
937 ODESolverType::Euler => {
938 let dh_dt = self.ode_func(t, &h.view())?;
939 h = h + dh_dt * dt;
940 }
941 ODESolverType::RungeKutta4 => {
942 let k1 = self.ode_func(t, &h.view())? * dt;
943 let k2 = self.ode_func(t + dt / 2.0, &(&h + &k1 * 0.5).view())? * dt;
944 let k3 = self.ode_func(t + dt / 2.0, &(&h + &k2 * 0.5).view())? * dt;
945 let k4 = self.ode_func(t + dt, &(&h + &k3).view())? * dt;
946
947 h = h + (k1 + k2 * 2.0 + k3 * 2.0 + k4) / 6.0;
948 }
949 }
950
951 trajectory.push(h.clone());
952 }
953
954 Ok(trajectory)
955 }
956}
957
958impl Estimator for NeuralODE<Untrained> {
959 type Config = ();
960 type Error = SklearsError;
961 type Float = f64;
962
963 fn config(&self) -> &Self::Config {
964 &()
965 }
966}
967
968impl Fit<Array2<f64>, ()> for NeuralODE<Untrained> {
969 type Fitted = NeuralODE<TrainedNODE>;
970
971 fn fit(self, x: &Array2<f64>, _y: &()) -> SklResult<Self::Fitted> {
972 let (n_samples, n_features) = x.dim();
973
974 if n_samples == 0 || n_features == 0 {
975 return Err(SklearsError::InvalidInput("Empty input data".to_string()));
976 }
977
978 if self.n_components >= n_features {
979 return Err(SklearsError::InvalidInput(
980 "n_components must be less than number of features".to_string(),
981 ));
982 }
983
984 if self.integration_time <= 0.0 {
985 return Err(SklearsError::InvalidInput(
986 "integration_time must be positive".to_string(),
987 ));
988 }
989
990 if self.num_time_steps == 0 {
991 return Err(SklearsError::InvalidInput(
992 "num_time_steps must be positive".to_string(),
993 ));
994 }
995
996 let state = self.initialize_node_weights(n_features);
997
998 Ok(NeuralODE {
1003 n_components: self.n_components,
1004 hidden_layers: self.hidden_layers,
1005 integration_time: self.integration_time,
1006 num_time_steps: self.num_time_steps,
1007 solver_type: self.solver_type,
1008 learning_rate: self.learning_rate,
1009 epochs: self.epochs,
1010 state,
1011 })
1012 }
1013}
1014
1015impl Transform<Array2<f64>, Array2<f64>> for NeuralODE<TrainedNODE> {
1016 fn transform(&self, x: &Array2<f64>) -> SklResult<Array2<f64>> {
1017 let (h_final, _) = self.forward_pass(&x.view())?;
1018 Ok(h_final)
1019 }
1020}
1021
1022#[derive(Debug, Clone)]
1024pub struct ContinuousNormalizingFlow<S = Untrained> {
1025 n_components: usize,
1026 hidden_layers: Vec<usize>,
1027 integration_time: f64,
1028 num_time_steps: usize,
1029 solver_type: ODESolverType,
1030 trace_estimator: TraceEstimator,
1031 learning_rate: f64,
1032 epochs: usize,
1033 state: S,
1034}
1035
1036#[derive(Debug, Clone)]
1037pub enum TraceEstimator {
1038 Exact, Hutchinson, RademacherRandom, }
1045
1046#[derive(Debug, Clone)]
1047pub struct TrainedCNF {
1048 dynamics_weights: Vec<Array2<f64>>,
1049 dynamics_biases: Vec<Array1<f64>>,
1050 integration_time: f64,
1051 num_time_steps: usize,
1052 solver_type: ODESolverType,
1053 trace_estimator: TraceEstimator,
1054 input_dim: usize,
1055}
1056
1057impl ContinuousNormalizingFlow<Untrained> {
1058 pub fn new(n_components: usize) -> Self {
1059 Self {
1060 n_components,
1061 hidden_layers: vec![64, 32, 64],
1062 integration_time: 1.0,
1063 num_time_steps: 20,
1064 solver_type: ODESolverType::RungeKutta4,
1065 trace_estimator: TraceEstimator::Hutchinson,
1066 learning_rate: 0.001,
1067 epochs: 100,
1068 state: Untrained,
1069 }
1070 }
1071
1072 pub fn with_hidden_layers(mut self, layers: Vec<usize>) -> Self {
1073 self.hidden_layers = layers;
1074 self
1075 }
1076
1077 pub fn with_integration_time(mut self, time: f64) -> Self {
1078 self.integration_time = time;
1079 self
1080 }
1081
1082 pub fn with_num_time_steps(mut self, steps: usize) -> Self {
1083 self.num_time_steps = steps;
1084 self
1085 }
1086
1087 pub fn with_solver_type(mut self, solver: ODESolverType) -> Self {
1088 self.solver_type = solver;
1089 self
1090 }
1091
1092 pub fn with_trace_estimator(mut self, estimator: TraceEstimator) -> Self {
1093 self.trace_estimator = estimator;
1094 self
1095 }
1096
1097 pub fn with_learning_rate(mut self, lr: f64) -> Self {
1098 self.learning_rate = lr;
1099 self
1100 }
1101
1102 pub fn with_epochs(mut self, epochs: usize) -> Self {
1103 self.epochs = epochs;
1104 self
1105 }
1106
1107 fn initialize_cnf_weights(&self, input_dim: usize) -> TrainedCNF {
1108 let mut rng = thread_rng();
1109
1110 let mut dynamics_layer_sizes = vec![self.n_components];
1112 dynamics_layer_sizes.extend(&self.hidden_layers);
1113 dynamics_layer_sizes.push(self.n_components); let mut dynamics_weights = Vec::new();
1116 let mut dynamics_biases = Vec::new();
1117
1118 for i in 0..dynamics_layer_sizes.len() - 1 {
1119 let in_size = dynamics_layer_sizes[i];
1120 let out_size = dynamics_layer_sizes[i + 1];
1121
1122 let weight = Array2::from_shape_fn((in_size, out_size), |(_, _)| {
1123 Normal::new(0.0, (2.0 / (in_size + out_size) as f64).sqrt())
1124 .expect("operation should succeed")
1125 .sample(&mut rng)
1126 });
1127
1128 let bias = if i == dynamics_layer_sizes.len() - 2 {
1130 Array1::from_shape_fn(out_size, |_| {
1131 Normal::new(0.0, 0.01)
1132 .expect("operation should succeed")
1133 .sample(&mut rng)
1134 })
1135 } else {
1136 Array1::zeros(out_size)
1137 };
1138
1139 dynamics_weights.push(weight);
1140 dynamics_biases.push(bias);
1141 }
1142
1143 TrainedCNF {
1145 dynamics_weights,
1146 dynamics_biases,
1147 integration_time: self.integration_time,
1148 num_time_steps: self.num_time_steps,
1149 solver_type: self.solver_type.clone(),
1150 trace_estimator: self.trace_estimator.clone(),
1151 input_dim,
1152 }
1153 }
1154}
1155
1156impl ContinuousNormalizingFlow<TrainedCNF> {
1157 fn validate_input_dim(&self, data: &ArrayView2<f64>) -> SklResult<()> {
1158 if data.ncols() != self.state.input_dim {
1159 return Err(SklearsError::InvalidInput(format!(
1160 "Expected input with {} features, but received {}",
1161 self.state.input_dim,
1162 data.ncols()
1163 )));
1164 }
1165
1166 Ok(())
1167 }
1168
1169 fn split_input(&self, data: &ArrayView2<f64>) -> (Array2<f64>, Option<Array2<f64>>) {
1170 let latent = data
1171 .slice(scirs2_core::ndarray::s![.., 0..self.n_components])
1172 .to_owned();
1173
1174 if self.state.input_dim > self.n_components {
1175 let residual = data
1176 .slice(scirs2_core::ndarray::s![
1177 ..,
1178 self.n_components..self.state.input_dim
1179 ])
1180 .to_owned();
1181 (latent, Some(residual))
1182 } else {
1183 (latent, None)
1184 }
1185 }
1186
1187 fn combine_latent(&self, latent: &Array2<f64>, residual: Option<&Array2<f64>>) -> Array2<f64> {
1188 if self.state.input_dim == self.n_components {
1189 return latent.clone();
1190 }
1191
1192 let mut combined = Array2::zeros((latent.nrows(), self.state.input_dim));
1193 combined
1194 .slice_mut(scirs2_core::ndarray::s![.., 0..self.n_components])
1195 .assign(latent);
1196
1197 if let Some(residual) = residual {
1198 combined
1199 .slice_mut(scirs2_core::ndarray::s![
1200 ..,
1201 self.n_components..self.state.input_dim
1202 ])
1203 .assign(residual);
1204 }
1205
1206 combined
1207 }
1208
1209 fn integrate_latent(
1210 &self,
1211 latent0: &Array2<f64>,
1212 forward: bool,
1213 ) -> SklResult<(Array2<f64>, Array1<f64>)> {
1214 let mut latent = latent0.clone();
1215 let mut log_det = Array1::zeros(latent.nrows());
1216
1217 if self.state.num_time_steps == 0 {
1218 return Ok((latent, log_det));
1219 }
1220
1221 let base_dt = self.state.integration_time / self.state.num_time_steps as f64;
1222 let dt_signed = if forward { base_dt } else { -base_dt };
1223
1224 for step in 0..self.state.num_time_steps {
1225 let t = if forward {
1226 step as f64 * base_dt
1227 } else {
1228 self.state.integration_time - step as f64 * base_dt
1229 };
1230
1231 let trace = self.compute_trace_jacobian(t, &latent.view())?;
1232 let trace_contrib = trace.mapv(|val| val * dt_signed);
1233 log_det = log_det + trace_contrib;
1234
1235 match self.state.solver_type {
1236 ODESolverType::Euler => {
1237 let dz_dt = self.dynamics(t, &latent.view())?;
1238 latent = latent + dz_dt * dt_signed;
1239 }
1240 ODESolverType::RungeKutta4 => {
1241 let k1 = self.dynamics(t, &latent.view())? * dt_signed;
1242 let k2 = self.dynamics(t + dt_signed / 2.0, &(&latent + &k1 * 0.5).view())?
1243 * dt_signed;
1244 let k3 = self.dynamics(t + dt_signed / 2.0, &(&latent + &k2 * 0.5).view())?
1245 * dt_signed;
1246 let k4 = self.dynamics(t + dt_signed, &(&latent + &k3).view())? * dt_signed;
1247
1248 latent = latent + (k1 + k2 * 2.0 + k3 * 2.0 + k4) / 6.0;
1249 }
1250 }
1251 }
1252
1253 Ok((latent, log_det))
1254 }
1255
1256 fn dynamics(&self, _t: f64, z: &ArrayView2<f64>) -> SklResult<Array2<f64>> {
1257 let mut activations = z.to_owned();
1258
1259 for (i, (weights, biases)) in self
1260 .state
1261 .dynamics_weights
1262 .iter()
1263 .zip(self.state.dynamics_biases.iter())
1264 .enumerate()
1265 {
1266 activations = activations.dot(weights) + biases;
1267 if i < self.state.dynamics_weights.len() - 1 {
1269 activations.mapv_inplace(|x| x.tanh()); }
1271 }
1272
1273 Ok(activations)
1274 }
1275
1276 fn compute_trace_jacobian(&self, _t: f64, z: &ArrayView2<f64>) -> SklResult<Array1<f64>> {
1277 let epsilon = 1e-6;
1278 let (n_samples, n_dims) = z.dim();
1279
1280 match self.state.trace_estimator {
1281 TraceEstimator::Exact => {
1282 let mut traces = Array1::zeros(n_samples);
1284
1285 for sample_idx in 0..n_samples {
1286 let z_sample = z.slice(scirs2_core::ndarray::s![sample_idx, ..]).to_owned();
1287 let mut trace = 0.0;
1288
1289 for dim_idx in 0..n_dims {
1290 let mut z_plus = z_sample.clone();
1292 let mut z_minus = z_sample.clone();
1293
1294 z_plus[dim_idx] += epsilon;
1295 z_minus[dim_idx] -= epsilon;
1296
1297 let z_plus_2d = z_plus.view().insert_axis(scirs2_core::ndarray::Axis(0));
1298 let z_minus_2d = z_minus.view().insert_axis(scirs2_core::ndarray::Axis(0));
1299
1300 let f_plus = self.dynamics(_t, &z_plus_2d)?;
1301 let f_minus = self.dynamics(_t, &z_minus_2d)?;
1302
1303 let partial_deriv =
1305 (f_plus[[0, dim_idx]] - f_minus[[0, dim_idx]]) / (2.0 * epsilon);
1306 trace += partial_deriv;
1307 }
1308
1309 traces[sample_idx] = trace;
1310 }
1311
1312 Ok(traces)
1313 }
1314 TraceEstimator::Hutchinson => {
1315 let mut rng = thread_rng();
1317 let mut traces = Array1::zeros(n_samples);
1318
1319 for sample_idx in 0..n_samples {
1320 let z_sample = z.slice(scirs2_core::ndarray::s![sample_idx, ..]).to_owned();
1321
1322 let epsilon_vec = Array1::from_shape_fn(n_dims, |_| {
1324 if rng.random::<f64>() < 0.5 {
1325 -1.0
1326 } else {
1327 1.0
1328 }
1329 });
1330
1331 let z_plus = &z_sample + &epsilon_vec * epsilon;
1333 let z_minus = &z_sample - &epsilon_vec * epsilon;
1334
1335 let z_plus_2d = z_plus.view().insert_axis(scirs2_core::ndarray::Axis(0));
1336 let z_minus_2d = z_minus.view().insert_axis(scirs2_core::ndarray::Axis(0));
1337
1338 let f_plus = self.dynamics(_t, &z_plus_2d)?;
1339 let f_minus = self.dynamics(_t, &z_minus_2d)?;
1340
1341 let jv = (f_plus.slice(scirs2_core::ndarray::s![0, ..]).to_owned()
1342 - f_minus.slice(scirs2_core::ndarray::s![0, ..]).to_owned())
1343 / (2.0 * epsilon);
1344
1345 let trace_estimate = epsilon_vec.dot(&jv);
1347 traces[sample_idx] = trace_estimate;
1348 }
1349
1350 Ok(traces)
1351 }
1352 TraceEstimator::RademacherRandom => {
1353 let mut rng = thread_rng();
1355 let mut traces = Array1::zeros(n_samples);
1356
1357 for sample_idx in 0..n_samples {
1358 let z_sample = z.slice(scirs2_core::ndarray::s![sample_idx, ..]).to_owned();
1359
1360 let epsilon_vec = Array1::from_shape_fn(n_dims, |_| {
1362 Normal::new(0.0, 1.0)
1363 .expect("operation should succeed")
1364 .sample(&mut rng)
1365 });
1366
1367 let z_plus = &z_sample + &epsilon_vec * epsilon;
1368 let z_minus = &z_sample - &epsilon_vec * epsilon;
1369
1370 let z_plus_2d = z_plus.view().insert_axis(scirs2_core::ndarray::Axis(0));
1371 let z_minus_2d = z_minus.view().insert_axis(scirs2_core::ndarray::Axis(0));
1372
1373 let f_plus = self.dynamics(_t, &z_plus_2d)?;
1374 let f_minus = self.dynamics(_t, &z_minus_2d)?;
1375
1376 let jv = (f_plus.slice(scirs2_core::ndarray::s![0, ..]).to_owned()
1377 - f_minus.slice(scirs2_core::ndarray::s![0, ..]).to_owned())
1378 / (2.0 * epsilon);
1379
1380 let trace_estimate = epsilon_vec.dot(&jv);
1381 traces[sample_idx] = trace_estimate;
1382 }
1383
1384 Ok(traces)
1385 }
1386 }
1387 }
1388
1389 pub fn forward_flow(&self, z0: &ArrayView2<f64>) -> SklResult<(Array2<f64>, Array1<f64>)> {
1390 self.validate_input_dim(z0)?;
1391 let (latent0, residual) = self.split_input(z0);
1392 let (latent_final, log_det) = self.integrate_latent(&latent0, true)?;
1393 let combined = self.combine_latent(&latent_final, residual.as_ref());
1394
1395 Ok((combined, log_det))
1396 }
1397
1398 pub fn backward_flow(&self, z1: &ArrayView2<f64>) -> SklResult<(Array2<f64>, Array1<f64>)> {
1399 self.validate_input_dim(z1)?;
1400 let (latent0, residual) = self.split_input(z1);
1401 let (latent_initial, log_det) = self.integrate_latent(&latent0, false)?;
1402 let combined = self.combine_latent(&latent_initial, residual.as_ref());
1403
1404 Ok((combined, log_det))
1405 }
1406
1407 pub fn log_likelihood(
1408 &self,
1409 x: &ArrayView2<f64>,
1410 base_log_prob: f64,
1411 ) -> SklResult<Array1<f64>> {
1412 let (_, log_det_jac) = self.forward_flow(x)?;
1413
1414 let log_likelihood = Array1::from_elem(x.nrows(), base_log_prob) + log_det_jac;
1417
1418 Ok(log_likelihood)
1419 }
1420
1421 pub fn sample(
1422 &self,
1423 n_samples: usize,
1424 base_distribution: &dyn Fn(usize, usize) -> Array2<f64>,
1425 ) -> SklResult<Array2<f64>> {
1426 let latent_target = base_distribution(n_samples, self.n_components);
1428 let (latent_source, _) = self.integrate_latent(&latent_target, false)?;
1429
1430 if self.state.input_dim == self.n_components {
1431 Ok(latent_source)
1432 } else {
1433 let mut combined = Array2::zeros((n_samples, self.state.input_dim));
1434 combined
1435 .slice_mut(scirs2_core::ndarray::s![.., 0..self.n_components])
1436 .assign(&latent_source);
1437 Ok(combined)
1438 }
1439 }
1440}
1441
1442impl Estimator for ContinuousNormalizingFlow<Untrained> {
1443 type Config = ();
1444 type Error = SklearsError;
1445 type Float = f64;
1446
1447 fn config(&self) -> &Self::Config {
1448 &()
1449 }
1450}
1451
1452impl Fit<Array2<f64>, ()> for ContinuousNormalizingFlow<Untrained> {
1453 type Fitted = ContinuousNormalizingFlow<TrainedCNF>;
1454
1455 fn fit(self, x: &Array2<f64>, _y: &()) -> SklResult<Self::Fitted> {
1456 let (n_samples, n_features) = x.dim();
1457
1458 if n_samples == 0 || n_features == 0 {
1459 return Err(SklearsError::InvalidInput("Empty input data".to_string()));
1460 }
1461
1462 if self.n_components > n_features {
1463 return Err(SklearsError::InvalidInput(
1464 "n_components should not exceed number of features".to_string(),
1465 ));
1466 }
1467
1468 if self.integration_time <= 0.0 {
1469 return Err(SklearsError::InvalidInput(
1470 "integration_time must be positive".to_string(),
1471 ));
1472 }
1473
1474 if self.num_time_steps == 0 {
1475 return Err(SklearsError::InvalidInput(
1476 "num_time_steps must be positive".to_string(),
1477 ));
1478 }
1479
1480 let state = self.initialize_cnf_weights(n_features);
1481
1482 Ok(ContinuousNormalizingFlow {
1487 n_components: self.n_components,
1488 hidden_layers: self.hidden_layers,
1489 integration_time: self.integration_time,
1490 num_time_steps: self.num_time_steps,
1491 solver_type: self.solver_type,
1492 trace_estimator: self.trace_estimator,
1493 learning_rate: self.learning_rate,
1494 epochs: self.epochs,
1495 state,
1496 })
1497 }
1498}
1499
1500impl Transform<Array2<f64>, Array2<f64>> for ContinuousNormalizingFlow<TrainedCNF> {
1501 fn transform(&self, x: &Array2<f64>) -> SklResult<Array2<f64>> {
1502 self.validate_input_dim(&x.view())?;
1503 let (latent0, _) = self.split_input(&x.view());
1504 let (latent_final, _) = self.integrate_latent(&latent0, true)?;
1505 Ok(latent_final)
1506 }
1507}
1508
1509#[allow(non_snake_case)]
1510#[cfg(test)]
1511mod tests {
1512 use super::*;
1513 use scirs2_core::ndarray::Array2;
1514
1515 #[test]
1516 fn test_autoencoder_basic() {
1517 let data = Array2::from_shape_fn((100, 10), |(i, j)| i as f64 * 0.1 + j as f64 * 0.01);
1518 let autoencoder = AutoencoderManifold::new(5)
1519 .with_epochs(10)
1520 .with_batch_size(16);
1521
1522 let fitted = autoencoder
1523 .fit(&data, &())
1524 .expect("operation should succeed");
1525 let encoded = fitted.transform(&data).expect("operation should succeed");
1526
1527 assert_eq!(encoded.ncols(), 5);
1528 assert_eq!(encoded.nrows(), 100);
1529 }
1530
1531 #[test]
1532 fn test_autoencoder_invalid_params() {
1533 let data = Array2::from_shape_fn((50, 5), |(i, j)| i as f64 + j as f64);
1534 let autoencoder = AutoencoderManifold::new(10); assert!(autoencoder.fit(&data, &()).is_err());
1537 }
1538
1539 #[test]
1540 fn test_variational_autoencoder_basic() {
1541 let vae = VariationalAutoencoder::new(3).with_epochs(5).with_beta(0.5);
1542
1543 assert_eq!(vae.n_components, 3);
1544 assert_eq!(vae.beta, 0.5);
1545 }
1546
1547 #[test]
1548 fn test_autoencoder_configuration() {
1549 let autoencoder = AutoencoderManifold::new(3)
1550 .with_hidden_layers(vec![64, 32])
1551 .with_learning_rate(0.01)
1552 .with_batch_size(64)
1553 .with_epochs(50);
1554
1555 assert_eq!(autoencoder.hidden_layers, vec![64, 32]);
1556 assert_eq!(autoencoder.learning_rate, 0.01);
1557 assert_eq!(autoencoder.batch_size, 64);
1558 assert_eq!(autoencoder.epochs, 50);
1559 }
1560
1561 #[test]
1562 fn test_autoencoder_empty_data() {
1563 let data = Array2::zeros((0, 5));
1564 let autoencoder = AutoencoderManifold::new(2);
1565
1566 assert!(autoencoder.fit(&data, &()).is_err());
1567 }
1568
1569 #[test]
1570 fn test_adversarial_autoencoder_basic() {
1571 let data = Array2::from_shape_fn((100, 10), |(i, j)| i as f64 * 0.1 + j as f64 * 0.01);
1572 let aae = AdversarialAutoencoder::new(5)
1573 .with_epochs(10)
1574 .with_batch_size(16)
1575 .with_adversarial_weight(0.5);
1576
1577 let fitted = aae.fit(&data, &()).expect("operation should succeed");
1578 let encoded = fitted.transform(&data).expect("operation should succeed");
1579
1580 assert_eq!(encoded.ncols(), 5);
1581 assert_eq!(encoded.nrows(), 100);
1582 }
1583
1584 #[test]
1585 fn test_adversarial_autoencoder_configuration() {
1586 let aae = AdversarialAutoencoder::new(3)
1587 .with_hidden_layers(vec![64, 32])
1588 .with_discriminator_layers(vec![32, 16])
1589 .with_learning_rate(0.01)
1590 .with_adversarial_weight(2.0)
1591 .with_prior_type(PriorType::Uniform);
1592
1593 assert_eq!(aae.hidden_layers, vec![64, 32]);
1594 assert_eq!(aae.discriminator_layers, vec![32, 16]);
1595 assert_eq!(aae.learning_rate, 0.01);
1596 assert_eq!(aae.adversarial_weight, 2.0);
1597 matches!(aae.prior_type, PriorType::Uniform);
1598 }
1599
1600 #[test]
1601 fn test_adversarial_autoencoder_prior_types() {
1602 let gaussian_aae = AdversarialAutoencoder::new(3).with_prior_type(PriorType::Gaussian);
1603 assert!(matches!(gaussian_aae.prior_type, PriorType::Gaussian));
1604
1605 let uniform_aae = AdversarialAutoencoder::new(3).with_prior_type(PriorType::Uniform);
1606 assert!(matches!(uniform_aae.prior_type, PriorType::Uniform));
1607
1608 let categorical_aae =
1609 AdversarialAutoencoder::new(3).with_prior_type(PriorType::Categorical(5));
1610 assert!(matches!(
1611 categorical_aae.prior_type,
1612 PriorType::Categorical(5)
1613 ));
1614 }
1615
1616 #[test]
1617 fn test_adversarial_autoencoder_invalid_params() {
1618 let data = Array2::from_shape_fn((50, 5), |(i, j)| i as f64 + j as f64);
1619 let aae = AdversarialAutoencoder::new(10); assert!(aae.fit(&data, &()).is_err());
1622 }
1623
1624 #[test]
1625 fn test_adversarial_autoencoder_empty_data() {
1626 let data = Array2::zeros((0, 5));
1627 let aae = AdversarialAutoencoder::new(2);
1628
1629 assert!(aae.fit(&data, &()).is_err());
1630 }
1631
1632 #[test]
1633 fn test_neural_ode_basic() {
1634 let data = Array2::from_shape_fn((50, 8), |(i, j)| i as f64 * 0.1 + j as f64 * 0.01);
1635 let node = NeuralODE::new(3)
1636 .with_integration_time(0.5)
1637 .with_num_time_steps(5)
1638 .with_solver_type(ODESolverType::RungeKutta4);
1639
1640 let fitted = node.fit(&data, &()).expect("operation should succeed");
1641 let encoded = fitted.transform(&data).expect("operation should succeed");
1642
1643 assert_eq!(encoded.ncols(), 3);
1644 assert_eq!(encoded.nrows(), 50);
1645 }
1646
1647 #[test]
1648 fn test_neural_ode_configuration() {
1649 let node = NeuralODE::new(4)
1650 .with_hidden_layers(vec![32, 16])
1651 .with_integration_time(2.0)
1652 .with_num_time_steps(20)
1653 .with_solver_type(ODESolverType::Euler)
1654 .with_learning_rate(0.01)
1655 .with_epochs(50);
1656
1657 assert_eq!(node.hidden_layers, vec![32, 16]);
1658 assert_eq!(node.integration_time, 2.0);
1659 assert_eq!(node.num_time_steps, 20);
1660 assert!(matches!(node.solver_type, ODESolverType::Euler));
1661 assert_eq!(node.learning_rate, 0.01);
1662 assert_eq!(node.epochs, 50);
1663 }
1664
1665 #[test]
1666 fn test_neural_ode_solver_types() {
1667 let euler_node = NeuralODE::new(2).with_solver_type(ODESolverType::Euler);
1668 assert!(matches!(euler_node.solver_type, ODESolverType::Euler));
1669
1670 let rk4_node = NeuralODE::new(2).with_solver_type(ODESolverType::RungeKutta4);
1671 assert!(matches!(rk4_node.solver_type, ODESolverType::RungeKutta4));
1672 }
1673
1674 #[test]
1675 fn test_neural_ode_invalid_params() {
1676 let data = Array2::from_shape_fn((30, 5), |(i, j)| i as f64 + j as f64);
1677
1678 let node = NeuralODE::new(10);
1680 assert!(node.fit(&data, &()).is_err());
1681
1682 let node = NeuralODE::new(2).with_integration_time(-1.0);
1684 assert!(node.fit(&data, &()).is_err());
1685
1686 let node = NeuralODE::new(2).with_num_time_steps(0);
1688 assert!(node.fit(&data, &()).is_err());
1689 }
1690
1691 #[test]
1692 fn test_neural_ode_empty_data() {
1693 let data = Array2::zeros((0, 5));
1694 let node = NeuralODE::new(2);
1695
1696 assert!(node.fit(&data, &()).is_err());
1697 }
1698
1699 #[test]
1700 fn test_neural_ode_manifold_trajectory() {
1701 let data = Array2::from_shape_fn((10, 6), |(i, j)| i as f64 * 0.1 + j as f64 * 0.01);
1702 let node = NeuralODE::new(2).with_num_time_steps(3);
1703
1704 let fitted = node.fit(&data, &()).expect("operation should succeed");
1705 let trajectory = fitted
1706 .get_manifold_trajectory(&data.view())
1707 .expect("operation should succeed");
1708
1709 assert_eq!(trajectory.len(), 4);
1711
1712 for state in &trajectory {
1714 assert_eq!(state.ncols(), 2); assert_eq!(state.nrows(), 10); }
1717 }
1718
1719 #[test]
1720 fn test_continuous_normalizing_flow_basic() {
1721 let data = Array2::from_shape_fn((30, 4), |(i, j)| i as f64 * 0.1 + j as f64 * 0.01);
1722 let cnf = ContinuousNormalizingFlow::new(3)
1723 .with_integration_time(0.5)
1724 .with_num_time_steps(8)
1725 .with_trace_estimator(TraceEstimator::Hutchinson);
1726
1727 let fitted = cnf.fit(&data, &()).expect("operation should succeed");
1728 let transformed = fitted.transform(&data).expect("operation should succeed");
1729
1730 assert_eq!(transformed.ncols(), 3);
1731 assert_eq!(transformed.nrows(), 30);
1732 }
1733
1734 #[test]
1735 fn test_continuous_normalizing_flow_configuration() {
1736 let cnf = ContinuousNormalizingFlow::new(2)
1737 .with_hidden_layers(vec![32, 16, 32])
1738 .with_integration_time(2.0)
1739 .with_num_time_steps(25)
1740 .with_solver_type(ODESolverType::Euler)
1741 .with_trace_estimator(TraceEstimator::Exact)
1742 .with_learning_rate(0.005)
1743 .with_epochs(150);
1744
1745 assert_eq!(cnf.hidden_layers, vec![32, 16, 32]);
1746 assert_eq!(cnf.integration_time, 2.0);
1747 assert_eq!(cnf.num_time_steps, 25);
1748 assert!(matches!(cnf.solver_type, ODESolverType::Euler));
1749 assert!(matches!(cnf.trace_estimator, TraceEstimator::Exact));
1750 assert_eq!(cnf.learning_rate, 0.005);
1751 assert_eq!(cnf.epochs, 150);
1752 }
1753
1754 #[test]
1755 fn test_cnf_trace_estimator_types() {
1756 let exact_cnf =
1757 ContinuousNormalizingFlow::new(2).with_trace_estimator(TraceEstimator::Exact);
1758 assert!(matches!(exact_cnf.trace_estimator, TraceEstimator::Exact));
1759
1760 let hutchinson_cnf =
1761 ContinuousNormalizingFlow::new(2).with_trace_estimator(TraceEstimator::Hutchinson);
1762 assert!(matches!(
1763 hutchinson_cnf.trace_estimator,
1764 TraceEstimator::Hutchinson
1765 ));
1766
1767 let rademacher_cnf = ContinuousNormalizingFlow::new(2)
1768 .with_trace_estimator(TraceEstimator::RademacherRandom);
1769 assert!(matches!(
1770 rademacher_cnf.trace_estimator,
1771 TraceEstimator::RademacherRandom
1772 ));
1773 }
1774
1775 #[test]
1776 fn test_cnf_forward_backward_flow() {
1777 let data = Array2::from_shape_fn((5, 3), |(i, j)| i as f64 + j as f64);
1778 let cnf = ContinuousNormalizingFlow::new(3)
1779 .with_num_time_steps(5)
1780 .with_integration_time(1.0);
1781
1782 let fitted = cnf.fit(&data, &()).expect("operation should succeed");
1783
1784 let (z, log_det_forward) = fitted
1786 .forward_flow(&data.view())
1787 .expect("operation should succeed");
1788 assert_eq!(z.dim(), data.dim());
1789 assert_eq!(log_det_forward.len(), data.nrows());
1790
1791 let (x_reconstructed, log_det_backward) = fitted
1793 .backward_flow(&z.view())
1794 .expect("operation should succeed");
1795 assert_eq!(x_reconstructed.dim(), data.dim());
1796 assert_eq!(log_det_backward.len(), data.nrows());
1797
1798 let reconstruction_error = (&data - &x_reconstructed).mapv(|x| x.abs()).sum();
1800 assert!(reconstruction_error < 1.0); }
1802
1803 #[test]
1804 fn test_cnf_log_likelihood() {
1805 let data = Array2::from_shape_fn((10, 2), |(i, j)| i as f64 * 0.1 + j as f64 * 0.01);
1806 let cnf = ContinuousNormalizingFlow::new(2).with_num_time_steps(5);
1807
1808 let fitted = cnf.fit(&data, &()).expect("operation should succeed");
1809 let log_likelihood = fitted
1810 .log_likelihood(&data.view(), 0.0)
1811 .expect("operation should succeed");
1812
1813 assert_eq!(log_likelihood.len(), data.nrows());
1814 for &ll in log_likelihood.iter() {
1816 assert!(ll.is_finite());
1817 }
1818 }
1819
1820 #[test]
1821 fn test_cnf_sampling() {
1822 let data = Array2::from_shape_fn((20, 3), |(i, j)| i as f64 * 0.1 + j as f64 * 0.01);
1823 let cnf = ContinuousNormalizingFlow::new(3).with_num_time_steps(5);
1824
1825 let fitted = cnf.fit(&data, &()).expect("operation should succeed");
1826
1827 let gaussian_sampler = |n_samples: usize, n_dims: usize| -> Array2<f64> {
1829 let mut rng = thread_rng();
1830 Array2::from_shape_fn((n_samples, n_dims), |(_, _)| {
1831 Normal::new(0.0, 1.0)
1832 .expect("operation should succeed")
1833 .sample(&mut rng)
1834 })
1835 };
1836
1837 let samples = fitted
1838 .sample(15, &gaussian_sampler)
1839 .expect("operation should succeed");
1840 assert_eq!(samples.nrows(), 15);
1841 assert_eq!(samples.ncols(), 3);
1842
1843 for &val in samples.iter() {
1845 assert!(val.is_finite());
1846 }
1847 }
1848
1849 #[test]
1850 fn test_cnf_invalid_params() {
1851 let data = Array2::from_shape_fn((25, 4), |(i, j)| i as f64 + j as f64);
1852
1853 let cnf = ContinuousNormalizingFlow::new(6);
1855 assert!(cnf.fit(&data, &()).is_err());
1856
1857 let cnf = ContinuousNormalizingFlow::new(2).with_integration_time(-0.5);
1859 assert!(cnf.fit(&data, &()).is_err());
1860
1861 let cnf = ContinuousNormalizingFlow::new(2).with_num_time_steps(0);
1863 assert!(cnf.fit(&data, &()).is_err());
1864 }
1865
1866 #[test]
1867 fn test_cnf_empty_data() {
1868 let data = Array2::zeros((0, 4));
1869 let cnf = ContinuousNormalizingFlow::new(2);
1870
1871 assert!(cnf.fit(&data, &()).is_err());
1872 }
1873}