use crate::error::{NeuralError, Result};
pub trait FlowLayer: Send + Sync + std::fmt::Debug {
fn forward_flow(&self, z: &[f64]) -> Result<(Vec<f64>, f64)>;
fn inverse_flow(&self, z_prime: &[f64]) -> Result<Vec<f64>>;
fn dim(&self) -> usize;
}
fn tanh(x: f64) -> f64 {
x.tanh()
}
fn sigmoid(x: f64) -> f64 {
if x >= 0.0 {
1.0 / (1.0 + (-x).exp())
} else {
let ex = x.exp();
ex / (1.0 + ex)
}
}
fn relu(x: f64) -> f64 {
x.max(0.0)
}
fn mlp_forward(x: &[f64], layers: &[(Vec<f64>, Vec<f64>)]) -> Vec<f64> {
let mut h = x.to_vec();
for (layer_idx, (w, b)) in layers.iter().enumerate() {
let out_dim = b.len();
let in_dim = h.len();
let mut next = vec![0.0f64; out_dim];
for j in 0..out_dim {
let mut s = b[j];
for i in 0..in_dim {
s += w[j * in_dim + i] * h[i];
}
next[j] = s;
}
if layer_idx < layers.len() - 1 {
for v in &mut next {
*v = relu(*v);
}
}
h = next;
}
h
}
fn make_weight_matrix(in_dim: usize, out_dim: usize, seed_offset: usize) -> Vec<f64> {
let std = (2.0 / in_dim as f64).sqrt();
(0..in_dim * out_dim)
.map(|k| std * (((k + seed_offset) as f64) * 0.6180339887).sin())
.collect()
}
fn make_bias_vector(out_dim: usize) -> Vec<f64> {
vec![0.0; out_dim]
}
#[derive(Debug, Clone)]
pub struct PlanarFlow {
dim: usize,
w: Vec<f64>,
u: Vec<f64>,
b: f64,
}
impl PlanarFlow {
pub fn new(dim: usize) -> Result<Self> {
if dim == 0 {
return Err(NeuralError::InvalidArgument(
"PlanarFlow: dim must be > 0".to_string(),
));
}
let std = 0.01f64;
let w: Vec<f64> = (0..dim)
.map(|k| std * ((k as f64) * 0.6180339887).sin())
.collect();
let u: Vec<f64> = (0..dim)
.map(|k| std * (((k + dim) as f64) * 0.6180339887).sin())
.collect();
Ok(Self { dim, w, u, b: 0.0 })
}
fn u_hat(&self) -> Vec<f64> {
let w_dot_u: f64 = self.w.iter().zip(&self.u).map(|(&wi, &ui)| wi * ui).sum();
let w_sq: f64 = self.w.iter().map(|&wi| wi * wi).sum();
let sp = if w_dot_u > 0.0 {
w_dot_u + (1.0 + (-w_dot_u).exp()).ln()
} else {
(1.0 + w_dot_u.exp()).ln()
};
let alpha = (sp - 1.0 - w_dot_u) / w_sq.max(1e-8);
self.u
.iter()
.zip(&self.w)
.map(|(&ui, &wi)| ui + alpha * wi)
.collect()
}
}
impl FlowLayer for PlanarFlow {
fn forward_flow(&self, z: &[f64]) -> Result<(Vec<f64>, f64)> {
if z.len() != self.dim {
return Err(NeuralError::ShapeMismatch(format!(
"PlanarFlow: expected dim {}, got {}",
self.dim,
z.len()
)));
}
let u_hat = self.u_hat();
let lin: f64 = z.iter().zip(&self.w).map(|(&zi, &wi)| zi * wi).sum::<f64>() + self.b;
let h = tanh(lin);
let h_prime = 1.0 - h * h; let z_prime: Vec<f64> = z
.iter()
.zip(&u_hat)
.map(|(&zi, &ui)| zi + ui * h)
.collect();
let u_dot_w: f64 = u_hat.iter().zip(&self.w).map(|(&ui, &wi)| ui * wi).sum();
let log_det = (1.0 + u_dot_w * h_prime).abs().ln();
Ok((z_prime, log_det))
}
fn inverse_flow(&self, z_prime: &[f64]) -> Result<Vec<f64>> {
if z_prime.len() != self.dim {
return Err(NeuralError::ShapeMismatch(format!(
"PlanarFlow inverse: expected dim {}, got {}",
self.dim,
z_prime.len()
)));
}
let u_hat = self.u_hat();
let u_dot_w: f64 = u_hat.iter().zip(&self.w).map(|(&ui, &wi)| ui * wi).sum();
let w_dot_zprime: f64 = z_prime.iter().zip(&self.w).map(|(&zi, &wi)| zi * wi).sum();
let mut a = w_dot_zprime;
for _ in 0..100 {
let a_new = w_dot_zprime - u_dot_w * tanh(a);
if (a_new - a).abs() < 1e-10 {
a = a_new;
break;
}
a = a_new;
}
let h = tanh(a - self.b + self.b); let h_val = tanh(a);
let z: Vec<f64> = z_prime
.iter()
.zip(&u_hat)
.map(|(&zi, &ui)| zi - ui * h_val)
.collect();
Ok(z)
}
fn dim(&self) -> usize {
self.dim
}
}
#[derive(Debug, Clone)]
pub struct AffineCoupling {
dim: usize,
scale_layers: Vec<(Vec<f64>, Vec<f64>)>,
translate_layers: Vec<(Vec<f64>, Vec<f64>)>,
hidden_dim: usize,
}
impl AffineCoupling {
pub fn new(dim: usize, hidden_dim: usize) -> Result<Self> {
if dim < 2 {
return Err(NeuralError::InvalidArgument(
"AffineCoupling: dim must be >= 2".to_string(),
));
}
let half = dim / 2;
let rest = dim - half;
let scale_layers = vec![
(make_weight_matrix(half, hidden_dim, 0), make_bias_vector(hidden_dim)),
(make_weight_matrix(hidden_dim, rest, hidden_dim), make_bias_vector(rest)),
];
let translate_layers = vec![
(make_weight_matrix(half, hidden_dim, 2 * hidden_dim), make_bias_vector(hidden_dim)),
(
make_weight_matrix(hidden_dim, rest, 3 * hidden_dim),
make_bias_vector(rest),
),
];
Ok(Self {
dim,
scale_layers,
translate_layers,
hidden_dim,
})
}
fn half(&self) -> usize {
self.dim / 2
}
fn rest(&self) -> usize {
self.dim - self.half()
}
}
impl FlowLayer for AffineCoupling {
fn forward_flow(&self, z: &[f64]) -> Result<(Vec<f64>, f64)> {
if z.len() != self.dim {
return Err(NeuralError::ShapeMismatch(format!(
"AffineCoupling forward: expected {}, got {}",
self.dim,
z.len()
)));
}
let half = self.half();
let z1 = &z[..half];
let z2 = &z[half..];
let s = mlp_forward(z1, &self.scale_layers);
let t = mlp_forward(z1, &self.translate_layers);
let z2_out: Vec<f64> = z2
.iter()
.zip(&s)
.zip(&t)
.map(|((&zi, &si), &ti)| zi * si.exp() + ti)
.collect();
let log_det: f64 = s.iter().sum();
let mut out = z1.to_vec();
out.extend_from_slice(&z2_out);
Ok((out, log_det))
}
fn inverse_flow(&self, z_prime: &[f64]) -> Result<Vec<f64>> {
if z_prime.len() != self.dim {
return Err(NeuralError::ShapeMismatch(format!(
"AffineCoupling inverse: expected {}, got {}",
self.dim,
z_prime.len()
)));
}
let half = self.half();
let z1 = &z_prime[..half];
let z2_prime = &z_prime[half..];
let s = mlp_forward(z1, &self.scale_layers);
let t = mlp_forward(z1, &self.translate_layers);
let z2: Vec<f64> = z2_prime
.iter()
.zip(&s)
.zip(&t)
.map(|((&zi, &si), &ti)| (zi - ti) * (-si).exp())
.collect();
let mut out = z1.to_vec();
out.extend_from_slice(&z2);
Ok(out)
}
fn dim(&self) -> usize {
self.dim
}
}
#[derive(Debug, Clone)]
pub struct RealNVP {
coupling: AffineCoupling,
mask_first: bool,
}
impl RealNVP {
pub fn new(dim: usize, hidden_dim: usize, mask_first: bool) -> Result<Self> {
let coupling = AffineCoupling::new(dim, hidden_dim)?;
Ok(Self { coupling, mask_first })
}
}
impl FlowLayer for RealNVP {
fn forward_flow(&self, z: &[f64]) -> Result<(Vec<f64>, f64)> {
if self.mask_first {
self.coupling.forward_flow(z)
} else {
let dim = z.len();
let half = dim / 2;
let mut flipped: Vec<f64> = z[half..].to_vec();
flipped.extend_from_slice(&z[..half]);
let (mut out, log_det) = self.coupling.forward_flow(&flipped)?;
let second_half = out[half..].to_vec();
let first_half = out[..half].to_vec();
out[..half].copy_from_slice(&second_half[..half.min(second_half.len())]);
out[half..].copy_from_slice(&first_half[..first_half.len().min(dim - half)]);
Ok((out, log_det))
}
}
fn inverse_flow(&self, z_prime: &[f64]) -> Result<Vec<f64>> {
if self.mask_first {
self.coupling.inverse_flow(z_prime)
} else {
let dim = z_prime.len();
let half = dim / 2;
let mut flipped: Vec<f64> = z_prime[half..].to_vec();
flipped.extend_from_slice(&z_prime[..half]);
let mut z = self.coupling.inverse_flow(&flipped)?;
let a = z[half..].to_vec();
let b = z[..half].to_vec();
z[..half].copy_from_slice(&a[..half.min(a.len())]);
z[half..].copy_from_slice(&b[..b.len().min(dim - half)]);
Ok(z)
}
}
fn dim(&self) -> usize {
self.coupling.dim()
}
}
#[derive(Debug, Clone)]
pub struct ActNorm {
dim: usize,
log_scale: Vec<f64>,
bias: Vec<f64>,
initialized: bool,
}
impl ActNorm {
pub fn new(dim: usize) -> Result<Self> {
if dim == 0 {
return Err(NeuralError::InvalidArgument(
"ActNorm: dim must be > 0".to_string(),
));
}
Ok(Self {
dim,
log_scale: vec![0.0; dim], bias: vec![0.0; dim],
initialized: false,
})
}
pub fn initialize_from_data(&mut self, x: &[f64]) -> Result<()> {
if x.len() != self.dim {
return Err(NeuralError::ShapeMismatch(format!(
"ActNorm init: expected {} values, got {}",
self.dim,
x.len()
)));
}
let mean = x.iter().sum::<f64>() / self.dim as f64;
let var = x.iter().map(|&v| (v - mean).powi(2)).sum::<f64>() / self.dim as f64;
let std = var.sqrt().max(1e-8);
for i in 0..self.dim {
self.bias[i] = -x[i]; self.log_scale[i] = -std.ln(); }
self.initialized = true;
Ok(())
}
pub fn is_initialized(&self) -> bool {
self.initialized
}
}
impl FlowLayer for ActNorm {
fn forward_flow(&self, z: &[f64]) -> Result<(Vec<f64>, f64)> {
if z.len() != self.dim {
return Err(NeuralError::ShapeMismatch(format!(
"ActNorm forward: expected {}, got {}",
self.dim,
z.len()
)));
}
let z_out: Vec<f64> = z
.iter()
.zip(&self.bias)
.zip(&self.log_scale)
.map(|((&zi, &bi), &ls)| (zi + bi) * ls.exp())
.collect();
let log_det: f64 = self.log_scale.iter().sum();
Ok((z_out, log_det))
}
fn inverse_flow(&self, z_prime: &[f64]) -> Result<Vec<f64>> {
if z_prime.len() != self.dim {
return Err(NeuralError::ShapeMismatch(format!(
"ActNorm inverse: expected {}, got {}",
self.dim,
z_prime.len()
)));
}
let z: Vec<f64> = z_prime
.iter()
.zip(&self.bias)
.zip(&self.log_scale)
.map(|((&zi, &bi), &ls)| zi * (-ls).exp() - bi)
.collect();
Ok(z)
}
fn dim(&self) -> usize {
self.dim
}
}
pub struct NormalizingFlowModel {
layers: Vec<Box<dyn FlowLayer>>,
dim: usize,
}
impl std::fmt::Debug for NormalizingFlowModel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NormalizingFlowModel")
.field("num_layers", &self.layers.len())
.field("dim", &self.dim)
.finish()
}
}
impl NormalizingFlowModel {
pub fn new(dim: usize) -> Result<Self> {
if dim == 0 {
return Err(NeuralError::InvalidArgument(
"NormalizingFlowModel: dim must be > 0".to_string(),
));
}
Ok(Self { layers: Vec::new(), dim })
}
pub fn push_layer(&mut self, layer: Box<dyn FlowLayer>) -> Result<()> {
if layer.dim() != self.dim {
return Err(NeuralError::ShapeMismatch(format!(
"NormalizingFlowModel: layer dim {} != model dim {}",
layer.dim(),
self.dim
)));
}
self.layers.push(layer);
Ok(())
}
pub fn num_layers(&self) -> usize {
self.layers.len()
}
pub fn inverse(&self, x: &[f64]) -> Result<(Vec<f64>, f64)> {
let mut z = x.to_vec();
let mut log_det_total = 0.0f64;
for layer in self.layers.iter().rev() {
z = layer.inverse_flow(&z)?;
}
let mut z_fwd = z.clone();
for layer in &self.layers {
let (z_next, ld) = layer.forward_flow(&z_fwd)?;
log_det_total += ld;
z_fwd = z_next;
}
Ok((z, log_det_total))
}
pub fn forward(&self, z0: &[f64]) -> Result<Vec<f64>> {
let mut z = z0.to_vec();
for layer in &self.layers {
let (z_next, _) = layer.forward_flow(&z)?;
z = z_next;
}
Ok(z)
}
pub fn log_likelihood(&self, x: &[f64]) -> Result<f64> {
let (z0, log_det) = self.inverse(x)?;
let sq_norm: f64 = z0.iter().map(|&v| v * v).sum();
let d = self.dim as f64;
let log_pz = -0.5 * (sq_norm + d * (2.0 * std::f64::consts::PI).ln());
Ok(log_pz + log_det)
}
pub fn sample(&self, rng_state: &mut u64) -> Vec<f64> {
let z0: Vec<f64> = (0..self.dim)
.map(|_| standard_normal_sample(rng_state))
.collect();
self.forward(&z0).unwrap_or_else(|_| z0)
}
}
fn standard_normal_sample(state: &mut u64) -> f64 {
*state = state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1_442_695_040_888_963_407);
let u1 = ((*state >> 33) as f64 + 1.0) / (u32::MAX as f64 + 2.0);
*state = state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1_442_695_040_888_963_407);
let u2 = ((*state >> 33) as f64 + 1.0) / (u32::MAX as f64 + 2.0);
(-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
}
#[derive(Debug, Clone)]
pub struct FlowTrainerConfig {
pub learning_rate: f64,
pub epochs: usize,
pub batch_size: usize,
pub fd_eps: f64,
pub grad_clip: f64,
}
impl Default for FlowTrainerConfig {
fn default() -> Self {
Self {
learning_rate: 1e-3,
epochs: 10,
batch_size: 32,
fd_eps: 1e-4,
grad_clip: 1.0,
}
}
}
#[derive(Debug, Clone)]
pub struct FlowTrainingStats {
pub nll_history: Vec<f64>,
}
pub struct FlowTrainer {
pub config: FlowTrainerConfig,
pub stats: FlowTrainingStats,
rng_state: u64,
}
impl std::fmt::Debug for FlowTrainer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FlowTrainer")
.field("config", &self.config)
.finish()
}
}
impl FlowTrainer {
pub fn new(config: FlowTrainerConfig) -> Self {
Self {
config,
stats: FlowTrainingStats { nll_history: Vec::new() },
rng_state: 0xabcdef1234567890,
}
}
pub fn train(
&mut self,
model: &mut NormalizingFlowModel,
data: &[Vec<f64>],
) -> Result<&FlowTrainingStats> {
if data.is_empty() {
return Err(NeuralError::InvalidArgument(
"FlowTrainer: data must be non-empty".to_string(),
));
}
self.stats.nll_history.clear();
let bs = self.config.batch_size.max(1);
for _epoch in 0..self.config.epochs {
let mut epoch_nll = 0.0f64;
let mut n_batches = 0usize;
let mut start = 0;
while start < data.len() {
let end = (start + bs).min(data.len());
let batch = &data[start..end];
let batch_nll: f64 = batch
.iter()
.map(|x| {
model
.log_likelihood(x)
.map(|ll| -ll)
.unwrap_or(f64::INFINITY)
})
.sum::<f64>()
/ batch.len() as f64;
epoch_nll += batch_nll;
n_batches += 1;
start = end;
}
let avg_nll = if n_batches > 0 {
epoch_nll / n_batches as f64
} else {
f64::INFINITY
};
self.stats.nll_history.push(avg_nll);
}
Ok(&self.stats)
}
pub fn sample(&mut self, model: &NormalizingFlowModel) -> Vec<f64> {
model.sample(&mut self.rng_state)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_planar_flow_forward_inverse() {
let pf = PlanarFlow::new(4).expect("creation failed");
let z = vec![0.5, -0.3, 1.2, 0.0];
let (z_prime, log_det) = pf.forward_flow(&z).expect("forward failed");
assert_eq!(z_prime.len(), 4);
assert!(log_det.is_finite());
let z_rec = pf.inverse_flow(&z_prime).expect("inverse failed");
for (a, b) in z.iter().zip(&z_rec) {
assert!((a - b).abs() < 1e-6, "reconstruction error: {a} vs {b}");
}
}
#[test]
fn test_affine_coupling_invertible() {
let ac = AffineCoupling::new(6, 8).expect("creation failed");
let z = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6];
let (z_prime, log_det) = ac.forward_flow(&z).expect("forward failed");
assert!(log_det.is_finite());
let z_rec = ac.inverse_flow(&z_prime).expect("inverse failed");
for (a, b) in z.iter().zip(&z_rec) {
assert!((a - b).abs() < 1e-6, "AC reconstruction error");
}
}
#[test]
fn test_real_nvp_invertible() {
let rnvp = RealNVP::new(6, 8, true).expect("creation failed");
let z = vec![0.1, -0.2, 0.3, 0.4, -0.5, 0.6];
let (z_prime, _ld) = rnvp.forward_flow(&z).expect("forward failed");
let z_rec = rnvp.inverse_flow(&z_prime).expect("inverse failed");
for (a, b) in z.iter().zip(&z_rec) {
assert!((a - b).abs() < 1e-5, "RealNVP reconstruction error");
}
}
#[test]
fn test_act_norm_invertible() {
let mut an = ActNorm::new(4).expect("creation failed");
let data = vec![1.0, 2.0, 3.0, 4.0];
an.initialize_from_data(&data).expect("init failed");
assert!(an.is_initialized());
let z = vec![1.0, 2.0, 3.0, 4.0];
let (z_prime, log_det) = an.forward_flow(&z).expect("forward failed");
assert!(log_det.is_finite());
let z_rec = an.inverse_flow(&z_prime).expect("inverse failed");
for (a, b) in z.iter().zip(&z_rec) {
assert!((a - b).abs() < 1e-8, "ActNorm reconstruction error");
}
}
#[test]
fn test_normalizing_flow_model_log_likelihood() {
let mut model = NormalizingFlowModel::new(4).expect("model creation failed");
model
.push_layer(Box::new(PlanarFlow::new(4).expect("planar flow")))
.expect("push layer failed");
model
.push_layer(Box::new(AffineCoupling::new(4, 8).expect("affine coupling")))
.expect("push layer failed");
let x = vec![0.1, 0.2, 0.3, 0.4];
let ll = model.log_likelihood(&x).expect("log_likelihood failed");
assert!(ll.is_finite(), "log likelihood must be finite");
}
#[test]
fn test_flow_trainer_basic() {
let config = FlowTrainerConfig {
learning_rate: 1e-3,
epochs: 2,
batch_size: 4,
..FlowTrainerConfig::default()
};
let mut trainer = FlowTrainer::new(config);
let mut model = NormalizingFlowModel::new(4).expect("model creation");
model
.push_layer(Box::new(AffineCoupling::new(4, 8).expect("coupling")))
.expect("push");
let data: Vec<Vec<f64>> = (0..8)
.map(|i| vec![i as f64 * 0.1, 0.2, 0.3, 0.4])
.collect();
let stats = trainer.train(&mut model, &data).expect("training failed");
assert_eq!(stats.nll_history.len(), 2);
for &nll in &stats.nll_history {
assert!(nll.is_finite());
}
}
}