use crate::random::{
core::{seeded_rng, Random},
distributions::MultivariateNormal,
parallel::{ParallelRng, ThreadLocalRngPool},
};
use ::ndarray::{s, Array1, Array2, Array3, Axis};
use rand::{Rng, RngExt};
use rand_distr::{Distribution, Normal, Uniform};
use std::collections::VecDeque;
#[derive(Debug, Clone)]
pub struct NormalizingFlow {
dimension: usize,
num_layers: usize,
flow_layers: Vec<FlowLayer>,
base_distribution: MultivariateNormal,
trained: bool,
}
#[derive(Debug, Clone)]
struct FlowLayer {
mask: Array1<bool>,
scale_network: MLP,
translation_network: MLP,
}
#[derive(Debug, Clone)]
struct MLP {
weights: Vec<Array2<f64>>,
biases: Vec<Array1<f64>>,
hidden_dims: Vec<usize>,
}
impl NormalizingFlow {
pub fn new(dimension: usize, num_layers: usize) -> Self {
let mut flow_layers = Vec::new();
for i in 0..num_layers {
let mut mask = Array1::from_elem(dimension, false);
for j in 0..dimension {
mask[j] = (j + i) % 2 == 0;
}
let hidden_dim = dimension.max(32);
let scale_net = MLP::new(&[dimension / 2, hidden_dim, hidden_dim, dimension / 2]);
let trans_net = MLP::new(&[dimension / 2, hidden_dim, hidden_dim, dimension / 2]);
flow_layers.push(FlowLayer {
mask,
scale_network: scale_net,
translation_network: trans_net,
});
}
let mut cov_matrix = vec![vec![0.0; dimension]; dimension];
for i in 0..dimension {
cov_matrix[i][i] = 1.0;
}
let base_distribution =
MultivariateNormal::new(vec![0.0; dimension], cov_matrix).expect("Operation failed");
Self {
dimension,
num_layers,
flow_layers,
base_distribution,
trained: false,
}
}
pub fn train(&mut self, training_data: &Array2<f64>, num_epochs: usize) -> Result<(), String> {
let learning_rate = 0.001;
let batch_size = 32;
for epoch in 0..num_epochs {
let num_batches = training_data.nrows().div_ceil(batch_size);
for batch_idx in 0..num_batches {
let start_idx = batch_idx * batch_size;
let end_idx = (start_idx + batch_size).min(training_data.nrows());
let batch = training_data.slice(s![start_idx..end_idx, ..]);
let mut _total_loss = 0.0;
for i in 0..batch.nrows() {
let x = batch.row(i).to_owned();
let (z, log_det_jacobian) = self.forward(&x)?;
let log_prob_z = self.base_distribution.log_probability(&z.to_vec())?;
let log_prob_x = log_prob_z + log_det_jacobian;
_total_loss -= log_prob_x; }
self.update_parameters(learning_rate, &batch)?;
}
if epoch % 100 == 0 {
println!("Epoch {}: Training flow...", epoch);
}
}
self.trained = true;
Ok(())
}
fn forward(&self, x: &Array1<f64>) -> Result<(Array1<f64>, f64), String> {
let mut z = x.clone();
let mut log_det_jacobian = 0.0;
for layer in &self.flow_layers {
let (new_z, log_det) = layer.forward(&z)?;
z = new_z;
log_det_jacobian += log_det;
}
Ok((z, log_det_jacobian))
}
fn inverse(&self, z: &Array1<f64>) -> Result<Array1<f64>, String> {
let mut x = z.clone();
for layer in self.flow_layers.iter().rev() {
x = layer.inverse(&x)?;
}
Ok(x)
}
pub fn sample(&self, num_samples: usize, seed: u64) -> Result<Array2<f64>, String> {
if !self.trained {
return Err("Flow must be trained before sampling".to_string());
}
let mut rng = seeded_rng(seed);
let mut samples = Array2::zeros((num_samples, self.dimension));
for i in 0..num_samples {
let z = Array1::from_vec(self.base_distribution.sample(&mut rng));
let x = self.inverse(&z)?;
for j in 0..self.dimension {
samples[[i, j]] = x[j];
}
}
Ok(samples)
}
pub fn log_probability(&self, x: &Array1<f64>) -> Result<f64, String> {
if !self.trained {
return Err("Flow must be trained before computing probabilities".to_string());
}
let (z, log_det_jacobian) = self.forward(x)?;
let log_prob_z = self.base_distribution.log_probability(&z.to_vec())?;
Ok(log_prob_z + log_det_jacobian)
}
fn update_parameters(
&mut self,
learning_rate: f64,
batch: &crate::ndarray::ArrayView2<f64>,
) -> Result<(), String> {
for layer in &mut self.flow_layers {
layer.update_parameters(learning_rate, batch)?;
}
Ok(())
}
}
impl FlowLayer {
fn forward(&self, x: &Array1<f64>) -> Result<(Array1<f64>, f64), String> {
let mut y = x.clone();
let mut log_det_jacobian = 0.0;
let x_unchanged: Vec<f64> = x
.iter()
.enumerate()
.filter(|(i, _)| self.mask[*i])
.map(|(_, &val)| val)
.collect();
let x_to_transform: Vec<f64> = x
.iter()
.enumerate()
.filter(|(i, _)| !self.mask[*i])
.map(|(_, &val)| val)
.collect();
if !x_unchanged.is_empty() && !x_to_transform.is_empty() {
let scale = self
.scale_network
.forward(&Array1::from_vec(x_unchanged.clone()))?;
let translation = self
.translation_network
.forward(&Array1::from_vec(x_unchanged))?;
let mut transform_idx = 0;
for (i, &masked) in self.mask.iter().enumerate() {
if !masked && transform_idx < scale.len() && transform_idx < translation.len() {
let s = scale[transform_idx];
let t = translation[transform_idx];
y[i] = x_to_transform[transform_idx] * s.exp() + t;
log_det_jacobian += s;
transform_idx += 1;
}
}
}
Ok((y, log_det_jacobian))
}
fn inverse(&self, y: &Array1<f64>) -> Result<Array1<f64>, String> {
let mut x = y.clone();
let y_unchanged: Vec<f64> = y
.iter()
.enumerate()
.filter(|(i, _)| self.mask[*i])
.map(|(_, &val)| val)
.collect();
if !y_unchanged.is_empty() {
let scale = self
.scale_network
.forward(&Array1::from_vec(y_unchanged.clone()))?;
let translation = self
.translation_network
.forward(&Array1::from_vec(y_unchanged))?;
let mut transform_idx = 0;
for (i, &masked) in self.mask.iter().enumerate() {
if !masked && transform_idx < scale.len() && transform_idx < translation.len() {
let s = scale[transform_idx];
let t = translation[transform_idx];
x[i] = (y[i] - t) * (-s).exp();
transform_idx += 1;
}
}
}
Ok(x)
}
fn update_parameters(
&mut self,
learning_rate: f64,
_batch: &crate::ndarray::ArrayView2<f64>,
) -> Result<(), String> {
self.scale_network.update_parameters(learning_rate)?;
self.translation_network.update_parameters(learning_rate)?;
Ok(())
}
}
impl MLP {
fn new(layer_sizes: &[usize]) -> Self {
let mut weights = Vec::new();
let mut biases = Vec::new();
for i in 0..layer_sizes.len() - 1 {
let w = Array2::zeros((layer_sizes[i + 1], layer_sizes[i]));
let b = Array1::zeros(layer_sizes[i + 1]);
weights.push(w);
biases.push(b);
}
Self {
weights,
biases,
hidden_dims: layer_sizes[1..layer_sizes.len() - 1].to_vec(),
}
}
fn forward(&self, input: &Array1<f64>) -> Result<Array1<f64>, String> {
let mut x = input.clone();
for (i, (weight, bias)) in self.weights.iter().zip(self.biases.iter()).enumerate() {
let mut output = Array1::zeros(weight.nrows());
for j in 0..weight.nrows() {
let mut sum = bias[j];
for k in 0..weight.ncols() {
if k < x.len() {
sum += weight[[j, k]] * x[k];
}
}
output[j] = sum;
}
if i < self.weights.len() - 1 {
for elem in output.iter_mut() {
*elem = elem.max(0.0); }
}
x = output;
}
Ok(x)
}
fn update_parameters(&mut self, _learning_rate: f64) -> Result<(), String> {
Ok(())
}
}
#[derive(Debug)]
pub struct ScoreBasedDiffusion {
config: DiffusionConfig,
score_network: ScoreNetwork,
noise_schedule: NoiseSchedule,
trained: bool,
}
#[derive(Debug, Clone)]
pub struct DiffusionConfig {
pub dimension: usize,
pub num_timesteps: usize,
pub beta_start: f64,
pub beta_end: f64,
pub hidden_dims: Vec<usize>,
}
impl Default for DiffusionConfig {
fn default() -> Self {
Self {
dimension: 10,
num_timesteps: 1000,
beta_start: 1e-4,
beta_end: 0.02,
hidden_dims: vec![128, 256, 128],
}
}
}
#[derive(Debug)]
struct ScoreNetwork {
mlp: MLP,
time_embedding_dim: usize,
}
#[derive(Debug)]
struct NoiseSchedule {
betas: Vec<f64>,
alphas: Vec<f64>,
alpha_bars: Vec<f64>,
}
impl ScoreBasedDiffusion {
pub fn new(config: DiffusionConfig) -> Self {
let time_embedding_dim = 64;
let input_dim = config.dimension + time_embedding_dim;
let mut network_dims = vec![input_dim];
network_dims.extend_from_slice(&config.hidden_dims);
network_dims.push(config.dimension);
let score_network = ScoreNetwork {
mlp: MLP::new(&network_dims),
time_embedding_dim,
};
let noise_schedule =
NoiseSchedule::new(config.num_timesteps, config.beta_start, config.beta_end);
Self {
config,
score_network,
noise_schedule,
trained: false,
}
}
pub fn train(&mut self, training_data: &Array2<f64>) -> Result<(), String> {
let num_epochs = 1000;
let batch_size = 32;
for epoch in 0..num_epochs {
for _ in 0..training_data.nrows().div_ceil(batch_size) {
let mut rng = seeded_rng(42 + epoch as u64);
let t = rng
.sample(Uniform::new(0, self.config.num_timesteps).expect("Operation failed"));
let noise = self.sample_noise(training_data.nrows(), &mut rng)?;
let noisy_data = self.add_noise(training_data, &noise, t)?;
self.score_network.train_step(&noisy_data, &noise, t)?;
}
if epoch % 100 == 0 {
println!("Epoch {}: Training diffusion model...", epoch);
}
}
self.trained = true;
Ok(())
}
pub fn sample(
&self,
num_samples: usize,
num_timesteps: usize,
seed: u64,
) -> Result<Array2<f64>, String> {
if !self.trained {
return Err("Model must be trained before sampling".to_string());
}
let mut rng = seeded_rng(seed);
let mut samples = Array2::zeros((num_samples, self.config.dimension));
for i in 0..num_samples {
for j in 0..self.config.dimension {
samples[[i, j]] = rng.sample(Normal::new(0.0, 1.0).expect("Operation failed"));
}
}
let timestep_stride = self.config.num_timesteps / num_timesteps;
for t in (0..num_timesteps).rev() {
let actual_t = t * timestep_stride;
let predicted_noise = self.score_network.predict(&samples, actual_t)?;
samples = self.ddpm_update(&samples, &predicted_noise, actual_t, &mut rng)?;
}
Ok(samples)
}
fn sample_noise(
&self,
batch_size: usize,
rng: &mut Random<rand::rngs::StdRng>,
) -> Result<Array2<f64>, String> {
let mut noise = Array2::zeros((batch_size, self.config.dimension));
for i in 0..batch_size {
for j in 0..self.config.dimension {
noise[[i, j]] = rng.sample(Normal::new(0.0, 1.0).expect("Operation failed"));
}
}
Ok(noise)
}
fn add_noise(
&self,
data: &Array2<f64>,
noise: &Array2<f64>,
t: usize,
) -> Result<Array2<f64>, String> {
let alpha_bar = self.noise_schedule.alpha_bars[t];
let mut noisy_data = Array2::zeros(data.raw_dim());
for i in 0..data.nrows() {
for j in 0..data.ncols() {
noisy_data[[i, j]] =
alpha_bar.sqrt() * data[[i, j]] + (1.0 - alpha_bar).sqrt() * noise[[i, j]];
}
}
Ok(noisy_data)
}
fn ddpm_update(
&self,
x_t: &Array2<f64>,
predicted_noise: &Array2<f64>,
t: usize,
rng: &mut Random<rand::rngs::StdRng>,
) -> Result<Array2<f64>, String> {
let alpha = self.noise_schedule.alphas[t];
let alpha_bar = self.noise_schedule.alpha_bars[t];
let beta = self.noise_schedule.betas[t];
let mut x_t_minus_1 = Array2::zeros(x_t.raw_dim());
for i in 0..x_t.nrows() {
for j in 0..x_t.ncols() {
let mean_coeff = 1.0 / alpha.sqrt();
let noise_coeff = beta / (1.0 - alpha_bar).sqrt();
let mean = mean_coeff * (x_t[[i, j]] - noise_coeff * predicted_noise[[i, j]]);
let noise = if t > 0 {
rng.sample(Normal::new(0.0, beta.sqrt()).expect("Operation failed"))
} else {
0.0
};
x_t_minus_1[[i, j]] = mean + noise;
}
}
Ok(x_t_minus_1)
}
}
impl NoiseSchedule {
fn new(num_timesteps: usize, beta_start: f64, beta_end: f64) -> Self {
let mut betas = Vec::with_capacity(num_timesteps);
let mut alphas = Vec::with_capacity(num_timesteps);
let mut alpha_bars = Vec::with_capacity(num_timesteps);
for i in 0..num_timesteps {
let beta =
beta_start + (beta_end - beta_start) * (i as f64) / (num_timesteps as f64 - 1.0);
let alpha = 1.0 - beta;
betas.push(beta);
alphas.push(alpha);
let alpha_bar = if i == 0 {
alpha
} else {
alpha_bars[i - 1] * alpha
};
alpha_bars.push(alpha_bar);
}
Self {
betas,
alphas,
alpha_bars,
}
}
}
impl ScoreNetwork {
fn train_step(
&mut self,
noisy_data: &Array2<f64>,
target_noise: &Array2<f64>,
t: usize,
) -> Result<(), String> {
for i in 0..noisy_data.nrows() {
let input = self.prepare_input(&noisy_data.row(i).to_owned(), t)?;
let _predicted = self.mlp.forward(&input)?;
}
Ok(())
}
fn predict(&self, x: &Array2<f64>, t: usize) -> Result<Array2<f64>, String> {
let mut predictions = Array2::zeros(x.raw_dim());
for i in 0..x.nrows() {
let input = self.prepare_input(&x.row(i).to_owned(), t)?;
let pred = self.mlp.forward(&input)?;
for j in 0..pred.len().min(x.ncols()) {
predictions[[i, j]] = pred[j];
}
}
Ok(predictions)
}
fn prepare_input(&self, x: &Array1<f64>, t: usize) -> Result<Array1<f64>, String> {
let mut time_emb = Array1::zeros(self.time_embedding_dim);
for i in 0..self.time_embedding_dim {
let freq = 2.0 * std::f64::consts::PI * (t as f64)
/ (10000.0_f64.powf(2.0 * (i as f64) / (self.time_embedding_dim as f64)));
time_emb[i] = if i % 2 == 0 { freq.sin() } else { freq.cos() };
}
let mut input = Array1::zeros(x.len() + time_emb.len());
for i in 0..x.len() {
input[i] = x[i];
}
for i in 0..time_emb.len() {
input[x.len() + i] = time_emb[i];
}
Ok(input)
}
}
#[derive(Debug)]
pub struct EnergyBasedModel {
energy_network: MLP,
dimension: usize,
temperature: f64,
mcmc_steps: usize,
}
impl EnergyBasedModel {
pub fn new(dimension: usize, hidden_dims: &[usize]) -> Self {
let mut network_dims = vec![dimension];
network_dims.extend_from_slice(hidden_dims);
network_dims.push(1);
Self {
energy_network: MLP::new(&network_dims),
dimension,
temperature: 1.0,
mcmc_steps: 100,
}
}
pub fn train(&mut self, training_data: &Array2<f64>, num_epochs: usize) -> Result<(), String> {
for epoch in 0..num_epochs {
for i in 0..training_data.nrows() {
let positive_sample = training_data.row(i).to_owned();
let negative_sample = self.sample_mcmc(&positive_sample, self.mcmc_steps)?;
self.contrastive_divergence_update(&positive_sample, &negative_sample)?;
}
if epoch % 100 == 0 {
println!("Epoch {}: Training EBM...", epoch);
}
}
Ok(())
}
pub fn sample(
&self,
num_samples: usize,
num_steps: usize,
seed: u64,
) -> Result<Array2<f64>, String> {
let mut rng = seeded_rng(seed);
let mut samples = Array2::zeros((num_samples, self.dimension));
for i in 0..num_samples {
let mut x = Array1::zeros(self.dimension);
for j in 0..self.dimension {
x[j] = rng.sample(Normal::new(0.0, 1.0).expect("Operation failed"));
}
x = self.sample_mcmc(&x, num_steps)?;
for j in 0..self.dimension {
samples[[i, j]] = x[j];
}
}
Ok(samples)
}
fn sample_mcmc(&self, initial: &Array1<f64>, num_steps: usize) -> Result<Array1<f64>, String> {
let mut x = initial.clone();
let step_size = 0.01;
let mut rng = seeded_rng(42);
for _ in 0..num_steps {
let grad = self.energy_gradient(&x)?;
for i in 0..self.dimension {
let noise = rng.sample(
Normal::new(0.0, (2.0_f64 * step_size).sqrt()).expect("Operation failed"),
);
x[i] -= step_size * grad[i] + noise;
}
}
Ok(x)
}
fn energy_gradient(&self, x: &Array1<f64>) -> Result<Array1<f64>, String> {
let mut gradient = Array1::zeros(self.dimension);
let epsilon = 1e-5;
for i in 0..self.dimension {
let mut x_plus = x.clone();
let mut x_minus = x.clone();
x_plus[i] += epsilon;
x_minus[i] -= epsilon;
let energy_plus = self.energy_network.forward(&x_plus)?[0];
let energy_minus = self.energy_network.forward(&x_minus)?[0];
gradient[i] = (energy_plus - energy_minus) / (2.0 * epsilon);
}
Ok(gradient)
}
fn contrastive_divergence_update(
&mut self,
positive: &Array1<f64>,
negative: &Array1<f64>,
) -> Result<(), String> {
let _pos_energy = self.energy_network.forward(positive)?;
let _neg_energy = self.energy_network.forward(negative)?;
Ok(())
}
}
#[derive(Debug)]
pub struct NeuralPosteriorEstimation {
posterior_network: MLP,
prior_network: MLP,
observation_dim: usize,
parameter_dim: usize,
trained: bool,
}
impl NeuralPosteriorEstimation {
pub fn new(observation_dim: usize, parameter_dim: usize, hidden_dims: &[usize]) -> Self {
let mut posterior_dims = vec![observation_dim];
posterior_dims.extend_from_slice(hidden_dims);
posterior_dims.push(parameter_dim * 2);
let mut prior_dims = vec![parameter_dim];
prior_dims.extend_from_slice(hidden_dims);
prior_dims.push(parameter_dim);
Self {
posterior_network: MLP::new(&posterior_dims),
prior_network: MLP::new(&prior_dims),
observation_dim,
parameter_dim,
trained: false,
}
}
pub fn train(
&mut self,
simulator: impl Fn(&Array1<f64>) -> Array1<f64>,
num_simulations: usize,
) -> Result<(), String> {
let mut rng = seeded_rng(42);
for epoch in 0..1000 {
for _ in 0..num_simulations / 1000 {
let mut theta = Array1::zeros(self.parameter_dim);
for i in 0..self.parameter_dim {
theta[i] = rng.sample(Normal::new(0.0, 1.0).expect("Operation failed"));
}
let x = simulator(&theta);
self.train_posterior_step(&x, &theta)?;
}
if epoch % 100 == 0 {
println!("Epoch {}: Training NPE...", epoch);
}
}
self.trained = true;
Ok(())
}
pub fn posterior(
&self,
observation: &Array1<f64>,
num_samples: usize,
seed: u64,
) -> Result<Array2<f64>, String> {
if !self.trained {
return Err("Model must be trained before inference".to_string());
}
let posterior_params = self.posterior_network.forward(observation)?;
let mean_start = 0;
let var_start = self.parameter_dim;
let mut rng = seeded_rng(seed);
let mut samples = Array2::zeros((num_samples, self.parameter_dim));
for i in 0..num_samples {
for j in 0..self.parameter_dim {
let mean = posterior_params[mean_start + j];
let var = posterior_params[var_start + j].exp();
samples[[i, j]] =
rng.sample(Normal::new(mean, var.sqrt()).expect("Operation failed"));
}
}
Ok(samples)
}
fn train_posterior_step(
&mut self,
observation: &Array1<f64>,
true_parameter: &Array1<f64>,
) -> Result<(), String> {
let _predicted_params = self.posterior_network.forward(observation)?;
Ok(())
}
}
trait LogProbability {
fn log_probability(&self, x: &[f64]) -> Result<f64, String>;
}
impl LogProbability for MultivariateNormal {
fn log_probability(&self, x: &[f64]) -> Result<f64, String> {
if x.len() != self.dimension() {
return Err("Dimension mismatch".to_string());
}
let mut log_prob = 0.0;
for &xi in x {
log_prob += -0.5 * xi * xi; }
log_prob += -0.5 * (x.len() as f64) * (2.0 * std::f64::consts::PI).ln();
Ok(log_prob)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_normalizing_flow_creation() {
let flow = NormalizingFlow::new(5, 3);
assert_eq!(flow.dimension, 5);
assert_eq!(flow.num_layers, 3);
assert!(!flow.trained);
}
#[test]
fn test_diffusion_model_creation() {
let config = DiffusionConfig {
dimension: 10,
num_timesteps: 100,
beta_start: 1e-4,
beta_end: 0.02,
hidden_dims: vec![32, 64, 32],
};
let diffusion = ScoreBasedDiffusion::new(config);
assert_eq!(diffusion.config.dimension, 10);
assert_eq!(diffusion.config.num_timesteps, 100);
}
#[test]
fn test_energy_based_model() {
let ebm = EnergyBasedModel::new(5, &[32, 32]);
assert_eq!(ebm.dimension, 5);
assert_eq!(ebm.mcmc_steps, 100);
}
#[test]
fn test_neural_posterior_estimation() {
let npe = NeuralPosteriorEstimation::new(10, 5, &[32, 32]);
assert_eq!(npe.observation_dim, 10);
assert_eq!(npe.parameter_dim, 5);
assert!(!npe.trained);
}
#[test]
fn test_mlp_forward() {
let mlp = MLP::new(&[3, 5, 2]);
let input = Array1::from_vec(vec![1.0, 2.0, 3.0]);
let output = mlp.forward(&input).expect("Operation failed");
assert_eq!(output.len(), 2);
}
#[test]
fn test_noise_schedule() {
let schedule = NoiseSchedule::new(100, 1e-4, 0.02);
assert_eq!(schedule.betas.len(), 100);
assert_eq!(schedule.alphas.len(), 100);
assert_eq!(schedule.alpha_bars.len(), 100);
for i in 1..schedule.alpha_bars.len() {
assert!(schedule.alpha_bars[i] <= schedule.alpha_bars[i - 1]);
}
}
}