Skip to main content

sklears_manifold/
deep_learning.rs

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)] // retained for serialization/introspection
22pub 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
106        TrainedAutoencoder {
107            encoder_weights,
108            encoder_biases,
109            decoder_weights,
110            decoder_biases,
111        }
112    }
113}
114
115impl AutoencoderManifold<TrainedAutoencoder> {
116    #[allow(dead_code)] // retained for introspection
117    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)); // ReLU
128        }
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)); // ReLU
147            }
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        // Note: For a complete implementation, we would need to implement
183        // the training loop with gradient descent here. For now, we return
184        // the initialized model as a placeholder.
185
186        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)); // ReLU
209        }
210
211        Ok(activations)
212    }
213}
214
215#[derive(Debug, Clone)]
216#[allow(dead_code)] // retained for serialization/introspection
217pub 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, // KL divergence weight
224    state: S,
225}
226
227#[derive(Debug, Clone)]
228#[allow(dead_code)] // retained for serialization/introspection
229pub 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)] // retained for introspection
274    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)] // retained for introspection
286    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)); // ReLU
314        }
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/// Adversarial Autoencoder (AAE) - Combines autoencoder reconstruction with adversarial regularization
324#[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
340    Gaussian,
341    /// Uniform
342    Uniform,
343    /// Categorical
344    Categorical(usize), // number of categories
345}
346
347#[derive(Debug, Clone)]
348#[allow(dead_code)] // retained for serialization/introspection
349pub 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        // Initialize encoder
413        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        // Initialize decoder (reverse of encoder)
436        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        // Initialize discriminator
458        let mut disc_layer_sizes = vec![self.n_components];
459        disc_layer_sizes.extend(&self.discriminator_layers);
460        disc_layer_sizes.push(1); // Binary classification output
461
462        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
481        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            // Apply ReLU except for the final layer
505            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)] // retained for introspection
520    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            // Apply ReLU except for the final layer
532            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)] // retained for introspection
541    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                // ReLU for hidden layers
555                activations.mapv_inplace(|x| x.max(0.0));
556            } else {
557                // Sigmoid for output layer
558                activations.mapv_inplace(|x| 1.0 / (1.0 + (-x).exp()));
559            }
560        }
561
562        Ok(activations)
563    }
564
565    #[allow(dead_code)] // retained for introspection
566    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)] // retained for introspection
598    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)] // retained for introspection
608    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        // Note: For a complete implementation, we would need to implement
651        // the full adversarial training loop with alternating updates between
652        // autoencoder and discriminator. For now, we return the initialized model.
653
654        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/// Neural Ordinary Differential Equation (NODE) - Models continuous dynamics for manifold learning
675#[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
690    Euler,
691    /// RungeKutta4
692    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        // Initialize encoder (compress to manifold dimension)
756        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        // Initialize ODE function (dynamics in manifold space)
776        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); // Output same dim as input
779
780        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        // Initialize decoder (expand back to original dimension)
799        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
819        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            // No activation for encoder to preserve manifold structure
845        }
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            // No activation for decoder to preserve reconstruction
861        }
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            // Apply activation function except for output layer
878            if i < self.state.ode_func_weights.len() - 1 {
879                activations.mapv_inplace(|x| x.tanh()); // Tanh for bounded dynamics
880            }
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                    // Forward Euler: h_{n+1} = h_n + dt * f(t_n, h_n)
896                    let dh_dt = self.ode_func(t, &h.view())?;
897                    h = h + dh_dt * dt;
898                }
899                ODESolverType::RungeKutta4 => {
900                    // 4th-order Runge-Kutta
901                    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        // Encode to manifold space
916        let h0 = self.encode(x)?;
917
918        // Solve ODE in manifold space
919        let h_final = self.solve_ode(&h0.view())?;
920
921        // Decode back to original space
922        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        // Note: For a complete implementation, we would need to implement
999        // the training loop with gradient computation through the ODE solver.
1000        // This requires adjoint sensitivity method for backpropagation.
1001
1002        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/// Continuous Normalizing Flow (CNF) - Invertible transformation through continuous dynamics
1023#[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
1039    Exact, // For small dimensions
1040    /// Hutchinson
1041    Hutchinson, // Stochastic trace estimation
1042    /// RademacherRandom
1043    RademacherRandom, // Random projection trace estimation
1044}
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        // Initialize dynamics function (velocity field)
1111        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); // Output same dimension as input
1114
1115        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            // Initialize biases to zero except for the final layer (small random bias)
1129            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
1144        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            // Apply activation function except for output layer
1268            if i < self.state.dynamics_weights.len() - 1 {
1269                activations.mapv_inplace(|x| x.tanh()); // Smooth, bounded activation
1270            }
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                // Compute exact trace using finite differences (expensive for large dimensions)
1283                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                        // Compute partial derivative using finite differences
1291                        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                        // Partial derivative of f_i with respect to z_i
1304                        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                // Hutchinson's stochastic trace estimator: Tr(J) ≈ E[ε^T J ε] where ε ~ Rademacher
1316                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                    // Generate random Rademacher vector
1323                    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                    // Compute J * ε using finite differences
1332                    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                    // ε^T * (J * ε) = ε^T * jv
1346                    let trace_estimate = epsilon_vec.dot(&jv);
1347                    traces[sample_idx] = trace_estimate;
1348                }
1349
1350                Ok(traces)
1351            }
1352            TraceEstimator::RademacherRandom => {
1353                // Alternative stochastic estimator
1354                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                    // Generate random Gaussian vector
1361                    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        // log p(x) = log p(z) + log |det J|
1415        // where z = f(x) and J is the Jacobian of the transformation
1416        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        // Sample from base distribution
1427        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        // Note: For a complete implementation, we would need to implement
1483        // the training loop with maximum likelihood estimation and
1484        // gradient computation through the CNF.
1485
1486        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); // n_components > n_features
1535
1536        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); // n_components > n_features
1620
1621        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        // n_components >= n_features
1679        let node = NeuralODE::new(10);
1680        assert!(node.fit(&data, &()).is_err());
1681
1682        // Invalid integration time
1683        let node = NeuralODE::new(2).with_integration_time(-1.0);
1684        assert!(node.fit(&data, &()).is_err());
1685
1686        // Invalid time steps
1687        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        // Should have initial state + 3 time steps = 4 total states
1710        assert_eq!(trajectory.len(), 4);
1711
1712        // Each state should have the same dimensions
1713        for state in &trajectory {
1714            assert_eq!(state.ncols(), 2); // n_components
1715            assert_eq!(state.nrows(), 10); // n_samples
1716        }
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        // Test forward flow
1785        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        // Test backward flow
1792        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        // Check that forward and backward flows are approximately inverses
1799        let reconstruction_error = (&data - &x_reconstructed).mapv(|x| x.abs()).sum();
1800        assert!(reconstruction_error < 1.0); // Allow some numerical error
1801    }
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        // Check that all likelihood values are finite
1815        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        // Define a simple Gaussian base distribution
1828        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        // Check that samples are finite
1844        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        // n_components > n_features
1854        let cnf = ContinuousNormalizingFlow::new(6);
1855        assert!(cnf.fit(&data, &()).is_err());
1856
1857        // Invalid integration time
1858        let cnf = ContinuousNormalizingFlow::new(2).with_integration_time(-0.5);
1859        assert!(cnf.fit(&data, &()).is_err());
1860
1861        // Invalid time steps
1862        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}