589 lines
15 KiB
Rust
589 lines
15 KiB
Rust
//! Inter-process communication types for Tauri frontend/backend
|
|
//!
|
|
//! This module defines all message types used for communication between
|
|
//! the Tauri frontend and the `RustyTorch`++ backend.
|
|
//!
|
|
//! # Example
|
|
//!
|
|
//! ```rust
|
|
//! use rtx_hemodynamics_shared::geometry::VesselGeometry;
|
|
//! use rtx_hemodynamics_shared::physics::SimulationConfig;
|
|
//! use rtx_hemodynamics_shared::ipc::{IpcRequest, IpcResponse};
|
|
//!
|
|
//! // Create initialization request
|
|
//! let geometry = VesselGeometry::straight(0.1, 0.005).unwrap();
|
|
//! let config = SimulationConfig::default();
|
|
//! let request = IpcRequest::initialize(geometry, config);
|
|
//!
|
|
//! // Serialize for IPC
|
|
//! let json = serde_json::to_string(&request).unwrap();
|
|
//!
|
|
//! // Create success response
|
|
//! let response = IpcResponse::success(serde_json::json!({"status": "initialized"}));
|
|
//! ```
|
|
|
|
use crate::fields::{FieldQuery, FieldResponse};
|
|
use crate::geometry::{GeometryModification, VesselGeometry};
|
|
use crate::physics::SimulationConfig;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// IPC request types from frontend to backend
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(tag = "type", content = "payload")]
|
|
pub enum IpcRequest {
|
|
/// Initialize simulation with geometry and config
|
|
Initialize {
|
|
/// Vessel geometry
|
|
geometry: VesselGeometry,
|
|
/// Simulation configuration
|
|
config: SimulationConfig,
|
|
},
|
|
|
|
/// Query field values at specific points
|
|
QueryFields(FieldQuery),
|
|
|
|
/// Modify vessel geometry
|
|
ModifyGeometry(GeometryModification),
|
|
|
|
/// Start training/optimization
|
|
StartTraining {
|
|
/// Number of training epochs
|
|
epochs: usize,
|
|
},
|
|
|
|
/// Stop training
|
|
StopTraining,
|
|
|
|
/// Get current simulation state
|
|
GetState,
|
|
|
|
/// Get performance metrics
|
|
GetMetrics,
|
|
|
|
/// Reset simulation to initial state
|
|
Reset,
|
|
|
|
/// Export simulation results
|
|
Export {
|
|
/// Output format (json, vtk, csv)
|
|
format: ExportFormat,
|
|
/// Output path
|
|
path: String,
|
|
},
|
|
}
|
|
|
|
impl IpcRequest {
|
|
/// Creates an initialize request
|
|
#[must_use]
|
|
pub fn initialize(geometry: VesselGeometry, config: SimulationConfig) -> Self {
|
|
Self::Initialize { geometry, config }
|
|
}
|
|
|
|
/// Creates a query fields request
|
|
#[must_use]
|
|
pub fn query_fields(query: FieldQuery) -> Self {
|
|
Self::QueryFields(query)
|
|
}
|
|
|
|
/// Creates a modify geometry request
|
|
#[must_use]
|
|
pub fn modify_geometry(modification: GeometryModification) -> Self {
|
|
Self::ModifyGeometry(modification)
|
|
}
|
|
|
|
/// Creates a start training request
|
|
#[must_use]
|
|
pub const fn start_training(epochs: usize) -> Self {
|
|
Self::StartTraining { epochs }
|
|
}
|
|
|
|
/// Creates a get state request
|
|
#[must_use]
|
|
pub const fn get_state() -> Self {
|
|
Self::GetState
|
|
}
|
|
|
|
/// Creates a get metrics request
|
|
#[must_use]
|
|
pub const fn get_metrics() -> Self {
|
|
Self::GetMetrics
|
|
}
|
|
|
|
/// Creates a reset request
|
|
#[must_use]
|
|
pub const fn reset() -> Self {
|
|
Self::Reset
|
|
}
|
|
|
|
/// Creates an export request
|
|
#[must_use]
|
|
pub fn export(format: ExportFormat, path: String) -> Self {
|
|
Self::Export { format, path }
|
|
}
|
|
}
|
|
|
|
/// Export format options
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum ExportFormat {
|
|
/// JSON format
|
|
Json,
|
|
/// VTK format for visualization
|
|
Vtk,
|
|
/// CSV format for data analysis
|
|
Csv,
|
|
}
|
|
|
|
/// IPC response from backend to frontend
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct IpcResponse {
|
|
/// Whether the request succeeded
|
|
success: bool,
|
|
/// Response data (if success)
|
|
data: Option<serde_json::Value>,
|
|
/// Error message (if failure)
|
|
error: Option<String>,
|
|
/// Response timestamp
|
|
timestamp_ms: u64,
|
|
}
|
|
|
|
impl IpcResponse {
|
|
/// Creates a success response with data
|
|
#[must_use]
|
|
pub fn success(data: serde_json::Value) -> Self {
|
|
Self {
|
|
success: true,
|
|
data: Some(data),
|
|
error: None,
|
|
timestamp_ms: current_timestamp_ms(),
|
|
}
|
|
}
|
|
|
|
/// Creates an error response
|
|
#[must_use]
|
|
pub fn error(message: impl Into<String>) -> Self {
|
|
Self {
|
|
success: false,
|
|
data: None,
|
|
error: Some(message.into()),
|
|
timestamp_ms: current_timestamp_ms(),
|
|
}
|
|
}
|
|
|
|
/// Creates a success response without data
|
|
#[must_use]
|
|
pub fn ok() -> Self {
|
|
Self {
|
|
success: true,
|
|
data: None,
|
|
error: None,
|
|
timestamp_ms: current_timestamp_ms(),
|
|
}
|
|
}
|
|
|
|
/// Creates a response with field data
|
|
#[must_use]
|
|
pub fn with_fields(fields: FieldResponse) -> Self {
|
|
Self::success(serde_json::to_value(fields).unwrap_or_default())
|
|
}
|
|
|
|
/// Creates a response with state data
|
|
#[must_use]
|
|
pub fn with_state(state: SimulationState) -> Self {
|
|
Self::success(serde_json::to_value(state).unwrap_or_default())
|
|
}
|
|
|
|
/// Creates a response with metrics data
|
|
#[must_use]
|
|
pub fn with_metrics(metrics: PerformanceMetrics) -> Self {
|
|
Self::success(serde_json::to_value(metrics).unwrap_or_default())
|
|
}
|
|
|
|
/// Returns whether the response indicates success
|
|
#[must_use]
|
|
pub const fn is_success(&self) -> bool {
|
|
self.success
|
|
}
|
|
|
|
/// Returns the response data
|
|
#[must_use]
|
|
pub const fn data(&self) -> &Option<serde_json::Value> {
|
|
&self.data
|
|
}
|
|
|
|
/// Returns the error message if present
|
|
#[must_use]
|
|
pub fn error_message(&self) -> Option<&str> {
|
|
self.error.as_deref()
|
|
}
|
|
|
|
/// Returns the timestamp in milliseconds
|
|
#[must_use]
|
|
pub const fn timestamp_ms(&self) -> u64 {
|
|
self.timestamp_ms
|
|
}
|
|
}
|
|
|
|
/// Current simulation state
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SimulationState {
|
|
/// Whether simulation is initialized
|
|
initialized: bool,
|
|
/// Whether training is in progress
|
|
training: bool,
|
|
/// Current training epoch (if training)
|
|
current_epoch: usize,
|
|
/// Total training epochs
|
|
total_epochs: usize,
|
|
/// Current loss value
|
|
loss: f64,
|
|
/// Best loss achieved
|
|
best_loss: f64,
|
|
/// Whether the model is converged
|
|
converged: bool,
|
|
}
|
|
|
|
impl SimulationState {
|
|
/// Creates a new simulation state
|
|
#[must_use]
|
|
pub fn new() -> Self {
|
|
Self {
|
|
initialized: false,
|
|
training: false,
|
|
current_epoch: 0,
|
|
total_epochs: 0,
|
|
loss: f64::INFINITY,
|
|
best_loss: f64::INFINITY,
|
|
converged: false,
|
|
}
|
|
}
|
|
|
|
/// Returns whether simulation is initialized
|
|
#[must_use]
|
|
pub const fn is_initialized(&self) -> bool {
|
|
self.initialized
|
|
}
|
|
|
|
/// Returns whether training is in progress
|
|
#[must_use]
|
|
pub const fn is_training(&self) -> bool {
|
|
self.training
|
|
}
|
|
|
|
/// Returns the current epoch
|
|
#[must_use]
|
|
pub const fn current_epoch(&self) -> usize {
|
|
self.current_epoch
|
|
}
|
|
|
|
/// Returns the total epochs
|
|
#[must_use]
|
|
pub const fn total_epochs(&self) -> usize {
|
|
self.total_epochs
|
|
}
|
|
|
|
/// Returns the current loss
|
|
#[must_use]
|
|
pub const fn loss(&self) -> f64 {
|
|
self.loss
|
|
}
|
|
|
|
/// Returns the best loss
|
|
#[must_use]
|
|
pub const fn best_loss(&self) -> f64 {
|
|
self.best_loss
|
|
}
|
|
|
|
/// Returns whether converged
|
|
#[must_use]
|
|
pub const fn is_converged(&self) -> bool {
|
|
self.converged
|
|
}
|
|
|
|
/// Returns training progress as percentage
|
|
#[must_use]
|
|
pub fn progress_percent(&self) -> f64 {
|
|
if self.total_epochs == 0 {
|
|
0.0
|
|
} else {
|
|
100.0 * self.current_epoch as f64 / self.total_epochs as f64
|
|
}
|
|
}
|
|
|
|
/// Sets the initialized flag
|
|
#[must_use]
|
|
pub const fn with_initialized(mut self, initialized: bool) -> Self {
|
|
self.initialized = initialized;
|
|
self
|
|
}
|
|
|
|
/// Sets the training flag
|
|
#[must_use]
|
|
pub const fn with_training(mut self, training: bool) -> Self {
|
|
self.training = training;
|
|
self
|
|
}
|
|
|
|
/// Sets the current epoch
|
|
#[must_use]
|
|
pub const fn with_epoch(mut self, current: usize, total: usize) -> Self {
|
|
self.current_epoch = current;
|
|
self.total_epochs = total;
|
|
self
|
|
}
|
|
|
|
/// Sets the loss values
|
|
#[must_use]
|
|
pub const fn with_loss(mut self, loss: f64, best: f64) -> Self {
|
|
self.loss = loss;
|
|
self.best_loss = best;
|
|
self
|
|
}
|
|
|
|
/// Sets the converged flag
|
|
#[must_use]
|
|
pub const fn with_converged(mut self, converged: bool) -> Self {
|
|
self.converged = converged;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Default for SimulationState {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
/// Performance metrics for monitoring
|
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
|
pub struct PerformanceMetrics {
|
|
/// Inference time in milliseconds
|
|
inference_time_ms: f64,
|
|
/// GPU utilization (0.0 to 1.0)
|
|
gpu_utilization: f64,
|
|
/// Memory usage in megabytes
|
|
memory_usage_mb: usize,
|
|
/// Training throughput (samples/second)
|
|
throughput: f64,
|
|
/// Average loss over recent iterations
|
|
avg_loss: f64,
|
|
}
|
|
|
|
impl PerformanceMetrics {
|
|
/// Creates new performance metrics
|
|
#[must_use]
|
|
pub fn new(inference_time_ms: f64, gpu_utilization: f64, memory_usage_mb: usize) -> Self {
|
|
Self {
|
|
inference_time_ms,
|
|
gpu_utilization,
|
|
memory_usage_mb,
|
|
throughput: 0.0,
|
|
avg_loss: 0.0,
|
|
}
|
|
}
|
|
|
|
/// Creates metrics with full data
|
|
#[must_use]
|
|
pub fn full(
|
|
inference_time_ms: f64,
|
|
gpu_utilization: f64,
|
|
memory_usage_mb: usize,
|
|
throughput: f64,
|
|
avg_loss: f64,
|
|
) -> Self {
|
|
Self {
|
|
inference_time_ms,
|
|
gpu_utilization,
|
|
memory_usage_mb,
|
|
throughput,
|
|
avg_loss,
|
|
}
|
|
}
|
|
|
|
/// Returns inference time in milliseconds
|
|
#[must_use]
|
|
pub const fn inference_time_ms(&self) -> f64 {
|
|
self.inference_time_ms
|
|
}
|
|
|
|
/// Returns GPU utilization (0.0 to 1.0)
|
|
#[must_use]
|
|
pub const fn gpu_utilization(&self) -> f64 {
|
|
self.gpu_utilization
|
|
}
|
|
|
|
/// Returns memory usage in megabytes
|
|
#[must_use]
|
|
pub const fn memory_usage_mb(&self) -> usize {
|
|
self.memory_usage_mb
|
|
}
|
|
|
|
/// Returns training throughput
|
|
#[must_use]
|
|
pub const fn throughput(&self) -> f64 {
|
|
self.throughput
|
|
}
|
|
|
|
/// Returns average loss
|
|
#[must_use]
|
|
pub const fn avg_loss(&self) -> f64 {
|
|
self.avg_loss
|
|
}
|
|
|
|
/// Estimates FPS from inference time
|
|
#[must_use]
|
|
pub fn estimated_fps(&self) -> f64 {
|
|
if self.inference_time_ms <= 0.0 {
|
|
0.0
|
|
} else {
|
|
1000.0 / self.inference_time_ms
|
|
}
|
|
}
|
|
|
|
/// Returns whether performance meets real-time requirements (<33ms for 30fps)
|
|
#[must_use]
|
|
pub fn is_realtime(&self) -> bool {
|
|
self.inference_time_ms < 33.0
|
|
}
|
|
}
|
|
|
|
impl Default for PerformanceMetrics {
|
|
fn default() -> Self {
|
|
Self::new(0.0, 0.0, 0)
|
|
}
|
|
}
|
|
|
|
/// Training progress update message
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct TrainingProgress {
|
|
/// Current epoch
|
|
pub epoch: usize,
|
|
/// Total epochs
|
|
pub total_epochs: usize,
|
|
/// Current loss
|
|
pub loss: f64,
|
|
/// Physics loss component
|
|
pub physics_loss: f64,
|
|
/// Data loss component
|
|
pub data_loss: f64,
|
|
/// Learning rate
|
|
pub learning_rate: f64,
|
|
/// Elapsed time in seconds
|
|
pub elapsed_secs: f64,
|
|
}
|
|
|
|
impl TrainingProgress {
|
|
/// Creates a new training progress update
|
|
#[must_use]
|
|
pub fn new(
|
|
epoch: usize,
|
|
total_epochs: usize,
|
|
loss: f64,
|
|
physics_loss: f64,
|
|
data_loss: f64,
|
|
learning_rate: f64,
|
|
elapsed_secs: f64,
|
|
) -> Self {
|
|
Self {
|
|
epoch,
|
|
total_epochs,
|
|
loss,
|
|
physics_loss,
|
|
data_loss,
|
|
learning_rate,
|
|
elapsed_secs,
|
|
}
|
|
}
|
|
|
|
/// Returns progress as percentage
|
|
#[must_use]
|
|
pub fn progress_percent(&self) -> f64 {
|
|
if self.total_epochs == 0 {
|
|
0.0
|
|
} else {
|
|
100.0 * self.epoch as f64 / self.total_epochs as f64
|
|
}
|
|
}
|
|
|
|
/// Estimates remaining time in seconds
|
|
#[must_use]
|
|
pub fn estimated_remaining_secs(&self) -> f64 {
|
|
if self.epoch == 0 {
|
|
return 0.0;
|
|
}
|
|
let time_per_epoch = self.elapsed_secs / self.epoch as f64;
|
|
time_per_epoch * (self.total_epochs - self.epoch) as f64
|
|
}
|
|
}
|
|
|
|
/// Helper function to get current timestamp in milliseconds
|
|
fn current_timestamp_ms() -> u64 {
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.map(|d| d.as_millis() as u64)
|
|
.unwrap_or(0)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::geometry::Point2D;
|
|
|
|
#[test]
|
|
fn test_ipc_request_serialization() {
|
|
let geometry = VesselGeometry::straight(0.1, 0.005).unwrap();
|
|
let config = SimulationConfig::default();
|
|
let request = IpcRequest::initialize(geometry, config);
|
|
|
|
let json = serde_json::to_string(&request).unwrap();
|
|
let deserialized: IpcRequest = serde_json::from_str(&json).unwrap();
|
|
|
|
assert!(matches!(deserialized, IpcRequest::Initialize { .. }));
|
|
}
|
|
|
|
#[test]
|
|
fn test_ipc_response_success() {
|
|
let response = IpcResponse::success(serde_json::json!({"key": "value"}));
|
|
assert!(response.is_success());
|
|
assert!(response.data().is_some());
|
|
assert!(response.error_message().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_ipc_response_error() {
|
|
let response = IpcResponse::error("test error");
|
|
assert!(!response.is_success());
|
|
assert!(response.data().is_none());
|
|
assert_eq!(response.error_message(), Some("test error"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_simulation_state_progress() {
|
|
let state = SimulationState::new()
|
|
.with_initialized(true)
|
|
.with_training(true)
|
|
.with_epoch(50, 100);
|
|
|
|
assert!(state.is_initialized());
|
|
assert!(state.is_training());
|
|
assert!((state.progress_percent() - 50.0).abs() < f64::EPSILON);
|
|
}
|
|
|
|
#[test]
|
|
fn test_performance_metrics_fps() {
|
|
let metrics = PerformanceMetrics::new(20.0, 0.8, 1024);
|
|
assert!((metrics.estimated_fps() - 50.0).abs() < f64::EPSILON);
|
|
assert!(metrics.is_realtime());
|
|
}
|
|
|
|
#[test]
|
|
fn test_training_progress_remaining_time() {
|
|
let progress = TrainingProgress::new(50, 100, 0.01, 0.005, 0.005, 0.001, 100.0);
|
|
|
|
// 100 seconds for 50 epochs = 2 sec/epoch
|
|
// 50 epochs remaining = 100 seconds
|
|
assert!((progress.estimated_remaining_secs() - 100.0).abs() < f64::EPSILON);
|
|
}
|
|
}
|