413 lines
11 KiB
Rust
413 lines
11 KiB
Rust
// Copyright (c) 2024 RustyTorch++ Team
|
|
// Licensed under the Apache License, Version 2.0
|
|
|
|
//! Utility functions and helpers for finite element analysis.
|
|
|
|
pub mod io;
|
|
pub mod math;
|
|
pub mod profiling;
|
|
pub mod visualization;
|
|
|
|
#[cfg(test)]
|
|
mod math_tests;
|
|
|
|
use nalgebra::Vector3;
|
|
use std::time::{Duration, Instant};
|
|
|
|
pub use io::*;
|
|
pub use math::*;
|
|
pub use profiling::*;
|
|
pub use visualization::*;
|
|
|
|
/// Coordinate system transformations.
|
|
pub struct CoordinateTransforms;
|
|
|
|
impl CoordinateTransforms {
|
|
/// Convert Cartesian to cylindrical coordinates.
|
|
pub fn cartesian_to_cylindrical(point: &Vector3<f64>) -> Vector3<f64> {
|
|
let r = (point.x * point.x + point.y * point.y).sqrt();
|
|
let theta = point.y.atan2(point.x);
|
|
let z = point.z;
|
|
Vector3::new(r, theta, z)
|
|
}
|
|
|
|
/// Convert cylindrical to Cartesian coordinates.
|
|
pub fn cylindrical_to_cartesian(point: &Vector3<f64>) -> Vector3<f64> {
|
|
let x = point.x * point.y.cos();
|
|
let y = point.x * point.y.sin();
|
|
let z = point.z;
|
|
Vector3::new(x, y, z)
|
|
}
|
|
|
|
/// Convert Cartesian to spherical coordinates.
|
|
pub fn cartesian_to_spherical(point: &Vector3<f64>) -> Vector3<f64> {
|
|
let r = point.norm();
|
|
let theta = (point.z / r).acos();
|
|
let phi = point.y.atan2(point.x);
|
|
Vector3::new(r, theta, phi)
|
|
}
|
|
|
|
/// Convert spherical to Cartesian coordinates.
|
|
pub fn spherical_to_cartesian(point: &Vector3<f64>) -> Vector3<f64> {
|
|
let x = point.x * point.y.sin() * point.z.cos();
|
|
let y = point.x * point.y.sin() * point.z.sin();
|
|
let z = point.x * point.y.cos();
|
|
Vector3::new(x, y, z)
|
|
}
|
|
}
|
|
|
|
/// Memory usage utilities.
|
|
pub struct MemoryUtils;
|
|
|
|
impl MemoryUtils {
|
|
/// Get current memory usage in bytes.
|
|
pub fn current_usage() -> usize {
|
|
// Simplified implementation - would use proper memory tracking
|
|
0
|
|
}
|
|
|
|
/// Get peak memory usage in bytes.
|
|
pub fn peak_usage() -> usize {
|
|
// Simplified implementation
|
|
0
|
|
}
|
|
|
|
/// Format memory size in human-readable format.
|
|
pub fn format_bytes(bytes: usize) -> String {
|
|
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
|
|
let mut size = bytes as f64;
|
|
let mut unit_index = 0;
|
|
|
|
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
|
|
size /= 1024.0;
|
|
unit_index += 1;
|
|
}
|
|
|
|
format!("{:.2} {}", size, UNITS[unit_index])
|
|
}
|
|
|
|
/// Estimate memory requirement for sparse matrix.
|
|
pub fn estimate_sparse_matrix_memory(
|
|
nnz: usize,
|
|
index_size: usize,
|
|
value_size: usize,
|
|
) -> usize {
|
|
nnz * (index_size + value_size) + index_size * 2 // Row pointers
|
|
}
|
|
|
|
/// Estimate memory requirement for dense matrix.
|
|
pub fn estimate_dense_matrix_memory(rows: usize, cols: usize, element_size: usize) -> usize {
|
|
rows * cols * element_size
|
|
}
|
|
}
|
|
|
|
/// Performance benchmarking utilities.
|
|
pub struct BenchmarkUtils;
|
|
|
|
impl BenchmarkUtils {
|
|
/// Measure execution time of a function.
|
|
pub fn time_function<F, R>(f: F) -> (R, Duration)
|
|
where
|
|
F: FnOnce() -> R,
|
|
{
|
|
let start = Instant::now();
|
|
let result = f();
|
|
let duration = start.elapsed();
|
|
(result, duration)
|
|
}
|
|
|
|
/// Run benchmark multiple times and get statistics.
|
|
pub fn benchmark_function<F>(f: F, iterations: usize) -> BenchmarkStats
|
|
where
|
|
F: Fn(),
|
|
{
|
|
let mut times = Vec::with_capacity(iterations);
|
|
|
|
for _ in 0..iterations {
|
|
let ((), duration) = Self::time_function(&f);
|
|
times.push(duration);
|
|
}
|
|
|
|
BenchmarkStats::from_times(times)
|
|
}
|
|
|
|
/// Create a simple progress bar.
|
|
pub fn create_progress_bar(total: usize) -> ProgressBar {
|
|
ProgressBar::new(total)
|
|
}
|
|
}
|
|
|
|
/// Benchmark statistics.
|
|
#[derive(Debug, Clone)]
|
|
pub struct BenchmarkStats {
|
|
pub mean: Duration,
|
|
pub std_dev: Duration,
|
|
pub min: Duration,
|
|
pub max: Duration,
|
|
pub iterations: usize,
|
|
}
|
|
|
|
impl BenchmarkStats {
|
|
/// Create statistics from timing measurements.
|
|
pub fn from_times(times: Vec<Duration>) -> Self {
|
|
if times.is_empty() {
|
|
return Self {
|
|
mean: Duration::new(0, 0),
|
|
std_dev: Duration::new(0, 0),
|
|
min: Duration::new(0, 0),
|
|
max: Duration::new(0, 0),
|
|
iterations: 0,
|
|
};
|
|
}
|
|
|
|
let iterations = times.len();
|
|
let total_nanos: u64 = times.iter().map(|d| d.as_nanos() as u64).sum();
|
|
let mean_nanos = total_nanos / iterations as u64;
|
|
let mean = Duration::from_nanos(mean_nanos);
|
|
|
|
let min = times.iter().min().copied().unwrap_or_default();
|
|
let max = times.iter().max().copied().unwrap_or_default();
|
|
|
|
// Calculate standard deviation
|
|
let variance: f64 = times
|
|
.iter()
|
|
.map(|d| {
|
|
let diff = d.as_nanos() as f64 - mean_nanos as f64;
|
|
diff * diff
|
|
})
|
|
.sum::<f64>()
|
|
/ iterations as f64;
|
|
|
|
let std_dev = Duration::from_nanos(variance.sqrt() as u64);
|
|
|
|
Self {
|
|
mean,
|
|
std_dev,
|
|
min,
|
|
max,
|
|
iterations,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for BenchmarkStats {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
writeln!(f, "Benchmark Statistics ({} iterations):", self.iterations)?;
|
|
writeln!(f, " Mean: {:.3} ms", self.mean.as_secs_f64() * 1000.0)?;
|
|
writeln!(
|
|
f,
|
|
" Std Dev: {:.3} ms",
|
|
self.std_dev.as_secs_f64() * 1000.0
|
|
)?;
|
|
writeln!(f, " Min: {:.3} ms", self.min.as_secs_f64() * 1000.0)?;
|
|
writeln!(f, " Max: {:.3} ms", self.max.as_secs_f64() * 1000.0)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Simple progress bar for console output.
|
|
#[derive(Debug)]
|
|
pub struct ProgressBar {
|
|
total: usize,
|
|
current: usize,
|
|
width: usize,
|
|
start_time: Instant,
|
|
}
|
|
|
|
impl ProgressBar {
|
|
/// Create a new progress bar.
|
|
pub fn new(total: usize) -> Self {
|
|
Self {
|
|
total,
|
|
current: 0,
|
|
width: 50,
|
|
start_time: Instant::now(),
|
|
}
|
|
}
|
|
|
|
/// Set the width of the progress bar.
|
|
pub fn set_width(&mut self, width: usize) {
|
|
self.width = width;
|
|
}
|
|
|
|
/// Update progress and display.
|
|
pub fn update(&mut self, current: usize) {
|
|
self.current = current;
|
|
self.display();
|
|
}
|
|
|
|
/// Increment progress by one.
|
|
pub fn increment(&mut self) {
|
|
self.current += 1;
|
|
self.display();
|
|
}
|
|
|
|
/// Display the progress bar.
|
|
fn display(&self) {
|
|
let percentage = if self.total > 0 {
|
|
(self.current as f64 / self.total as f64 * 100.0).min(100.0)
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
let filled = (percentage / 100.0 * self.width as f64) as usize;
|
|
let empty = self.width - filled;
|
|
|
|
let elapsed = self.start_time.elapsed();
|
|
let rate = if elapsed.as_secs() > 0 {
|
|
self.current as f64 / elapsed.as_secs_f64()
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
let eta = if rate > 0.0 && self.current < self.total {
|
|
Duration::from_secs_f64((self.total - self.current) as f64 / rate)
|
|
} else {
|
|
Duration::new(0, 0)
|
|
};
|
|
|
|
print!(
|
|
"\r[{}{}] {:.1}% ({}/{}) Rate: {:.1}/s ETA: {:.0}s",
|
|
"█".repeat(filled),
|
|
"░".repeat(empty),
|
|
percentage,
|
|
self.current,
|
|
self.total,
|
|
rate,
|
|
eta.as_secs_f64()
|
|
);
|
|
|
|
use std::io::{self, Write};
|
|
io::stdout().flush().unwrap();
|
|
|
|
if self.current >= self.total {
|
|
println!(); // New line when complete
|
|
}
|
|
}
|
|
|
|
/// Mark as complete.
|
|
pub fn finish(&mut self) {
|
|
self.current = self.total;
|
|
self.display();
|
|
}
|
|
}
|
|
|
|
/// String utilities for FEA.
|
|
pub struct StringUtils;
|
|
|
|
impl StringUtils {
|
|
/// Convert scientific notation to readable format.
|
|
pub fn format_scientific(value: f64, precision: usize) -> String {
|
|
if value.abs() < 1e-15 {
|
|
"0".to_string()
|
|
} else if value.abs() >= 1e6 || value.abs() < 1e-3 {
|
|
format!("{value:.precision$e}")
|
|
} else {
|
|
format!("{value:.precision$}")
|
|
}
|
|
}
|
|
|
|
/// Format duration in human-readable format.
|
|
pub fn format_duration(duration: Duration) -> String {
|
|
let total_secs = duration.as_secs_f64();
|
|
|
|
if total_secs < 1.0 {
|
|
format!("{:.1} ms", total_secs * 1000.0)
|
|
} else if total_secs < 60.0 {
|
|
format!("{total_secs:.2} s")
|
|
} else if total_secs < 3600.0 {
|
|
let minutes = (total_secs / 60.0) as u32;
|
|
let seconds = total_secs % 60.0;
|
|
format!("{minutes}m {seconds:.1}s")
|
|
} else {
|
|
let hours = (total_secs / 3600.0) as u32;
|
|
let minutes = ((total_secs % 3600.0) / 60.0) as u32;
|
|
format!("{hours}h {minutes}m")
|
|
}
|
|
}
|
|
|
|
/// Generate unique identifier.
|
|
pub fn generate_id() -> String {
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
let timestamp = SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos();
|
|
format!("fea_{timestamp}")
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_coordinate_transforms() {
|
|
let cartesian = Vector3::new(1.0, 1.0, 1.0);
|
|
let cylindrical = CoordinateTransforms::cartesian_to_cylindrical(&cartesian);
|
|
let back_to_cartesian = CoordinateTransforms::cylindrical_to_cartesian(&cylindrical);
|
|
|
|
assert!((cartesian - back_to_cartesian).norm() < 1e-10);
|
|
}
|
|
|
|
#[test]
|
|
fn test_memory_utils() {
|
|
let formatted = MemoryUtils::format_bytes(1024 * 1024);
|
|
assert_eq!(formatted, "1.00 MB");
|
|
|
|
let formatted = MemoryUtils::format_bytes(1536);
|
|
assert_eq!(formatted, "1.50 KB");
|
|
}
|
|
|
|
#[test]
|
|
fn test_benchmark_stats() {
|
|
let times = vec![
|
|
Duration::from_millis(10),
|
|
Duration::from_millis(12),
|
|
Duration::from_millis(11),
|
|
Duration::from_millis(13),
|
|
Duration::from_millis(9),
|
|
];
|
|
|
|
let stats = BenchmarkStats::from_times(times);
|
|
assert_eq!(stats.iterations, 5);
|
|
assert!(stats.mean.as_millis() > 0);
|
|
assert!(stats.min <= stats.mean);
|
|
assert!(stats.max >= stats.mean);
|
|
}
|
|
|
|
#[test]
|
|
fn test_progress_bar() {
|
|
let mut progress = ProgressBar::new(100);
|
|
progress.set_width(10);
|
|
progress.update(50);
|
|
assert_eq!(progress.current, 50);
|
|
assert_eq!(progress.total, 100);
|
|
}
|
|
|
|
#[test]
|
|
fn test_string_utils() {
|
|
let scientific = StringUtils::format_scientific(1.23e-6, 2);
|
|
assert!(scientific.contains("e"));
|
|
|
|
let duration = StringUtils::format_duration(Duration::from_secs(65));
|
|
assert!(duration.contains("m"));
|
|
|
|
let id = StringUtils::generate_id();
|
|
assert!(id.starts_with("fea_"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_benchmark_function() {
|
|
let stats = BenchmarkUtils::benchmark_function(
|
|
|| {
|
|
std::thread::sleep(Duration::from_millis(1));
|
|
},
|
|
3,
|
|
);
|
|
|
|
assert_eq!(stats.iterations, 3);
|
|
assert!(stats.mean.as_millis() >= 1);
|
|
}
|
|
}
|