Files
rustytorch/demos/pinn-benchmark-shared/src/config.rs
T
2026-03-04 00:08:42 +00:00

250 lines
7.8 KiB
Rust

//! Configuration types for PINN benchmark
use serde::{Deserialize, Serialize};
/// Type of PDE problem to solve
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ProblemType {
/// 1D Burgers equation: `u_t` + u*`u_x` = nu*`u_xx`
Burgers1D,
/// 1D Heat equation: `u_t` = alpha*`u_xx`
Heat1D,
/// 2D Heat equation: `u_t` = alpha*(`u_xx` + `u_yy`)
Heat2D,
/// 2D Poisson equation: `u_xx` + `u_yy` = f
Poisson2D,
/// 2D Navier-Stokes equations (advanced)
NavierStokes2D,
}
impl ProblemType {
/// Returns the number of input dimensions for this problem
#[must_use]
pub const fn input_dim(&self) -> usize {
match self {
Self::Burgers1D | Self::Heat1D => 2, // (x, t)
Self::Heat2D | Self::Poisson2D | Self::NavierStokes2D => 3, // (x, y, t) or (x, y) for Poisson
}
}
/// Returns the number of output dimensions for this problem
#[must_use]
pub const fn output_dim(&self) -> usize {
match self {
Self::Burgers1D | Self::Heat1D | Self::Heat2D | Self::Poisson2D => 1, // scalar field
Self::NavierStokes2D => 3, // (u, v, p) - velocity components and pressure
}
}
/// Returns a human-readable name for this problem
#[must_use]
pub const fn name(&self) -> &'static str {
match self {
Self::Burgers1D => "1D Burgers Equation",
Self::Heat1D => "1D Heat Equation",
Self::Heat2D => "2D Heat Equation",
Self::Poisson2D => "2D Poisson Equation",
Self::NavierStokes2D => "2D Navier-Stokes",
}
}
/// Returns a brief description of the PDE
#[must_use]
pub const fn description(&self) -> &'static str {
match self {
Self::Burgers1D => "Nonlinear advection-diffusion equation with shock formation",
Self::Heat1D => "Linear parabolic PDE modeling heat diffusion",
Self::Heat2D => "2D linear parabolic PDE modeling heat diffusion",
Self::Poisson2D => "Elliptic PDE for steady-state problems",
Self::NavierStokes2D => "Incompressible fluid dynamics equations",
}
}
}
/// Configuration for PINN benchmark
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkConfig {
/// Type of PDE problem to solve
pub problem: ProblemType,
/// Hidden layer sizes (e.g., [64, 64, 64])
pub hidden_layers: Vec<usize>,
/// Learning rate for optimizer
pub learning_rate: f64,
/// Number of training epochs
pub num_epochs: usize,
/// Number of collocation points for physics loss
pub num_collocation_points: usize,
/// Number of boundary/initial condition points
pub num_boundary_points: usize,
/// Device to use: "cpu", "cuda", "metal"
pub device: String,
}
impl BenchmarkConfig {
/// Creates a default configuration for the given problem type
#[must_use]
pub fn default_for_problem(problem: ProblemType) -> Self {
Self {
problem,
hidden_layers: vec![64, 64, 64],
learning_rate: 0.001,
num_epochs: 1000,
num_collocation_points: 10_000,
num_boundary_points: 100,
device: "cpu".to_string(),
}
}
/// Validates the configuration
///
/// # Errors
///
/// Returns an error if the configuration is invalid
pub fn validate(&self) -> Result<(), String> {
if self.hidden_layers.is_empty() {
return Err("Hidden layers cannot be empty".to_string());
}
if self.hidden_layers.contains(&0) {
return Err("Hidden layer sizes must be positive".to_string());
}
if self.learning_rate <= 0.0 || self.learning_rate > 1.0 {
return Err("Learning rate must be in (0, 1]".to_string());
}
if self.num_epochs == 0 {
return Err("Number of epochs must be positive".to_string());
}
if self.num_collocation_points == 0 {
return Err("Number of collocation points must be positive".to_string());
}
if self.num_boundary_points == 0 {
return Err("Number of boundary points must be positive".to_string());
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_problem_type_dimensions() {
assert_eq!(ProblemType::Burgers1D.input_dim(), 2);
assert_eq!(ProblemType::Burgers1D.output_dim(), 1);
assert_eq!(ProblemType::Heat2D.input_dim(), 3);
assert_eq!(ProblemType::Heat2D.output_dim(), 1);
assert_eq!(ProblemType::NavierStokes2D.input_dim(), 3);
assert_eq!(ProblemType::NavierStokes2D.output_dim(), 3);
}
#[test]
fn test_problem_type_metadata() {
let problem = ProblemType::Burgers1D;
assert_eq!(problem.name(), "1D Burgers Equation");
assert!(!problem.description().is_empty());
}
#[test]
fn test_benchmark_config_default() {
let config = BenchmarkConfig::default_for_problem(ProblemType::Heat1D);
assert_eq!(config.problem, ProblemType::Heat1D);
assert!(!config.hidden_layers.is_empty());
assert!(config.learning_rate > 0.0);
}
#[test]
fn test_benchmark_config_validation_success() {
let config = BenchmarkConfig::default_for_problem(ProblemType::Burgers1D);
assert!(config.validate().is_ok());
}
#[test]
fn test_benchmark_config_validation_empty_layers() {
let mut config = BenchmarkConfig::default_for_problem(ProblemType::Heat1D);
config.hidden_layers = vec![];
assert!(config.validate().is_err());
}
#[test]
fn test_benchmark_config_validation_zero_layer_size() {
let mut config = BenchmarkConfig::default_for_problem(ProblemType::Heat1D);
config.hidden_layers = vec![64, 0, 32];
assert!(config.validate().is_err());
}
#[test]
fn test_benchmark_config_validation_invalid_lr() {
let mut config = BenchmarkConfig::default_for_problem(ProblemType::Heat1D);
config.learning_rate = 0.0;
assert!(config.validate().is_err());
config.learning_rate = 1.5;
assert!(config.validate().is_err());
}
#[test]
fn test_benchmark_config_validation_zero_epochs() {
let mut config = BenchmarkConfig::default_for_problem(ProblemType::Heat1D);
config.num_epochs = 0;
assert!(config.validate().is_err());
}
#[test]
fn test_benchmark_config_validation_zero_points() {
let mut config = BenchmarkConfig::default_for_problem(ProblemType::Heat1D);
config.num_collocation_points = 0;
assert!(config.validate().is_err());
config.num_collocation_points = 100;
config.num_boundary_points = 0;
assert!(config.validate().is_err());
}
#[test]
fn test_serde_problem_type() {
let problem = ProblemType::Poisson2D;
let json = serde_json::to_string(&problem).expect("Failed to serialize");
let deserialized: ProblemType = serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(problem, deserialized);
}
#[test]
fn test_serde_benchmark_config() {
let config = BenchmarkConfig {
problem: ProblemType::Heat2D,
hidden_layers: vec![32, 32, 32],
learning_rate: 0.0005,
num_epochs: 500,
num_collocation_points: 5000,
num_boundary_points: 200,
device: "cuda".to_string(),
};
let json = serde_json::to_string(&config).expect("Failed to serialize");
let deserialized: BenchmarkConfig =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(config.problem, deserialized.problem);
assert_eq!(config.hidden_layers, deserialized.hidden_layers);
assert_eq!(config.device, deserialized.device);
}
}