1use crate::random::{
49 core::{seeded_rng, Random},
50 distributions::MultivariateNormal,
51 parallel::{ParallelRng, ThreadLocalRngPool},
52};
53use ::ndarray::{s, Array1, Array2, Array3, Axis};
54use rand::{Rng, RngExt};
55use rand_distr::{Distribution, Normal, Uniform};
56use std::collections::VecDeque;
57
58#[derive(Debug, Clone)]
64pub struct NormalizingFlow {
65 dimension: usize,
66 num_layers: usize,
67 flow_layers: Vec<FlowLayer>,
68 base_distribution: MultivariateNormal,
69 trained: bool,
70 training_history: Vec<f64>,
72}
73
74#[derive(Debug, Clone)]
75struct FlowLayer {
76 mask: Array1<bool>,
78 scale_network: MLP,
79 translation_network: MLP,
80}
81
82#[derive(Debug, Clone)]
83struct MLP {
84 weights: Vec<Array2<f64>>,
86 biases: Vec<Array1<f64>>,
87 hidden_dims: Vec<usize>,
88}
89
90impl NormalizingFlow {
91 pub fn new(dimension: usize, num_layers: usize) -> Self {
93 let mut flow_layers = Vec::new();
94
95 for i in 0..num_layers {
96 let mut mask = Array1::from_elem(dimension, false);
98 for j in 0..dimension {
99 mask[j] = (j + i) % 2 == 0;
100 }
101
102 let hidden_dim = dimension.max(32);
103 let scale_net = MLP::new(&[dimension / 2, hidden_dim, hidden_dim, dimension / 2]);
104 let trans_net = MLP::new(&[dimension / 2, hidden_dim, hidden_dim, dimension / 2]);
105
106 flow_layers.push(FlowLayer {
107 mask,
108 scale_network: scale_net,
109 translation_network: trans_net,
110 });
111 }
112
113 let mut cov_matrix = vec![vec![0.0; dimension]; dimension];
115 for i in 0..dimension {
116 cov_matrix[i][i] = 1.0;
117 }
118
119 let base_distribution =
120 MultivariateNormal::new(vec![0.0; dimension], cov_matrix).expect("Operation failed");
121
122 Self {
123 dimension,
124 num_layers,
125 flow_layers,
126 base_distribution,
127 trained: false,
128 training_history: Vec::new(),
129 }
130 }
131
132 pub fn train(&mut self, training_data: &Array2<f64>, num_epochs: usize) -> Result<(), String> {
134 let learning_rate = 0.001;
135 let batch_size = 32;
136
137 for epoch in 0..num_epochs {
138 let num_batches = training_data.nrows().div_ceil(batch_size);
140
141 let mut epoch_loss = 0.0;
142 let mut epoch_samples = 0usize;
143
144 for batch_idx in 0..num_batches {
145 let start_idx = batch_idx * batch_size;
146 let end_idx = (start_idx + batch_size).min(training_data.nrows());
147
148 let batch = training_data.slice(s![start_idx..end_idx, ..]);
149
150 let mut total_loss = 0.0;
152 for i in 0..batch.nrows() {
153 let x = batch.row(i).to_owned();
154 let (z, log_det_jacobian) = self.forward(&x)?;
155
156 let log_prob_z = self.base_distribution.log_probability(&z.to_vec())?;
158 let log_prob_x = log_prob_z + log_det_jacobian;
159
160 total_loss -= log_prob_x; }
162
163 epoch_loss += total_loss;
164 epoch_samples += batch.nrows();
165
166 self.update_parameters(learning_rate, &batch)?;
168 }
169
170 let avg_loss = if epoch_samples > 0 {
172 epoch_loss / epoch_samples as f64
173 } else {
174 0.0
175 };
176 self.training_history.push(avg_loss);
177
178 if epoch % 100 == 0 {
179 println!("Epoch {epoch}: loss = {avg_loss:.6}");
180 }
181 }
182
183 self.trained = true;
184 Ok(())
185 }
186
187 pub fn training_history(&self) -> &[f64] {
193 &self.training_history
194 }
195
196 fn forward(&self, x: &Array1<f64>) -> Result<(Array1<f64>, f64), String> {
198 let mut z = x.clone();
199 let mut log_det_jacobian = 0.0;
200
201 for layer in &self.flow_layers {
202 let (new_z, log_det) = layer.forward(&z)?;
203 z = new_z;
204 log_det_jacobian += log_det;
205 }
206
207 Ok((z, log_det_jacobian))
208 }
209
210 fn inverse(&self, z: &Array1<f64>) -> Result<Array1<f64>, String> {
212 let mut x = z.clone();
213
214 for layer in self.flow_layers.iter().rev() {
216 x = layer.inverse(&x)?;
217 }
218
219 Ok(x)
220 }
221
222 pub fn sample(&self, num_samples: usize, seed: u64) -> Result<Array2<f64>, String> {
224 if !self.trained {
225 return Err("Flow must be trained before sampling".to_string());
226 }
227
228 let mut rng = seeded_rng(seed);
229 let mut samples = Array2::zeros((num_samples, self.dimension));
230
231 for i in 0..num_samples {
232 let z = Array1::from_vec(self.base_distribution.sample(&mut rng));
234
235 let x = self.inverse(&z)?;
237
238 for j in 0..self.dimension {
239 samples[[i, j]] = x[j];
240 }
241 }
242
243 Ok(samples)
244 }
245
246 pub fn log_probability(&self, x: &Array1<f64>) -> Result<f64, String> {
248 if !self.trained {
249 return Err("Flow must be trained before computing probabilities".to_string());
250 }
251
252 let (z, log_det_jacobian) = self.forward(x)?;
253 let log_prob_z = self.base_distribution.log_probability(&z.to_vec())?;
254 Ok(log_prob_z + log_det_jacobian)
255 }
256
257 fn update_parameters(
259 &mut self,
260 learning_rate: f64,
261 batch: &crate::ndarray::ArrayView2<f64>,
262 ) -> Result<(), String> {
263 for layer in &mut self.flow_layers {
265 layer.update_parameters(learning_rate, batch)?;
266 }
267 Ok(())
268 }
269}
270
271impl FlowLayer {
272 fn forward(&self, x: &Array1<f64>) -> Result<(Array1<f64>, f64), String> {
274 let mut y = x.clone();
275 let mut log_det_jacobian = 0.0;
276
277 let x_unchanged: Vec<f64> = x
279 .iter()
280 .enumerate()
281 .filter(|(i, _)| self.mask[*i])
282 .map(|(_, &val)| val)
283 .collect();
284
285 let x_to_transform: Vec<f64> = x
286 .iter()
287 .enumerate()
288 .filter(|(i, _)| !self.mask[*i])
289 .map(|(_, &val)| val)
290 .collect();
291
292 if !x_unchanged.is_empty() && !x_to_transform.is_empty() {
293 let scale = self
295 .scale_network
296 .forward(&Array1::from_vec(x_unchanged.clone()))?;
297 let translation = self
298 .translation_network
299 .forward(&Array1::from_vec(x_unchanged))?;
300
301 let mut transform_idx = 0;
303 for (i, &masked) in self.mask.iter().enumerate() {
304 if !masked && transform_idx < scale.len() && transform_idx < translation.len() {
305 let s = scale[transform_idx];
306 let t = translation[transform_idx];
307 y[i] = x_to_transform[transform_idx] * s.exp() + t;
308 log_det_jacobian += s;
309 transform_idx += 1;
310 }
311 }
312 }
313
314 Ok((y, log_det_jacobian))
315 }
316
317 fn inverse(&self, y: &Array1<f64>) -> Result<Array1<f64>, String> {
319 let mut x = y.clone();
320
321 let y_unchanged: Vec<f64> = y
323 .iter()
324 .enumerate()
325 .filter(|(i, _)| self.mask[*i])
326 .map(|(_, &val)| val)
327 .collect();
328
329 if !y_unchanged.is_empty() {
330 let scale = self
332 .scale_network
333 .forward(&Array1::from_vec(y_unchanged.clone()))?;
334 let translation = self
335 .translation_network
336 .forward(&Array1::from_vec(y_unchanged))?;
337
338 let mut transform_idx = 0;
340 for (i, &masked) in self.mask.iter().enumerate() {
341 if !masked && transform_idx < scale.len() && transform_idx < translation.len() {
342 let s = scale[transform_idx];
343 let t = translation[transform_idx];
344 x[i] = (y[i] - t) * (-s).exp();
345 transform_idx += 1;
346 }
347 }
348 }
349
350 Ok(x)
351 }
352
353 fn update_parameters(
355 &mut self,
356 learning_rate: f64,
357 _batch: &crate::ndarray::ArrayView2<f64>,
358 ) -> Result<(), String> {
359 self.scale_network.update_parameters(learning_rate)?;
361 self.translation_network.update_parameters(learning_rate)?;
362 Ok(())
363 }
364}
365
366impl MLP {
367 fn new(layer_sizes: &[usize]) -> Self {
369 let mut weights = Vec::new();
370 let mut biases = Vec::new();
371
372 for i in 0..layer_sizes.len() - 1 {
373 let w = Array2::zeros((layer_sizes[i + 1], layer_sizes[i]));
374 let b = Array1::zeros(layer_sizes[i + 1]);
375 weights.push(w);
376 biases.push(b);
377 }
378
379 Self {
380 weights,
381 biases,
382 hidden_dims: layer_sizes[1..layer_sizes.len() - 1].to_vec(),
383 }
384 }
385
386 fn forward(&self, input: &Array1<f64>) -> Result<Array1<f64>, String> {
388 let mut x = input.clone();
389
390 for (i, (weight, bias)) in self.weights.iter().zip(self.biases.iter()).enumerate() {
391 let mut output = Array1::zeros(weight.nrows());
393 for j in 0..weight.nrows() {
394 let mut sum = bias[j];
395 for k in 0..weight.ncols() {
396 if k < x.len() {
397 sum += weight[[j, k]] * x[k];
398 }
399 }
400 output[j] = sum;
401 }
402
403 if i < self.weights.len() - 1 {
405 for elem in output.iter_mut() {
406 *elem = elem.max(0.0); }
408 }
409
410 x = output;
411 }
412
413 Ok(x)
414 }
415
416 fn update_parameters(&mut self, _learning_rate: f64) -> Result<(), String> {
418 Ok(())
420 }
421}
422
423#[derive(Debug)]
425pub struct ScoreBasedDiffusion {
426 config: DiffusionConfig,
427 score_network: ScoreNetwork,
428 noise_schedule: NoiseSchedule,
429 trained: bool,
430 training_history: Vec<f64>,
433}
434
435#[derive(Debug, Clone)]
436pub struct DiffusionConfig {
437 pub dimension: usize,
438 pub num_timesteps: usize,
439 pub beta_start: f64,
440 pub beta_end: f64,
441 pub hidden_dims: Vec<usize>,
442}
443
444impl Default for DiffusionConfig {
445 fn default() -> Self {
446 Self {
447 dimension: 10,
448 num_timesteps: 1000,
449 beta_start: 1e-4,
450 beta_end: 0.02,
451 hidden_dims: vec![128, 256, 128],
452 }
453 }
454}
455
456#[derive(Debug)]
457struct ScoreNetwork {
458 mlp: MLP,
460 time_embedding_dim: usize,
461}
462
463#[derive(Debug)]
464struct NoiseSchedule {
465 betas: Vec<f64>,
466 alphas: Vec<f64>,
467 alpha_bars: Vec<f64>,
468}
469
470impl ScoreBasedDiffusion {
471 pub fn new(config: DiffusionConfig) -> Self {
473 let time_embedding_dim = 64;
474 let input_dim = config.dimension + time_embedding_dim;
475
476 let mut network_dims = vec![input_dim];
477 network_dims.extend_from_slice(&config.hidden_dims);
478 network_dims.push(config.dimension);
479
480 let score_network = ScoreNetwork {
481 mlp: MLP::new(&network_dims),
482 time_embedding_dim,
483 };
484
485 let noise_schedule =
486 NoiseSchedule::new(config.num_timesteps, config.beta_start, config.beta_end);
487
488 Self {
489 config,
490 score_network,
491 noise_schedule,
492 trained: false,
493 training_history: Vec::new(),
494 }
495 }
496
497 pub fn train(&mut self, training_data: &Array2<f64>) -> Result<(), String> {
499 let num_epochs = 1000;
500 let batch_size = 32;
501
502 for epoch in 0..num_epochs {
503 let mut epoch_loss = 0.0;
504 let mut num_batches_processed = 0usize;
505
506 for _ in 0..training_data.nrows().div_ceil(batch_size) {
508 let mut rng = seeded_rng(42 + epoch as u64);
510 let t = rng
511 .sample(Uniform::new(0, self.config.num_timesteps).expect("Operation failed"));
512
513 let noise = self.sample_noise(training_data.nrows(), &mut rng)?;
515 let noisy_data = self.add_noise(training_data, &noise, t)?;
516
517 epoch_loss += self.score_network.train_step(&noisy_data, &noise, t)?;
519 num_batches_processed += 1;
520 }
521
522 let avg_loss = if num_batches_processed > 0 {
524 epoch_loss / num_batches_processed as f64
525 } else {
526 0.0
527 };
528 self.training_history.push(avg_loss);
529
530 if epoch % 100 == 0 {
531 println!("Epoch {epoch}: loss = {avg_loss:.6}");
532 }
533 }
534
535 self.trained = true;
536 Ok(())
537 }
538
539 pub fn training_history(&self) -> &[f64] {
545 &self.training_history
546 }
547
548 pub fn sample(
550 &self,
551 num_samples: usize,
552 num_timesteps: usize,
553 seed: u64,
554 ) -> Result<Array2<f64>, String> {
555 if !self.trained {
556 return Err("Model must be trained before sampling".to_string());
557 }
558
559 let mut rng = seeded_rng(seed);
560 let mut samples = Array2::zeros((num_samples, self.config.dimension));
561
562 for i in 0..num_samples {
564 for j in 0..self.config.dimension {
565 samples[[i, j]] = rng.sample(Normal::new(0.0, 1.0).expect("Operation failed"));
566 }
567 }
568
569 let timestep_stride = self.config.num_timesteps / num_timesteps;
571
572 for t in (0..num_timesteps).rev() {
573 let actual_t = t * timestep_stride;
574
575 let predicted_noise = self.score_network.predict(&samples, actual_t)?;
577
578 samples = self.ddpm_update(&samples, &predicted_noise, actual_t, &mut rng)?;
580 }
581
582 Ok(samples)
583 }
584
585 fn sample_noise(
587 &self,
588 batch_size: usize,
589 rng: &mut Random<rand::rngs::StdRng>,
590 ) -> Result<Array2<f64>, String> {
591 let mut noise = Array2::zeros((batch_size, self.config.dimension));
592 for i in 0..batch_size {
593 for j in 0..self.config.dimension {
594 noise[[i, j]] = rng.sample(Normal::new(0.0, 1.0).expect("Operation failed"));
595 }
596 }
597 Ok(noise)
598 }
599
600 fn add_noise(
602 &self,
603 data: &Array2<f64>,
604 noise: &Array2<f64>,
605 t: usize,
606 ) -> Result<Array2<f64>, String> {
607 let alpha_bar = self.noise_schedule.alpha_bars[t];
608 let mut noisy_data = Array2::zeros(data.raw_dim());
609
610 for i in 0..data.nrows() {
611 for j in 0..data.ncols() {
612 noisy_data[[i, j]] =
613 alpha_bar.sqrt() * data[[i, j]] + (1.0 - alpha_bar).sqrt() * noise[[i, j]];
614 }
615 }
616
617 Ok(noisy_data)
618 }
619
620 fn ddpm_update(
622 &self,
623 x_t: &Array2<f64>,
624 predicted_noise: &Array2<f64>,
625 t: usize,
626 rng: &mut Random<rand::rngs::StdRng>,
627 ) -> Result<Array2<f64>, String> {
628 let alpha = self.noise_schedule.alphas[t];
629 let alpha_bar = self.noise_schedule.alpha_bars[t];
630 let beta = self.noise_schedule.betas[t];
631
632 let mut x_t_minus_1 = Array2::zeros(x_t.raw_dim());
633
634 for i in 0..x_t.nrows() {
635 for j in 0..x_t.ncols() {
636 let mean_coeff = 1.0 / alpha.sqrt();
638 let noise_coeff = beta / (1.0 - alpha_bar).sqrt();
639 let mean = mean_coeff * (x_t[[i, j]] - noise_coeff * predicted_noise[[i, j]]);
640
641 let noise = if t > 0 {
643 rng.sample(Normal::new(0.0, beta.sqrt()).expect("Operation failed"))
644 } else {
645 0.0
646 };
647
648 x_t_minus_1[[i, j]] = mean + noise;
649 }
650 }
651
652 Ok(x_t_minus_1)
653 }
654}
655
656impl NoiseSchedule {
657 fn new(num_timesteps: usize, beta_start: f64, beta_end: f64) -> Self {
658 let mut betas = Vec::with_capacity(num_timesteps);
659 let mut alphas = Vec::with_capacity(num_timesteps);
660 let mut alpha_bars = Vec::with_capacity(num_timesteps);
661
662 for i in 0..num_timesteps {
664 let beta =
665 beta_start + (beta_end - beta_start) * (i as f64) / (num_timesteps as f64 - 1.0);
666 let alpha = 1.0 - beta;
667
668 betas.push(beta);
669 alphas.push(alpha);
670
671 let alpha_bar = if i == 0 {
673 alpha
674 } else {
675 alpha_bars[i - 1] * alpha
676 };
677 alpha_bars.push(alpha_bar);
678 }
679
680 Self {
681 betas,
682 alphas,
683 alpha_bars,
684 }
685 }
686}
687
688impl ScoreNetwork {
689 fn train_step(
695 &mut self,
696 noisy_data: &Array2<f64>,
697 target_noise: &Array2<f64>,
698 t: usize,
699 ) -> Result<f64, String> {
700 let mut squared_error_sum = 0.0;
702 let mut count = 0usize;
703
704 for i in 0..noisy_data.nrows() {
705 let input = self.prepare_input(&noisy_data.row(i).to_owned(), t)?;
706 let predicted = self.mlp.forward(&input)?;
707 let target = target_noise.row(i);
708
709 for j in 0..predicted.len().min(target.len()) {
711 let diff = predicted[j] - target[j];
712 squared_error_sum += diff * diff;
713 count += 1;
714 }
715 }
716
717 Ok(if count > 0 {
718 squared_error_sum / count as f64
719 } else {
720 0.0
721 })
722 }
723
724 fn predict(&self, x: &Array2<f64>, t: usize) -> Result<Array2<f64>, String> {
726 let mut predictions = Array2::zeros(x.raw_dim());
727
728 for i in 0..x.nrows() {
729 let input = self.prepare_input(&x.row(i).to_owned(), t)?;
730 let pred = self.mlp.forward(&input)?;
731
732 for j in 0..pred.len().min(x.ncols()) {
733 predictions[[i, j]] = pred[j];
734 }
735 }
736
737 Ok(predictions)
738 }
739
740 fn prepare_input(&self, x: &Array1<f64>, t: usize) -> Result<Array1<f64>, String> {
742 let mut time_emb = Array1::zeros(self.time_embedding_dim);
744 for i in 0..self.time_embedding_dim {
745 let freq = 2.0 * std::f64::consts::PI * (t as f64)
746 / (10000.0_f64.powf(2.0 * (i as f64) / (self.time_embedding_dim as f64)));
747 time_emb[i] = if i % 2 == 0 { freq.sin() } else { freq.cos() };
748 }
749
750 let mut input = Array1::zeros(x.len() + time_emb.len());
752 for i in 0..x.len() {
753 input[i] = x[i];
754 }
755 for i in 0..time_emb.len() {
756 input[x.len() + i] = time_emb[i];
757 }
758
759 Ok(input)
760 }
761}
762
763#[derive(Debug)]
765pub struct EnergyBasedModel {
766 energy_network: MLP,
767 dimension: usize,
768 temperature: f64,
769 mcmc_steps: usize,
770 training_history: Vec<f64>,
773}
774
775impl EnergyBasedModel {
776 pub fn new(dimension: usize, hidden_dims: &[usize]) -> Self {
778 let mut network_dims = vec![dimension];
779 network_dims.extend_from_slice(hidden_dims);
780 network_dims.push(1); Self {
783 energy_network: MLP::new(&network_dims),
784 dimension,
785 temperature: 1.0,
786 mcmc_steps: 100,
787 training_history: Vec::new(),
788 }
789 }
790
791 pub fn train(&mut self, training_data: &Array2<f64>, num_epochs: usize) -> Result<(), String> {
793 for epoch in 0..num_epochs {
794 let mut epoch_loss = 0.0;
795
796 for i in 0..training_data.nrows() {
797 let positive_sample = training_data.row(i).to_owned();
798
799 let negative_sample = self.sample_mcmc(&positive_sample, self.mcmc_steps)?;
801
802 epoch_loss +=
804 self.contrastive_divergence_update(&positive_sample, &negative_sample)?;
805 }
806
807 let avg_loss = if training_data.nrows() > 0 {
810 epoch_loss / training_data.nrows() as f64
811 } else {
812 0.0
813 };
814 self.training_history.push(avg_loss);
815
816 if epoch % 100 == 0 {
817 println!("Epoch {epoch}: loss = {avg_loss:.6}");
818 }
819 }
820
821 Ok(())
822 }
823
824 pub fn training_history(&self) -> &[f64] {
831 &self.training_history
832 }
833
834 pub fn sample(
836 &self,
837 num_samples: usize,
838 num_steps: usize,
839 seed: u64,
840 ) -> Result<Array2<f64>, String> {
841 let mut rng = seeded_rng(seed);
842 let mut samples = Array2::zeros((num_samples, self.dimension));
843
844 for i in 0..num_samples {
845 let mut x = Array1::zeros(self.dimension);
847 for j in 0..self.dimension {
848 x[j] = rng.sample(Normal::new(0.0, 1.0).expect("Operation failed"));
849 }
850
851 x = self.sample_mcmc(&x, num_steps)?;
853
854 for j in 0..self.dimension {
855 samples[[i, j]] = x[j];
856 }
857 }
858
859 Ok(samples)
860 }
861
862 fn sample_mcmc(&self, initial: &Array1<f64>, num_steps: usize) -> Result<Array1<f64>, String> {
864 let mut x = initial.clone();
865 let step_size = 0.01;
866 let mut rng = seeded_rng(42);
867
868 for _ in 0..num_steps {
869 let grad = self.energy_gradient(&x)?;
871
872 for i in 0..self.dimension {
874 let noise = rng.sample(
875 Normal::new(0.0, (2.0_f64 * step_size).sqrt()).expect("Operation failed"),
876 );
877 x[i] -= step_size * grad[i] + noise;
878 }
879 }
880
881 Ok(x)
882 }
883
884 fn energy_gradient(&self, x: &Array1<f64>) -> Result<Array1<f64>, String> {
886 let mut gradient = Array1::zeros(self.dimension);
887 let epsilon = 1e-5;
888
889 for i in 0..self.dimension {
890 let mut x_plus = x.clone();
891 let mut x_minus = x.clone();
892 x_plus[i] += epsilon;
893 x_minus[i] -= epsilon;
894
895 let energy_plus = self.energy_network.forward(&x_plus)?[0];
896 let energy_minus = self.energy_network.forward(&x_minus)?[0];
897
898 gradient[i] = (energy_plus - energy_minus) / (2.0 * epsilon);
899 }
900
901 Ok(gradient)
902 }
903
904 fn contrastive_divergence_update(
911 &mut self,
912 positive: &Array1<f64>,
913 negative: &Array1<f64>,
914 ) -> Result<f64, String> {
915 let pos_energy = self.energy_network.forward(positive)?;
917 let neg_energy = self.energy_network.forward(negative)?;
918
919 let cd_loss = pos_energy[0] - neg_energy[0];
922
923 Ok(cd_loss)
924 }
925}
926
927#[derive(Debug)]
929pub struct NeuralPosteriorEstimation {
930 posterior_network: MLP,
931 prior_network: MLP,
932 observation_dim: usize,
933 parameter_dim: usize,
934 trained: bool,
935 training_history: Vec<f64>,
938}
939
940impl NeuralPosteriorEstimation {
941 pub fn new(observation_dim: usize, parameter_dim: usize, hidden_dims: &[usize]) -> Self {
943 let mut posterior_dims = vec![observation_dim];
945 posterior_dims.extend_from_slice(hidden_dims);
946 posterior_dims.push(parameter_dim * 2); let mut prior_dims = vec![parameter_dim];
950 prior_dims.extend_from_slice(hidden_dims);
951 prior_dims.push(parameter_dim);
952
953 Self {
954 posterior_network: MLP::new(&posterior_dims),
955 prior_network: MLP::new(&prior_dims),
956 observation_dim,
957 parameter_dim,
958 trained: false,
959 training_history: Vec::new(),
960 }
961 }
962
963 pub fn train(
965 &mut self,
966 simulator: impl Fn(&Array1<f64>) -> Array1<f64>,
967 num_simulations: usize,
968 ) -> Result<(), String> {
969 let mut rng = seeded_rng(42);
970
971 for epoch in 0..1000 {
972 let mut epoch_loss = 0.0;
973 let mut num_steps = 0usize;
974
975 for _ in 0..num_simulations / 1000 {
976 let mut theta = Array1::zeros(self.parameter_dim);
978 for i in 0..self.parameter_dim {
979 theta[i] = rng.sample(Normal::new(0.0, 1.0).expect("Operation failed"));
980 }
981
982 let x = simulator(&theta);
984
985 epoch_loss += self.train_posterior_step(&x, &theta)?;
987 num_steps += 1;
988 }
989
990 let avg_loss = if num_steps > 0 {
992 epoch_loss / num_steps as f64
993 } else {
994 0.0
995 };
996 self.training_history.push(avg_loss);
997
998 if epoch % 100 == 0 {
999 println!("Epoch {epoch}: loss = {avg_loss:.6}");
1000 }
1001 }
1002
1003 self.trained = true;
1004 Ok(())
1005 }
1006
1007 pub fn training_history(&self) -> &[f64] {
1014 &self.training_history
1015 }
1016
1017 pub fn posterior(
1019 &self,
1020 observation: &Array1<f64>,
1021 num_samples: usize,
1022 seed: u64,
1023 ) -> Result<Array2<f64>, String> {
1024 if !self.trained {
1025 return Err("Model must be trained before inference".to_string());
1026 }
1027
1028 let posterior_params = self.posterior_network.forward(observation)?;
1030
1031 let mean_start = 0;
1032 let var_start = self.parameter_dim;
1033
1034 let mut rng = seeded_rng(seed);
1035 let mut samples = Array2::zeros((num_samples, self.parameter_dim));
1036
1037 for i in 0..num_samples {
1038 for j in 0..self.parameter_dim {
1039 let mean = posterior_params[mean_start + j];
1040 let var = posterior_params[var_start + j].exp(); samples[[i, j]] =
1043 rng.sample(Normal::new(mean, var.sqrt()).expect("Operation failed"));
1044 }
1045 }
1046
1047 Ok(samples)
1048 }
1049
1050 fn train_posterior_step(
1056 &mut self,
1057 observation: &Array1<f64>,
1058 true_parameter: &Array1<f64>,
1059 ) -> Result<f64, String> {
1060 let predicted_params = self.posterior_network.forward(observation)?;
1062
1063 let mean_start = 0;
1064 let var_start = self.parameter_dim;
1065
1066 let mut nll = 0.0;
1069 for j in 0..self.parameter_dim {
1070 let mean = predicted_params[mean_start + j];
1071 let var = predicted_params[var_start + j].exp().max(1e-12); let diff = true_parameter[j] - mean;
1073 nll += 0.5 * diff * diff / var + 0.5 * var.ln();
1074 }
1075 nll += 0.5 * (self.parameter_dim as f64) * (2.0 * std::f64::consts::PI).ln();
1076
1077 Ok(nll)
1078 }
1079}
1080
1081trait LogProbability {
1083 fn log_probability(&self, x: &[f64]) -> Result<f64, String>;
1084}
1085
1086impl LogProbability for MultivariateNormal {
1087 fn log_probability(&self, x: &[f64]) -> Result<f64, String> {
1088 if x.len() != self.dimension() {
1089 return Err("Dimension mismatch".to_string());
1090 }
1091
1092 let mut log_prob = 0.0;
1094 for &xi in x {
1095 log_prob += -0.5 * xi * xi; }
1097 log_prob += -0.5 * (x.len() as f64) * (2.0 * std::f64::consts::PI).ln();
1098
1099 Ok(log_prob)
1100 }
1101}
1102
1103#[cfg(test)]
1104mod tests {
1105 use super::*;
1106 use approx::assert_relative_eq;
1107
1108 #[test]
1109 fn test_normalizing_flow_creation() {
1110 let flow = NormalizingFlow::new(5, 3);
1111 assert_eq!(flow.dimension, 5);
1112 assert_eq!(flow.num_layers, 3);
1113 assert!(!flow.trained);
1114 }
1115
1116 #[test]
1117 fn test_diffusion_model_creation() {
1118 let config = DiffusionConfig {
1119 dimension: 10,
1120 num_timesteps: 100,
1121 beta_start: 1e-4,
1122 beta_end: 0.02,
1123 hidden_dims: vec![32, 64, 32],
1124 };
1125
1126 let diffusion = ScoreBasedDiffusion::new(config);
1127 assert_eq!(diffusion.config.dimension, 10);
1128 assert_eq!(diffusion.config.num_timesteps, 100);
1129 }
1130
1131 #[test]
1132 fn test_energy_based_model() {
1133 let ebm = EnergyBasedModel::new(5, &[32, 32]);
1134 assert_eq!(ebm.dimension, 5);
1135 assert_eq!(ebm.mcmc_steps, 100);
1136 }
1137
1138 #[test]
1139 fn test_neural_posterior_estimation() {
1140 let npe = NeuralPosteriorEstimation::new(10, 5, &[32, 32]);
1141 assert_eq!(npe.observation_dim, 10);
1142 assert_eq!(npe.parameter_dim, 5);
1143 assert!(!npe.trained);
1144 }
1145
1146 #[test]
1147 fn test_mlp_forward() {
1148 let mlp = MLP::new(&[3, 5, 2]);
1149 let input = Array1::from_vec(vec![1.0, 2.0, 3.0]);
1150 let output = mlp.forward(&input).expect("Operation failed");
1151 assert_eq!(output.len(), 2);
1152 }
1153
1154 #[test]
1155 fn test_noise_schedule() {
1156 let schedule = NoiseSchedule::new(100, 1e-4, 0.02);
1157 assert_eq!(schedule.betas.len(), 100);
1158 assert_eq!(schedule.alphas.len(), 100);
1159 assert_eq!(schedule.alpha_bars.len(), 100);
1160
1161 for i in 1..schedule.alpha_bars.len() {
1163 assert!(schedule.alpha_bars[i] <= schedule.alpha_bars[i - 1]);
1164 }
1165 }
1166
1167 #[test]
1168 fn test_normalizing_flow_training_history() {
1169 let mut flow = NormalizingFlow::new(4, 2);
1170 let training_data =
1171 Array2::from_shape_fn((8, 4), |(i, j)| (i as f64 * 0.3 + j as f64 * 0.1) - 1.0);
1172 let num_epochs = 5;
1173
1174 flow.train(&training_data, num_epochs)
1175 .expect("training should succeed on well-formed synthetic data");
1176
1177 let history = flow.training_history();
1178 assert_eq!(
1179 history.len(),
1180 num_epochs,
1181 "training_history should have exactly one entry per epoch"
1182 );
1183 for (i, &loss) in history.iter().enumerate() {
1184 assert!(
1185 loss.is_finite(),
1186 "epoch {i} loss should be finite, got {loss}"
1187 );
1188 }
1189 }
1190
1191 #[test]
1192 fn test_score_based_diffusion_training_history() {
1193 let config = DiffusionConfig {
1194 dimension: 3,
1195 num_timesteps: 20,
1196 beta_start: 1e-4,
1197 beta_end: 0.02,
1198 hidden_dims: vec![8, 8],
1199 };
1200 let mut diffusion = ScoreBasedDiffusion::new(config);
1201 let training_data = Array2::from_shape_fn((6, 3), |(i, j)| (i as f64 - j as f64) * 0.2);
1202
1203 diffusion
1204 .train(&training_data)
1205 .expect("training should succeed on well-formed synthetic data");
1206
1207 let history = diffusion.training_history();
1208 assert_eq!(
1211 history.len(),
1212 1000,
1213 "training_history should have exactly one entry per (fixed) epoch"
1214 );
1215 for (i, &loss) in history.iter().enumerate() {
1216 assert!(
1217 loss.is_finite(),
1218 "epoch {i} loss should be finite, got {loss}"
1219 );
1220 }
1221 }
1222
1223 #[test]
1224 fn test_energy_based_model_training_history() {
1225 let mut ebm = EnergyBasedModel::new(3, &[8, 8]);
1226 let training_data = Array2::from_shape_fn((4, 3), |(i, j)| (i as f64 - j as f64) * 0.15);
1227 let num_epochs = 4;
1228
1229 ebm.train(&training_data, num_epochs)
1230 .expect("training should succeed on well-formed synthetic data");
1231
1232 let history = ebm.training_history();
1233 assert_eq!(
1234 history.len(),
1235 num_epochs,
1236 "training_history should have exactly one entry per epoch"
1237 );
1238 for (i, &loss) in history.iter().enumerate() {
1239 assert!(
1240 loss.is_finite(),
1241 "epoch {i} loss should be finite, got {loss}"
1242 );
1243 }
1244 }
1245
1246 #[test]
1247 fn test_neural_posterior_estimation_training_history() {
1248 let mut npe = NeuralPosteriorEstimation::new(4, 2, &[8, 8]);
1249 let simulator = |theta: &Array1<f64>| -> Array1<f64> {
1250 Array1::from_vec(vec![
1251 theta[0],
1252 theta[1],
1253 theta[0] + theta[1],
1254 theta[0] - theta[1],
1255 ])
1256 };
1257
1258 npe.train(simulator, 2000)
1259 .expect("training should succeed on well-formed synthetic simulator");
1260
1261 let history = npe.training_history();
1262 assert_eq!(
1265 history.len(),
1266 1000,
1267 "training_history should have exactly one entry per (fixed) epoch"
1268 );
1269 for (i, &loss) in history.iter().enumerate() {
1270 assert!(
1271 loss.is_finite(),
1272 "epoch {i} loss should be finite, got {loss}"
1273 );
1274 }
1275 }
1276}