Files
rustytorch/demos/rtx-piddm-demo/src/sampling.rs
T
osobhandClaude Opus 4.6 02d382d5f6 style: apply rustfmt across all crates and demos
Consistent formatting pass: line wrapping, import sorting, trailing
whitespace removal, let-chain indentation, merged derive attributes,
and unsafe block reformatting.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-12 07:01:58 -07:00

287 lines
9.1 KiB
Rust

//! Sampling module for PIDDM demo.
use tokio::sync::mpsc;
use rtx_piddm::DDPMScheduler;
use rtx_piddm_shared::{
GeneratedSample, PdeType, PiddmError, PiddmResult, PiddmSamplingConfig, SamplingProgress,
SamplingResult,
};
/// PIDDM sampler for the demo.
pub struct PiddmSampler {
config: PiddmSamplingConfig,
pde_type: PdeType,
scheduler: Option<DDPMScheduler>,
}
impl PiddmSampler {
/// Create a new sampler.
#[must_use]
pub fn new(config: PiddmSamplingConfig, pde_type: PdeType) -> Self {
Self {
config,
pde_type,
scheduler: None,
}
}
/// Load model weights.
pub fn load_weights(&mut self, _path: Option<&str>) -> PiddmResult<()> {
// Initialize scheduler
let scheduler = DDPMScheduler::new(
1000, // num_timesteps
1e-4, // beta_start
0.02, // beta_end
);
self.scheduler = Some(scheduler);
// TODO: Load actual weights from SafeTensors file
Ok(())
}
/// Generate samples with progress reporting.
pub async fn sample(
&self,
progress_tx: mpsc::Sender<SamplingProgress>,
) -> PiddmResult<SamplingResult> {
if self.scheduler.is_none() {
return Err(PiddmError::ModelNotLoaded);
}
let start_time = std::time::Instant::now();
let num_samples = self.config.num_samples;
let resolution = self.config.resolution as usize;
let total_steps = self.config.sampling_steps;
let mut samples = Vec::with_capacity(num_samples as usize);
let mut total_physics_residual = 0.0;
for sample_idx in 0..num_samples {
// Simulate denoising process
let mut field = vec![0.0f32; resolution * resolution];
// Initialize with noise
for v in &mut field {
*v = rand::random::<f32>() * 2.0 - 1.0;
}
for step in 0..total_steps {
// Calculate noise level (decreasing)
let t = 1.0 - (f64::from(step) + 1.0) / f64::from(total_steps);
let noise_level = t * 0.5; // Simplified noise schedule
// Simulate denoising step
let scale = 1.0 - 0.01 * (step as f32 + 1.0) / total_steps as f32;
for v in &mut field {
*v *= scale;
// Add structure based on PDE type
*v += 0.01 * rand::random::<f32>();
}
// Calculate physics residual
let physics_residual = self.compute_physics_residual(&field, resolution);
let progress = SamplingProgress {
step: step + 1,
total_steps,
sample: sample_idx + 1,
total_samples: num_samples,
noise_level,
physics_residual,
};
if progress_tx.send(progress).await.is_err() {
return Err(PiddmError::SamplingError(
"Progress channel closed".to_string(),
));
}
// Small delay
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
}
// Generate final sample based on PDE type
let field = self.generate_pde_solution(resolution);
let (min_val, max_val) = field.iter().fold((f32::MAX, f32::MIN), |(min, max), &v| {
(min.min(v), max.max(v))
});
let physics_residual = self.compute_physics_residual(&field, resolution);
total_physics_residual += physics_residual;
samples.push(GeneratedSample {
index: sample_idx,
field,
resolution: resolution as u32,
physics_residual,
max_value: f64::from(max_val),
min_value: f64::from(min_val),
});
}
let sampling_time = start_time.elapsed().as_secs_f64();
let avg_physics_residual = total_physics_residual / f64::from(num_samples);
Ok(SamplingResult {
samples,
sampling_time_seconds: sampling_time,
avg_physics_residual,
pde_type: self.pde_type,
})
}
/// Generate a PDE solution based on type.
fn generate_pde_solution(&self, resolution: usize) -> Vec<f32> {
let mut field = vec![0.0f32; resolution * resolution];
let dx = 1.0 / (resolution as f32 - 1.0);
match self.pde_type {
PdeType::Poisson => {
// Sinusoidal solution
let kx = 2.0;
let ky = 2.0;
let pi = std::f32::consts::PI;
for j in 0..resolution {
for i in 0..resolution {
let x = i as f32 * dx;
let y = j as f32 * dx;
field[j * resolution + i] = (pi * kx * x).sin() * (pi * ky * y).sin();
}
}
}
PdeType::Heat => {
// Linear temperature gradient
for j in 0..resolution {
for i in 0..resolution {
let y = j as f32 * dx;
field[j * resolution + i] = y;
}
}
}
PdeType::Darcy => {
// Gaussian pressure field
let cx = 0.5;
let cy = 0.5;
let sigma = 0.2;
for j in 0..resolution {
for i in 0..resolution {
let x = i as f32 * dx;
let y = j as f32 * dx;
let r2 = (x - cx).powi(2) + (y - cy).powi(2);
field[j * resolution + i] = (-r2 / (2.0 * sigma * sigma)).exp();
}
}
}
PdeType::Burgers => {
// Shock solution
let shock_x = 0.5;
let steepness = 20.0;
for j in 0..resolution {
for i in 0..resolution {
let x = i as f32 * dx;
field[j * resolution + i] =
0.5 * (1.0 - (steepness * (x - shock_x)).tanh());
}
}
}
}
field
}
/// Compute physics residual (L2 norm of PDE residual).
fn compute_physics_residual(&self, field: &[f32], resolution: usize) -> f64 {
let dx = 1.0 / (resolution as f32 - 1.0);
let mut residual_sum = 0.0f64;
let mut count = 0;
for j in 1..resolution - 1 {
for i in 1..resolution - 1 {
let idx = j * resolution + i;
let u = field[idx];
let u_xm = field[idx - 1];
let u_xp = field[idx + 1];
let u_ym = field[idx - resolution];
let u_yp = field[idx + resolution];
// Laplacian
let laplacian =
(u_xm - 2.0 * u + u_xp) / (dx * dx) + (u_ym - 2.0 * u + u_yp) / (dx * dx);
// For Poisson, residual is laplacian (assuming zero source for simplicity)
residual_sum += f64::from(laplacian).powi(2);
count += 1;
}
}
if count > 0 {
(residual_sum / f64::from(count)).sqrt()
} else {
0.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sampler_creation() {
let config = PiddmSamplingConfig::default();
let sampler = PiddmSampler::new(config, PdeType::Poisson);
assert!(sampler.scheduler.is_none());
}
#[test]
fn test_load_weights() {
let config = PiddmSamplingConfig::default();
let mut sampler = PiddmSampler::new(config, PdeType::Poisson);
sampler.load_weights(None).unwrap();
assert!(sampler.scheduler.is_some());
}
#[tokio::test]
async fn test_sampling() {
let config = PiddmSamplingConfig {
num_samples: 2,
resolution: 16,
sampling_steps: 10,
..Default::default()
};
let mut sampler = PiddmSampler::new(config, PdeType::Poisson);
sampler.load_weights(None).unwrap();
let (tx, mut rx) = mpsc::channel(100);
let result = sampler.sample(tx).await.unwrap();
assert_eq!(result.samples.len(), 2);
assert_eq!(result.samples[0].resolution, 16);
// Check we received progress updates
let mut count = 0;
while rx.try_recv().is_ok() {
count += 1;
}
assert!(count > 0);
}
#[test]
fn test_physics_residual() {
let config = PiddmSamplingConfig {
resolution: 32,
..Default::default()
};
let sampler = PiddmSampler::new(config, PdeType::Poisson);
// Generate a smooth field
let field = sampler.generate_pde_solution(32);
let residual = sampler.compute_physics_residual(&field, 32);
// Residual should be finite and reasonable
assert!(residual.is_finite());
}
}