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