Initial commit
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user