Initial commit

This commit is contained in:
redclawsystems
2026-03-04 00:08:42 +00:00
commit 4d88dc0584
4449 changed files with 1556714 additions and 0 deletions
+307
View File
@@ -0,0 +1,307 @@
//! Benchmark runner for PINN problems
use crate::error::{PINNError, Result};
use pinn_benchmark_shared::ProblemType;
/// Benchmark runner configuration
#[derive(Debug, Clone)]
pub struct BenchmarkConfig {
/// Problem to benchmark
pub problem_type: ProblemType,
/// Hidden layer sizes
pub hidden_layers: Vec<usize>,
/// Learning rate
pub learning_rate: f64,
/// Number of training epochs
pub num_epochs: usize,
/// Number of collocation points
pub num_collocation_points: usize,
/// Number of boundary points
pub num_boundary_points: usize,
}
impl BenchmarkConfig {
/// Creates a new benchmark configuration
///
/// # Errors
///
/// Returns an error if configuration is invalid
pub fn new(
problem_type: ProblemType,
hidden_layers: Vec<usize>,
learning_rate: f64,
num_epochs: usize,
num_collocation_points: usize,
num_boundary_points: usize,
) -> Result<Self> {
if hidden_layers.is_empty() {
return Err(PINNError::invalid_config(
"must have at least one hidden layer",
));
}
if learning_rate <= 0.0 {
return Err(PINNError::invalid_config("learning rate must be positive"));
}
if num_epochs == 0 {
return Err(PINNError::invalid_config("num_epochs must be positive"));
}
if num_collocation_points == 0 {
return Err(PINNError::invalid_config(
"num_collocation_points must be positive",
));
}
if num_boundary_points == 0 {
return Err(PINNError::invalid_config(
"num_boundary_points must be positive",
));
}
Ok(Self {
problem_type,
hidden_layers,
learning_rate,
num_epochs,
num_collocation_points,
num_boundary_points,
})
}
}
/// Computes accuracy metrics between predictions and reference
pub fn compute_accuracy(predictions: &[f64], reference: &[f64]) -> Result<(f64, f64)> {
if predictions.len() != reference.len() {
return Err(PINNError::benchmark(
"prediction and reference size mismatch",
));
}
if predictions.is_empty() {
return Err(PINNError::benchmark("empty arrays"));
}
// L2 error
let l2_error: f64 = predictions
.iter()
.zip(reference.iter())
.map(|(pred, ref_val)| (pred - ref_val).powi(2))
.sum::<f64>()
.sqrt();
// L-infinity error (max absolute error)
let linf_error = predictions
.iter()
.zip(reference.iter())
.map(|(pred, ref_val)| (pred - ref_val).abs())
.fold(0.0_f64, f64::max);
Ok((l2_error, linf_error))
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_benchmark_config_creation_valid() {
let config =
BenchmarkConfig::new(ProblemType::Heat1D, vec![32, 32], 0.001, 1000, 10000, 100);
assert!(config.is_ok());
let config = config.unwrap();
assert_eq!(config.problem_type, ProblemType::Heat1D);
assert_eq!(config.hidden_layers, vec![32, 32]);
assert_abs_diff_eq!(config.learning_rate, 0.001);
}
#[test]
fn test_benchmark_config_empty_hidden_layers() {
let config = BenchmarkConfig::new(ProblemType::Heat1D, vec![], 0.001, 1000, 10000, 100);
assert!(config.is_err());
}
#[test]
fn test_benchmark_config_invalid_learning_rate() {
let config = BenchmarkConfig::new(ProblemType::Heat1D, vec![32], 0.0, 1000, 10000, 100);
assert!(config.is_err());
}
#[test]
fn test_benchmark_config_zero_epochs() {
let config = BenchmarkConfig::new(ProblemType::Heat1D, vec![32], 0.001, 0, 10000, 100);
assert!(config.is_err());
}
#[test]
fn test_benchmark_config_zero_collocation_points() {
let config = BenchmarkConfig::new(ProblemType::Heat1D, vec![32], 0.001, 1000, 0, 100);
assert!(config.is_err());
}
#[test]
fn test_benchmark_config_zero_boundary_points() {
let config = BenchmarkConfig::new(ProblemType::Heat1D, vec![32], 0.001, 1000, 10000, 0);
assert!(config.is_err());
}
#[test]
fn test_compute_accuracy_perfect() {
let predictions = vec![1.0, 2.0, 3.0, 4.0];
let reference = vec![1.0, 2.0, 3.0, 4.0];
let result = compute_accuracy(&predictions, &reference);
assert!(result.is_ok());
let (l2_error, linf_error) = result.unwrap();
assert_abs_diff_eq!(l2_error, 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(linf_error, 0.0, epsilon = 1e-10);
}
#[test]
fn test_compute_accuracy_with_errors() {
let predictions = vec![1.1, 2.2, 2.9, 4.1];
let reference = vec![1.0, 2.0, 3.0, 4.0];
let result = compute_accuracy(&predictions, &reference);
assert!(result.is_ok());
let (l2_error, linf_error) = result.unwrap();
assert!(l2_error > 0.0);
assert!(linf_error > 0.0);
// L-inf should be max error (0.2 at index 1)
assert_abs_diff_eq!(linf_error, 0.2, epsilon = 1e-10);
}
#[test]
fn test_compute_accuracy_l2_calculation() {
let predictions = vec![1.0, 2.0];
let reference = vec![2.0, 3.0];
let result = compute_accuracy(&predictions, &reference);
assert!(result.is_ok());
let (l2_error, _) = result.unwrap();
// L2 = sqrt(1^2 + 1^2) = sqrt(2) ≈ 1.414
assert_abs_diff_eq!(l2_error, 2.0_f64.sqrt(), epsilon = 1e-10);
}
#[test]
fn test_compute_accuracy_length_mismatch() {
let predictions = vec![1.0, 2.0];
let reference = vec![1.0, 2.0, 3.0];
let result = compute_accuracy(&predictions, &reference);
assert!(result.is_err());
}
#[test]
fn test_compute_accuracy_empty_arrays() {
let predictions: Vec<f64> = vec![];
let reference: Vec<f64> = vec![];
let result = compute_accuracy(&predictions, &reference);
assert!(result.is_err());
}
#[test]
fn test_compute_accuracy_single_value() {
let predictions = vec![5.0];
let reference = vec![3.0];
let result = compute_accuracy(&predictions, &reference);
assert!(result.is_ok());
let (l2_error, linf_error) = result.unwrap();
// Both should be 2.0 for single value
assert_abs_diff_eq!(l2_error, 2.0, epsilon = 1e-10);
assert_abs_diff_eq!(linf_error, 2.0, epsilon = 1e-10);
}
#[test]
fn test_compute_accuracy_linf_is_max() {
let predictions = vec![1.0, 2.0, 3.0, 4.0];
let reference = vec![1.1, 1.9, 3.5, 4.0];
let result = compute_accuracy(&predictions, &reference);
assert!(result.is_ok());
let (_, linf_error) = result.unwrap();
// Max error is 0.5 at index 2
assert_abs_diff_eq!(linf_error, 0.5, epsilon = 1e-10);
}
#[test]
fn test_compute_accuracy_negative_errors() {
let predictions = vec![0.0, 0.0];
let reference = vec![1.0, -1.0];
let result = compute_accuracy(&predictions, &reference);
assert!(result.is_ok());
let (l2_error, linf_error) = result.unwrap();
// L2 = sqrt(1 + 1) = sqrt(2)
assert_abs_diff_eq!(l2_error, 2.0_f64.sqrt(), epsilon = 1e-10);
// Linf = max(1, 1) = 1
assert_abs_diff_eq!(linf_error, 1.0, epsilon = 1e-10);
}
#[test]
fn test_compute_accuracy_large_values() {
let predictions = vec![1000.0, 2000.0];
let reference = vec![1001.0, 1999.0];
let result = compute_accuracy(&predictions, &reference);
assert!(result.is_ok());
let (l2_error, linf_error) = result.unwrap();
// L2 = sqrt(1 + 1) = sqrt(2)
assert_abs_diff_eq!(l2_error, 2.0_f64.sqrt(), epsilon = 1e-10);
// Linf = max(1, 1) = 1
assert_abs_diff_eq!(linf_error, 1.0, epsilon = 1e-10);
}
#[test]
fn test_benchmark_config_all_problem_types() {
let problem_types = vec![
ProblemType::Heat1D,
ProblemType::Burgers1D,
ProblemType::Heat2D,
ProblemType::Poisson2D,
ProblemType::NavierStokes2D,
];
for problem_type in problem_types {
let config = BenchmarkConfig::new(problem_type, vec![32], 0.001, 100, 1000, 50);
assert!(config.is_ok());
}
}
#[test]
fn test_benchmark_config_multiple_hidden_layers() {
let config = BenchmarkConfig::new(
ProblemType::Heat1D,
vec![64, 64, 64, 64],
0.001,
1000,
10000,
100,
);
assert!(config.is_ok());
let config = config.unwrap();
assert_eq!(config.hidden_layers.len(), 4);
}
}