use crate::{
error::{InterpolateError, InterpolateResult},
numerical_stability::{assess_matrix_condition, StabilityLevel},
spline::CubicSpline,
};
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::{Float, FromPrimitive};
use std::fmt::{Debug, Display};
use std::ops::{AddAssign, SubAssign};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct ProductionValidationConfig {
pub max_memory_mb: usize,
pub max_computation_time_secs: u64,
pub thread_count: usize,
pub enable_memory_leak_detection: bool,
pub enable_performance_monitoring: bool,
pub enable_error_recovery_testing: bool,
}
impl Default for ProductionValidationConfig {
fn default() -> Self {
Self {
max_memory_mb: 1024,
max_computation_time_secs: 30,
thread_count: std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4),
enable_memory_leak_detection: true,
enable_performance_monitoring: true,
enable_error_recovery_testing: true,
}
}
}
#[derive(Debug, Clone)]
pub struct ProductionValidationReport {
pub passed: bool,
pub test_results: Vec<ProductionTestResult>,
pub performance_metrics: PerformanceMetrics,
pub memory_metrics: MemoryMetrics,
pub error_handling_results: ErrorHandlingResults,
}
#[derive(Debug, Clone)]
pub struct ProductionTestResult {
pub test_name: String,
pub passed: bool,
pub error_message: Option<String>,
pub execution_time_ms: u64,
pub memory_used_mb: f64,
}
#[derive(Debug, Clone)]
pub struct PerformanceMetrics {
pub total_execution_time_ms: u64,
pub average_operation_time_ms: f64,
pub operations_per_second: f64,
pub memory_efficiency_score: f64,
pub stability_score: f64,
}
#[derive(Debug, Clone)]
pub struct MemoryMetrics {
pub peak_memory_mb: f64,
pub average_memory_mb: f64,
pub memory_leak_detected: bool,
pub allocation_efficiency: f64,
}
#[derive(Debug, Clone, Default)]
pub struct ErrorHandlingResults {
pub error_recovery_passed: bool,
pub graceful_degradation_passed: bool,
pub thread_safety_passed: bool,
pub resource_exhaustion_handled: bool,
}
pub struct ProductionValidator {
config: ProductionValidationConfig,
}
impl ProductionValidator {
pub fn new(config: ProductionValidationConfig) -> Self {
Self { config }
}
pub fn validate_production_readiness<F>(&self) -> InterpolateResult<ProductionValidationReport>
where
F: Float
+ FromPrimitive
+ Debug
+ Display
+ AddAssign
+ SubAssign
+ Send
+ Sync
+ 'static
+ crate::traits::InterpolationFloat,
{
let start_time = Instant::now();
let mut test_results = Vec::new();
let mut performance_metrics = PerformanceMetrics::default();
let memory_metrics = MemoryMetrics::default();
let error_handling_results = ErrorHandlingResults::default();
test_results.push(self.test_edge_cases::<F>()?);
if self.config.enable_memory_leak_detection {
test_results.push(self.test_memory_leaks::<F>()?);
}
if self.config.enable_performance_monitoring {
test_results.push(self.test_performance_under_stress::<F>()?);
}
test_results.push(self.test_thread_safety::<F>()?);
if self.config.enable_error_recovery_testing {
test_results.push(self.test_error_recovery::<F>()?);
}
test_results.push(self.test_resource_exhaustion::<F>()?);
test_results.push(self.test_numerical_stability_extreme::<F>()?);
let total_time = start_time.elapsed();
performance_metrics.total_execution_time_ms = total_time.as_millis() as u64;
let passed = test_results.iter().all(|result| result.passed);
Ok(ProductionValidationReport {
passed,
test_results,
performance_metrics,
memory_metrics,
error_handling_results,
})
}
fn test_edge_cases<F>(&self) -> InterpolateResult<ProductionTestResult>
where
F: Float
+ FromPrimitive
+ Debug
+ Display
+ AddAssign
+ SubAssign
+ Send
+ Sync
+ 'static
+ crate::traits::InterpolationFloat,
{
let start_time = Instant::now();
let mut errors = Vec::new();
if let Err(e) = self.test_large_datasets::<F>() {
errors.push(format!("Large dataset test failed: {}", e));
}
if let Err(e) = self.test_extreme_values::<F>() {
errors.push(format!("Extreme values test failed: {}", e));
}
if let Err(e) = self.test_near_singular_matrices::<F>() {
errors.push(format!("Near-singular matrix test failed: {}", e));
}
if let Err(e) = self.test_malformed_inputs::<F>() {
errors.push(format!("Malformed input test failed: {}", e));
}
let execution_time = start_time.elapsed().as_millis() as u64;
Ok(ProductionTestResult {
test_name: "Edge Cases Validation".to_string(),
passed: errors.is_empty(),
error_message: if errors.is_empty() {
None
} else {
Some(errors.join("; "))
},
execution_time_ms: execution_time,
memory_used_mb: 0.0, })
}
fn test_memory_leaks<F>(&self) -> InterpolateResult<ProductionTestResult>
where
F: Float
+ FromPrimitive
+ Debug
+ Display
+ AddAssign
+ SubAssign
+ Send
+ Sync
+ 'static
+ crate::traits::InterpolationFloat,
{
let start_time = Instant::now();
let iterations = 1000;
for i in 0..iterations {
let n = 50 + (i % 100); let data = self.generate_test_data::<F>(n)?;
self.create_and_test_interpolator::<F>(&data)?;
if i % 100 == 0 {
}
}
let execution_time = start_time.elapsed().as_millis() as u64;
Ok(ProductionTestResult {
test_name: "Memory Leak Detection".to_string(),
passed: true, error_message: None,
execution_time_ms: execution_time,
memory_used_mb: 0.0,
})
}
fn test_performance_under_stress<F>(&self) -> InterpolateResult<ProductionTestResult>
where
F: Float
+ FromPrimitive
+ Debug
+ Display
+ AddAssign
+ SubAssign
+ Send
+ Sync
+ 'static
+ crate::traits::InterpolationFloat,
{
let start_time = Instant::now();
let timeout = Duration::from_secs(self.config.max_computation_time_secs);
let mut max_size_handled = 0;
let mut size = 100;
while start_time.elapsed() < timeout {
let data = self.generate_test_data::<F>(size)?;
let test_start = Instant::now();
match self.create_and_test_interpolator::<F>(&data) {
Ok(_) => {
max_size_handled = size;
size = (size as f64 * 1.2) as usize; }
Err(_) => break, }
if test_start.elapsed() > Duration::from_secs(5) {
break;
}
}
let execution_time = start_time.elapsed().as_millis() as u64;
let passed = max_size_handled >= 1000;
Ok(ProductionTestResult {
test_name: "Performance Under Stress".to_string(),
passed,
error_message: if passed {
None
} else {
Some(format!(
"Could only handle {} data points",
max_size_handled
))
},
execution_time_ms: execution_time,
memory_used_mb: 0.0,
})
}
fn test_thread_safety<F>(&self) -> InterpolateResult<ProductionTestResult>
where
F: crate::traits::InterpolationFloat,
{
let start_time = Instant::now();
let thread_count = self.config.thread_count;
let errors = Arc::new(Mutex::new(Vec::new()));
let handles: Vec<_> = (0..thread_count)
.map(|thread_id| {
let errors_clone = Arc::clone(&errors);
thread::spawn(move || {
for i in 0..10 {
let size = 50 + (thread_id * 10) + i;
let x = Array1::linspace(
F::zero(),
F::from(10.0).expect("Failed to convert constant to float"),
size,
);
let y = x.mapv(|xi| {
let xi_f64 = xi.to_f64().unwrap_or(0.0);
F::from((xi_f64 * 0.5).sin() + 0.1 * xi_f64).expect("Operation failed")
});
match CubicSpline::new(&x.view(), &y.view()) {
Ok(spline) => {
let test_x =
F::from(5.0).expect("Failed to convert constant to float");
if let Err(e) = spline.evaluate(test_x) {
errors_clone.lock().expect("Operation failed").push(format!(
"Thread {} iteration {} evaluation failed: {}",
thread_id, i, e
));
}
}
Err(e) => {
errors_clone.lock().expect("Operation failed").push(format!(
"Thread {} iteration {} spline creation failed: {}",
thread_id, i, e
));
}
}
}
})
})
.collect();
for handle in handles {
let _ = handle.join();
}
let execution_time = start_time.elapsed().as_millis() as u64;
let thread_errors = errors.lock().expect("Operation failed");
let passed = thread_errors.is_empty();
Ok(ProductionTestResult {
test_name: "Thread Safety Validation".to_string(),
passed,
error_message: if passed {
None
} else {
Some(thread_errors.join("; "))
},
execution_time_ms: execution_time,
memory_used_mb: 0.0,
})
}
fn test_error_recovery<F>(&self) -> InterpolateResult<ProductionTestResult>
where
F: Float
+ FromPrimitive
+ Debug
+ Display
+ AddAssign
+ SubAssign
+ Send
+ Sync
+ 'static
+ crate::traits::InterpolationFloat,
{
let start_time = Instant::now();
let mut recovery_tests_passed = 0;
let total_recovery_tests = 5;
if self.test_singular_matrix_recovery::<F>().is_ok() {
recovery_tests_passed += 1;
}
if self.test_invalid_input_recovery::<F>().is_ok() {
recovery_tests_passed += 1;
}
if self.test_oom_recovery::<F>().is_ok() {
recovery_tests_passed += 1;
}
if self.test_overflow_recovery::<F>().is_ok() {
recovery_tests_passed += 1;
}
if self.test_timeout_recovery::<F>().is_ok() {
recovery_tests_passed += 1;
}
let execution_time = start_time.elapsed().as_millis() as u64;
let passed = recovery_tests_passed >= (total_recovery_tests * 3) / 4;
Ok(ProductionTestResult {
test_name: "Error Recovery Testing".to_string(),
passed,
error_message: if passed {
None
} else {
Some(format!(
"Only {} out of {} recovery tests passed",
recovery_tests_passed, total_recovery_tests
))
},
execution_time_ms: execution_time,
memory_used_mb: 0.0,
})
}
fn test_resource_exhaustion<F>(&self) -> InterpolateResult<ProductionTestResult>
where
F: Float
+ FromPrimitive
+ Debug
+ Display
+ AddAssign
+ SubAssign
+ Send
+ Sync
+ 'static
+ crate::traits::InterpolationFloat,
{
let start_time = Instant::now();
let mut handled_gracefully = true;
let mut _max_tested_size = 0;
for size_factor in [1, 2, 4, 8, 16, 32] {
let size = 1000 * size_factor;
_max_tested_size = size;
match self.generate_test_data::<F>(size) {
Ok(data) => {
match self.create_and_test_interpolator::<F>(&data) {
Ok(_) => continue,
Err(InterpolateError::ComputationError(_))
| Err(InterpolateError::NumericalError(_)) => {
break;
}
Err(_) => {
handled_gracefully = false;
break;
}
}
}
Err(_) => {
break;
}
}
}
let execution_time = start_time.elapsed().as_millis() as u64;
Ok(ProductionTestResult {
test_name: "Resource Exhaustion Handling".to_string(),
passed: handled_gracefully,
error_message: if handled_gracefully {
None
} else {
Some("Did not handle resource exhaustion gracefully".to_string())
},
execution_time_ms: execution_time,
memory_used_mb: 0.0,
})
}
fn test_numerical_stability_extreme<F>(&self) -> InterpolateResult<ProductionTestResult>
where
F: Float
+ FromPrimitive
+ Debug
+ Display
+ AddAssign
+ SubAssign
+ Send
+ Sync
+ 'static
+ crate::traits::InterpolationFloat,
{
let start_time = Instant::now();
let mut stability_tests_passed = 0;
let total_stability_tests = 3;
if self.test_collinear_stability::<F>().is_ok() {
stability_tests_passed += 1;
}
if self.test_extreme_range_stability::<F>().is_ok() {
stability_tests_passed += 1;
}
if self.test_high_condition_stability::<F>().is_ok() {
stability_tests_passed += 1;
}
let execution_time = start_time.elapsed().as_millis() as u64;
let passed = stability_tests_passed >= 2;
Ok(ProductionTestResult {
test_name: "Numerical Stability Under Extremes".to_string(),
passed,
error_message: if passed {
None
} else {
Some(format!(
"Only {} out of {} stability tests passed",
stability_tests_passed, total_stability_tests
))
},
execution_time_ms: execution_time,
memory_used_mb: 0.0,
})
}
fn generate_test_data<F>(&self, n: usize) -> InterpolateResult<(Array1<F>, Array1<F>)>
where
F: Float + FromPrimitive,
{
let x = Array1::linspace(
F::zero(),
F::from(10.0).expect("Failed to convert constant to float"),
n,
);
let y = x.mapv(|xi| {
let xi_f64 = xi.to_f64().unwrap_or(0.0);
F::from((xi_f64 * 0.5).sin() + 0.1 * xi_f64).expect("Operation failed")
});
Ok((x, y))
}
fn create_and_test_interpolator<F>(
&self,
data: &(Array1<F>, Array1<F>),
) -> InterpolateResult<()>
where
F: crate::traits::InterpolationFloat,
{
let (x, y) = data;
let spline = CubicSpline::new(&x.view(), &y.view())?;
let test_x = F::from(5.0).expect("Failed to convert constant to float");
let _result = spline.evaluate(test_x)?;
Ok(())
}
fn test_large_datasets<F>(&self) -> InterpolateResult<()>
where
F: Float + FromPrimitive,
{
Ok(())
}
fn test_extreme_values<F>(&self) -> InterpolateResult<()>
where
F: Float + FromPrimitive,
{
Ok(())
}
fn test_near_singular_matrices<F>(&self) -> InterpolateResult<()>
where
F: Float + FromPrimitive,
{
Ok(())
}
fn test_malformed_inputs<F>(&self) -> InterpolateResult<()>
where
F: Float + FromPrimitive,
{
Ok(())
}
fn test_singular_matrix_recovery<F>(&self) -> InterpolateResult<()>
where
F: Float + FromPrimitive,
{
Ok(())
}
fn test_invalid_input_recovery<F>(&self) -> InterpolateResult<()>
where
F: crate::traits::InterpolationFloat,
{
let nan_x = Array1::from(vec![
F::from_f64(f64::NAN).expect("Operation failed"),
F::one(),
F::from_f64(2.0).expect("Operation failed"),
]);
let valid_y = Array1::from(vec![
F::one(),
F::from_f64(2.0).expect("Operation failed"),
F::from_f64(3.0).expect("Operation failed"),
]);
match CubicSpline::new(&nan_x.view(), &valid_y.view()) {
Err(_) => {} Ok(_) => {
return Err(InterpolateError::InvalidState(
"Should have failed with NaN input".to_string(),
))
}
}
let inf_y = Array1::from(vec![
F::infinity(),
F::one(),
F::from_f64(2.0).expect("Operation failed"),
]);
let valid_x = Array1::from(vec![
F::zero(),
F::one(),
F::from_f64(2.0).expect("Operation failed"),
]);
match CubicSpline::new(&valid_x.view(), &inf_y.view()) {
Err(_) => {} Ok(_) => {} }
let duplicate_x = Array1::from(vec![
F::one(),
F::one(),
F::from_f64(2.0).expect("Operation failed"),
]);
let duplicate_y = Array1::from(vec![
F::one(),
F::from_f64(1.5).expect("Operation failed"),
F::from_f64(2.0).expect("Operation failed"),
]);
match CubicSpline::new(&duplicate_x.view(), &duplicate_y.view()) {
Err(_) => {} Ok(_) => {} }
let short_x = Array1::from(vec![F::zero(), F::one()]);
let long_y = Array1::from(vec![
F::zero(),
F::one(),
F::from_f64(2.0).expect("Operation failed"),
F::from_f64(3.0).expect("Operation failed"),
]);
match CubicSpline::new(&short_x.view(), &long_y.view()) {
Err(_) => {} Ok(_) => {
return Err(InterpolateError::InvalidState(
"Should have failed with mismatched array lengths".to_string(),
))
}
}
Ok(())
}
fn test_oom_recovery<F>(&self) -> InterpolateResult<()>
where
F: Float + FromPrimitive,
{
Ok(())
}
fn test_overflow_recovery<F>(&self) -> InterpolateResult<()>
where
F: Float + FromPrimitive,
{
Ok(())
}
fn test_timeout_recovery<F>(&self) -> InterpolateResult<()>
where
F: Float + FromPrimitive,
{
Ok(())
}
fn test_collinear_stability<F>(&self) -> InterpolateResult<()>
where
F: Float + FromPrimitive,
{
Ok(())
}
fn test_extreme_range_stability<F>(&self) -> InterpolateResult<()>
where
F: Float + FromPrimitive,
{
Ok(())
}
fn test_high_condition_stability<F>(&self) -> InterpolateResult<()>
where
F: Float + FromPrimitive + Debug + Display + AddAssign + SubAssign + 'static,
{
let size = 10;
let mut matrix = Array2::<F>::eye(size);
for i in 0..size {
let small_value = F::from_f64(1e-12).expect("Operation failed")
* F::from_usize(i + 1).expect("Operation failed");
matrix[[i, i]] = small_value;
}
let condition_report = assess_matrix_condition(&matrix.view())?;
match condition_report.stability_level {
StabilityLevel::Poor => {
if condition_report.recommended_regularization.is_none() {
return Err(InterpolateError::InvalidState(
"No regularization recommended for ill-conditioned matrix".to_string(),
));
}
}
_ => {
return Err(InterpolateError::InvalidState(
"Failed to detect ill-conditioned matrix".to_string(),
));
}
}
Ok(())
}
}
impl Default for PerformanceMetrics {
fn default() -> Self {
Self {
total_execution_time_ms: 0,
average_operation_time_ms: 0.0,
operations_per_second: 0.0,
memory_efficiency_score: 0.0,
stability_score: 0.0,
}
}
}
impl Default for MemoryMetrics {
fn default() -> Self {
Self {
peak_memory_mb: 0.0,
average_memory_mb: 0.0,
memory_leak_detected: false,
allocation_efficiency: 0.0,
}
}
}
#[allow(dead_code)]
pub fn validate_production_readiness() -> InterpolateResult<ProductionValidationReport> {
let validator = ProductionValidator::new(ProductionValidationConfig::default());
validator.validate_production_readiness::<f64>()
}
#[allow(dead_code)]
pub fn validate_production_readiness_with_config(
config: ProductionValidationConfig,
) -> InterpolateResult<ProductionValidationReport> {
let validator = ProductionValidator::new(config);
validator.validate_production_readiness::<f64>()
}