//! Tauri IPC command handlers //! //! This module defines all the Tauri commands that can be invoked from the //! frontend. Each command maps to a HemodynamicsService or MreService method. use serde::{Deserialize, Serialize}; use tauri::State; use rtx_hemodynamics_shared::geometry::{GeometryModification, Point2D, VesselGeometry}; use rtx_hemodynamics_shared::ipc::{PerformanceMetrics, SimulationState}; use rtx_hemodynamics_server::ServerError; // MRE types use mre_shared::ipc::{LossRecord, MreSnapshot, MreStatus, PhantomType}; // Bioheat types use bioheat_shared::{ BioheatLossRecord, BioheatSnapshot, BioheatStatus, ProbeGeometry, SimulationParams, SliceAxis, SliceData, }; // SlideScope types use slidescope_shared::{ JobStatus, NmfConfig, NmfResult, SlideFilter, SlideMetadata, SlidescopeStatus, TileRequest, TileResponse, }; use crate::state::AppState; /// Vessel parameters for initialization (frontend-friendly format) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VesselParams { /// Length in meters pub length: f64, /// Radius in meters pub radius: f64, /// Optional stenosis at center with given diameter ratio (0-1) pub stenosis_ratio: Option, } /// Query result for field values #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FieldQueryResult { /// Query points pub points: Vec, /// U velocity component at each point pub u: Vec, /// V velocity component at each point pub v: Vec, /// Pressure at each point pub p: Vec, /// Wall shear stress at boundary points (optional) pub wss: Option>, /// Inference time in milliseconds pub inference_time_ms: f64, } /// Grid query parameters #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GridQueryParams { /// Number of grid points in X direction pub nx: usize, /// Number of grid points in Y direction pub ny: usize, /// Time value for query pub time: f64, } /// Grid query result with structured data #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GridQueryResponse { /// Grid dimensions (nx, ny) pub dimensions: (usize, usize), /// Grid spacing pub dx: f64, /// Grid spacing y pub dy: f64, /// X minimum coordinate pub x_min: f64, /// Y minimum coordinate pub y_min: f64, /// U velocity field (row-major order) pub u_field: Vec, /// V velocity field (row-major order) pub v_field: Vec, /// Pressure field (row-major order) pub p_field: Vec, /// Interior mask (true if inside vessel) pub mask: Vec, /// Inference time in milliseconds pub inference_time_ms: f64, } /// Status response for frontend #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StatusResponse { /// Whether simulation is initialized pub initialized: bool, /// Whether model is trained pub trained: bool, /// Total inference count pub inference_count: u64, /// Average inference time in ms pub avg_inference_time_ms: f64, /// Uptime in seconds pub uptime_secs: u64, } /// Converts ServerError to a string for Tauri fn error_to_string(e: ServerError) -> String { e.to_string() } /// Initializes the simulation with given vessel parameters #[tauri::command] pub async fn initialize( state: State<'_, AppState>, params: VesselParams, ) -> Result { let service = state.service(); let guard = service.read().await; // Create vessel geometry let mut geometry = VesselGeometry::straight(params.length, params.radius).map_err(|e| e.to_string())?; // Add stenosis if specified if let Some(ratio) = params.stenosis_ratio && ratio > 0.0 && ratio < 1.0 { let stenosis = rtx_hemodynamics_shared::geometry::StenosisParams::new( ratio, params.length * 0.2, // 20% of vessel length params.length * 0.5, // Center of vessel ) .map_err(|e| e.to_string())?; geometry = geometry.with_stenosis(stenosis); } // Drop read guard and acquire write guard drop(guard); let write_guard = service.write().await; write_guard .initialize(geometry) .await .map_err(error_to_string)?; let status = write_guard.status().await.map_err(error_to_string)?; Ok(StatusResponse { initialized: status.initialized, trained: status.trained, inference_count: status.inference_count, avg_inference_time_ms: status.avg_inference_time_ms, uptime_secs: status.uptime_secs, }) } /// Resets the simulation #[tauri::command] pub async fn reset(state: State<'_, AppState>) -> Result<(), String> { let service = state.service(); let guard = service.write().await; guard.reset().await.map_err(error_to_string) } /// Gets the current simulation status #[tauri::command] pub async fn get_status(state: State<'_, AppState>) -> Result { let service = state.service(); let guard = service.read().await; let status = guard.status().await.map_err(error_to_string)?; Ok(StatusResponse { initialized: status.initialized, trained: status.trained, inference_count: status.inference_count, avg_inference_time_ms: status.avg_inference_time_ms, uptime_secs: status.uptime_secs, }) } /// Queries field values at specific points #[tauri::command] pub async fn query_fields( state: State<'_, AppState>, points: Vec, time: f64, ) -> Result { let service = state.service(); let guard = service.read().await; let start = std::time::Instant::now(); let response = guard .query_fields(&points, time) .await .map_err(error_to_string)?; let inference_time_ms = start.elapsed().as_secs_f64() * 1000.0; // Extract velocity and pressure data let velocity = response.velocity(); let pressure = response.pressure(); Ok(FieldQueryResult { points, u: velocity.u().to_vec(), v: velocity.v().to_vec(), p: pressure.values().to_vec(), wss: None, inference_time_ms, }) } /// Queries field values on a regular grid #[tauri::command] pub async fn query_grid( state: State<'_, AppState>, params: GridQueryParams, ) -> Result { let service = state.service(); let guard = service.read().await; let start = std::time::Instant::now(); let result = guard .query_grid(params.nx, params.ny, params.time) .await .map_err(error_to_string)?; let inference_time_ms = start.elapsed().as_secs_f64() * 1000.0; Ok(GridQueryResponse { dimensions: (result.nx, result.ny), dx: result.dx, dy: result.dy, x_min: result.x_min, y_min: result.y_min, u_field: result.u, v_field: result.v, p_field: result.p, mask: result.mask, inference_time_ms, }) } /// Modifies the vessel geometry #[tauri::command] pub async fn modify_geometry( state: State<'_, AppState>, modification: GeometryModification, ) -> Result { let service = state.service(); let guard = service.read().await; guard .modify_geometry(modification) .await .map_err(error_to_string)?; let status = guard.status().await.map_err(error_to_string)?; Ok(StatusResponse { initialized: status.initialized, trained: status.trained, inference_count: status.inference_count, avg_inference_time_ms: status.avg_inference_time_ms, uptime_secs: status.uptime_secs, }) } /// Gets performance metrics #[tauri::command] pub async fn get_metrics(state: State<'_, AppState>) -> Result { let service = state.service(); let guard = service.read().await; guard.metrics().await.map_err(error_to_string) } /// Gets the current simulation state #[tauri::command] pub async fn get_simulation_state( state: State<'_, AppState>, ) -> Result { let service = state.service(); let guard = service.read().await; guard.simulation_state().await.map_err(error_to_string) } /// Samples interior points from the current geometry #[tauri::command] pub async fn sample_interior( state: State<'_, AppState>, num_points: usize, ) -> Result, String> { let service = state.service(); let guard = service.read().await; guard .sample_interior(num_points) .await .map_err(error_to_string) } /// Samples boundary points from the current geometry #[tauri::command] pub async fn sample_boundary( state: State<'_, AppState>, num_points: usize, ) -> Result, String> { let service = state.service(); let guard = service.read().await; guard .sample_boundary(num_points) .await .map_err(error_to_string) } // ============================================================================= // MRE ELASTOGRAPHY COMMANDS // ============================================================================= /// Initialize the MRE solver with a phantom configuration #[tauri::command] pub async fn mre_initialize_phantom( state: State<'_, AppState>, phantom_type: String, ) -> Result { let mre_service = state.mre_service(); // Parse phantom type from string let phantom = match phantom_type.as_str() { "SingleTumor" => PhantomType::SingleTumor, "MultipleLesions" => PhantomType::MultipleLesions, "Layered" => PhantomType::Layered, _ => PhantomType::SingleTumor, }; mre_service .initialize_phantom(phantom) .await .map_err(|e| e.to_string())?; mre_service.status().await.map_err(|e| e.to_string()) } /// Run a single training step #[tauri::command] pub async fn mre_step(state: State<'_, AppState>) -> Result { let mre_service = state.mre_service(); mre_service.step().await.map_err(|e| e.to_string()) } /// Run multiple training steps #[tauri::command] pub async fn mre_train( state: State<'_, AppState>, num_steps: usize, ) -> Result, String> { let mre_service = state.mre_service(); mre_service.train(num_steps).await.map_err(|e| e.to_string()) } /// Get a visualization snapshot of current solver state #[tauri::command] pub async fn mre_snapshot(state: State<'_, AppState>) -> Result { let mre_service = state.mre_service(); mre_service.snapshot().await.map_err(|e| e.to_string()) } /// Reset the MRE solver #[tauri::command] pub async fn mre_reset(state: State<'_, AppState>) -> Result<(), String> { let mre_service = state.mre_service(); mre_service.reset().await.map_err(|e| e.to_string()) } /// Get current MRE solver status #[tauri::command] pub async fn mre_status(state: State<'_, AppState>) -> Result { let mre_service = state.mre_service(); mre_service.status().await.map_err(|e| e.to_string()) } // ============================================================================= // SLIDESCOPE PATHOLOGY COMMANDS // ============================================================================= /// Initialize the SlideScope service with a workspace directory #[tauri::command] pub async fn slidescope_initialize( state: State<'_, AppState>, workspace: String, ) -> Result { let slidescope_service = state.slidescope_service(); slidescope_service .initialize(&workspace) .await .map_err(|e| e.to_string())?; slidescope_service.status().await.map_err(|e| e.to_string()) } /// Import a slide from a file path #[tauri::command] pub async fn slidescope_import_slide( state: State<'_, AppState>, path: String, ) -> Result { let slidescope_service = state.slidescope_service(); slidescope_service .import_slide(&path) .await .map_err(|e| e.to_string()) } /// List all slides with optional filter #[tauri::command] pub async fn slidescope_list_slides( state: State<'_, AppState>, filter: Option, ) -> Result, String> { let slidescope_service = state.slidescope_service(); slidescope_service .list_slides(filter) .await .map_err(|e| e.to_string()) } /// Get a slide by ID #[tauri::command] pub async fn slidescope_get_slide( state: State<'_, AppState>, slide_id: String, ) -> Result { let slidescope_service = state.slidescope_service(); slidescope_service .get_slide(&slide_id) .await .map_err(|e| e.to_string()) } /// Get a tile from a slide #[tauri::command] pub async fn slidescope_get_tile( state: State<'_, AppState>, request: TileRequest, ) -> Result { let slidescope_service = state.slidescope_service(); slidescope_service .get_tile(request) .await .map_err(|e| e.to_string()) } /// Queue NMF processing for a slide #[tauri::command] pub async fn slidescope_queue_processing( state: State<'_, AppState>, slide_id: String, config: NmfConfig, ) -> Result { let slidescope_service = state.slidescope_service(); slidescope_service .queue_processing(&slide_id, config) .await .map_err(|e| e.to_string()) } /// Get job status by ID #[tauri::command] pub async fn slidescope_job_status( state: State<'_, AppState>, job_id: String, ) -> Result { let slidescope_service = state.slidescope_service(); slidescope_service .job_status(&job_id) .await .map_err(|e| e.to_string()) } /// Get NMF result for a slide #[tauri::command] pub async fn slidescope_get_result( state: State<'_, AppState>, slide_id: String, ) -> Result { let slidescope_service = state.slidescope_service(); slidescope_service .get_result(&slide_id) .await .map_err(|e| e.to_string()) } /// Get current SlideScope service status #[tauri::command] pub async fn slidescope_status(state: State<'_, AppState>) -> Result { let slidescope_service = state.slidescope_service(); slidescope_service.status().await.map_err(|e| e.to_string()) } /// Reset the SlideScope service #[tauri::command] pub async fn slidescope_reset(state: State<'_, AppState>) -> Result<(), String> { let slidescope_service = state.slidescope_service(); slidescope_service.reset().await.map_err(|e| e.to_string()) } /// Delete a slide by ID #[tauri::command] pub async fn slidescope_delete_slide( state: State<'_, AppState>, slide_id: String, ) -> Result<(), String> { let slidescope_service = state.slidescope_service(); slidescope_service .delete_slide(&slide_id) .await .map_err(|e| e.to_string()) } // ============================================================================= // COMPUTE BACKEND DETECTION // ============================================================================= /// Information about the active compute backend #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ComputeBackendInfo { /// Primary backend in use: "CUDA", "METAL", or "CPU" pub backend: String, /// All available backends on this system pub available: Vec, } /// Get the active compute backend based on runtime device detection #[tauri::command] pub async fn get_compute_backend() -> Result { use rtx_tensor::Device; let devices = Device::available_devices(); let available: Vec = devices .iter() .map(|d| d.device_type().to_uppercase()) .collect(); // Determine primary backend (priority: CUDA > Metal > CPU) let backend = if devices.iter().any(|d| d.is_cuda()) { "CUDA" } else if devices.iter().any(|d| d.is_metal()) { "METAL" } else { "CPU" } .to_string(); Ok(ComputeBackendInfo { backend, available }) } // ============================================================================= // THERMAL ABLATION BIOHEAT COMMANDS // ============================================================================= /// Initialize the bioheat solver with simulation parameters #[tauri::command] pub async fn bioheat_initialize( state: State<'_, AppState>, params: SimulationParams, ) -> Result { let bioheat_service = state.bioheat_service(); bioheat_service .initialize(params) .await .map_err(|e| e.to_string()) } /// Initialize with default liver ablation parameters #[tauri::command] pub async fn bioheat_initialize_default( state: State<'_, AppState>, ) -> Result { let bioheat_service = state.bioheat_service(); bioheat_service .initialize_default() .await .map_err(|e| e.to_string()) } /// Run a single training step #[tauri::command] pub async fn bioheat_step(state: State<'_, AppState>) -> Result { let bioheat_service = state.bioheat_service(); bioheat_service.step().await.map_err(|e| e.to_string()) } /// Run multiple training steps #[tauri::command] pub async fn bioheat_train( state: State<'_, AppState>, num_steps: usize, ) -> Result, String> { let bioheat_service = state.bioheat_service(); bioheat_service .train(num_steps) .await .map_err(|e| e.to_string()) } /// Get a 3D visualization snapshot #[tauri::command] pub async fn bioheat_snapshot(state: State<'_, AppState>) -> Result { let bioheat_service = state.bioheat_service(); bioheat_service.snapshot().await.map_err(|e| e.to_string()) } /// Get a 2D slice at the specified position #[tauri::command] pub async fn bioheat_get_slice( state: State<'_, AppState>, axis: String, position: f32, ) -> Result { let bioheat_service = state.bioheat_service(); // Parse axis from string let slice_axis = match axis.to_uppercase().as_str() { "X" => SliceAxis::X, "Y" => SliceAxis::Y, "Z" => SliceAxis::Z, _ => SliceAxis::Z, }; bioheat_service .get_slice(slice_axis, position) .await .map_err(|e| e.to_string()) } /// Update probe geometry #[tauri::command] pub async fn bioheat_update_probe( state: State<'_, AppState>, probe: ProbeGeometry, ) -> Result<(), String> { let bioheat_service = state.bioheat_service(); bioheat_service .update_probe(probe) .await .map_err(|e| e.to_string()) } /// Update probe power #[tauri::command] pub async fn bioheat_update_power( state: State<'_, AppState>, power: f32, ) -> Result<(), String> { let bioheat_service = state.bioheat_service(); bioheat_service .update_power(power) .await .map_err(|e| e.to_string()) } /// Advance simulation time #[tauri::command] pub async fn bioheat_advance_time( state: State<'_, AppState>, dt: f32, ) -> Result { let bioheat_service = state.bioheat_service(); bioheat_service .advance_time(dt) .await .map_err(|e| e.to_string()) } /// Set simulation time directly #[tauri::command] pub async fn bioheat_set_time( state: State<'_, AppState>, t: f32, ) -> Result { let bioheat_service = state.bioheat_service(); bioheat_service.set_time(t).await.map_err(|e| e.to_string()) } /// Get current bioheat solver status #[tauri::command] pub async fn bioheat_status(state: State<'_, AppState>) -> Result { let bioheat_service = state.bioheat_service(); bioheat_service.status().await.map_err(|e| e.to_string()) } /// Get loss history #[tauri::command] pub async fn bioheat_loss_history( state: State<'_, AppState>, ) -> Result, String> { let bioheat_service = state.bioheat_service(); bioheat_service .loss_history() .await .map_err(|e| e.to_string()) } /// Reset the bioheat solver #[tauri::command] pub async fn bioheat_reset(state: State<'_, AppState>) -> Result<(), String> { let bioheat_service = state.bioheat_service(); bioheat_service.reset().await.map_err(|e| e.to_string()) } // ============================================================================= // NEURAL OPERATOR DEMO COMMANDS // ============================================================================= use rtx_neural_operator_shared::{ config::PDEConfig, ipc::{ModelInfo, PerformanceMetrics as NeuralOperatorMetrics, SolutionData, TrainingConfig, TrainingProgress}, }; use rtx_neural_operator_demo::TrainingSession; /// Initialize the neural operator with a PDE configuration #[tauri::command] pub async fn neural_operator_initialize( state: State<'_, AppState>, pde_type: String, resolution: u32, ) -> Result { let demo = state.neural_operator_demo(); let mut guard = demo.write().await; // Parse PDE type let pde_config = match pde_type.to_lowercase().as_str() { "darcy" | "darcy_flow" => PDEConfig::darcy(resolution), "heat" | "heat_equation" => PDEConfig::heat(resolution), "poisson" => PDEConfig::poisson(resolution), "navier_stokes" | "navier-stokes" => PDEConfig::navier_stokes(resolution), _ => return Err(format!("Unknown PDE type: {pde_type}")), }; guard.initialize(pde_config).map_err(|e| e.to_string())?; guard.model_info().ok_or_else(|| "Failed to get model info".to_string()) } /// Solve the PDE with the given input field #[tauri::command] pub async fn neural_operator_solve( state: State<'_, AppState>, input: Vec, ) -> Result { let demo = state.neural_operator_demo(); let mut guard = demo.write().await; let result = guard.solve(&input).map_err(|e| e.to_string())?; guard .create_solution_data(&result) .ok_or_else(|| "Failed to create solution data".to_string()) } /// Get performance metrics for the neural operator #[tauri::command] pub async fn neural_operator_get_metrics( state: State<'_, AppState>, ) -> Result { let demo = state.neural_operator_demo(); let guard = demo.read().await; Ok(*guard.metrics()) } /// Get model information #[tauri::command] pub async fn neural_operator_get_model_info( state: State<'_, AppState>, ) -> Result, String> { let demo = state.neural_operator_demo(); let guard = demo.read().await; Ok(guard.model_info()) } /// Reset the neural operator demo #[tauri::command] pub async fn neural_operator_reset(state: State<'_, AppState>) -> Result<(), String> { let demo = state.neural_operator_demo(); let mut guard = demo.write().await; guard.reset(); Ok(()) } /// Check if the neural operator is initialized #[tauri::command] pub async fn neural_operator_is_initialized(state: State<'_, AppState>) -> Result { let demo = state.neural_operator_demo(); let guard = demo.read().await; Ok(guard.is_initialized()) } // ============================================================================= // NEURAL OPERATOR TRAINING COMMANDS // ============================================================================= /// Start FNO training in background /// /// Returns immediately. Use `neural_operator_training_progress` to poll for progress. #[tauri::command] pub async fn neural_operator_start_training( state: State<'_, AppState>, config: TrainingConfig, ) -> Result<(), String> { let demo = state.neural_operator_demo(); let demo_guard = demo.read().await; // Get PDE config from demo let pde_config = demo_guard .config() .cloned() .ok_or_else(|| "Neural operator not initialized".to_string())?; drop(demo_guard); // Check if already training let training = state.neural_operator_training(); let mut training_guard = training.write().await; if let Some(session) = training_guard.as_ref() { if session.is_training() { return Err("Training is already in progress".to_string()); } } // Create new training session let session = TrainingSession::new(config, pde_config); // Start training in background session.start(); // Store session *training_guard = Some(session); Ok(()) } /// Get training progress (poll-based) #[tauri::command] pub async fn neural_operator_training_progress( state: State<'_, AppState>, ) -> Result { let training = state.neural_operator_training(); let guard = training.read().await; if let Some(session) = guard.as_ref() { Ok(session.get_progress()) } else { // Return default "not started" progress Ok(TrainingProgress::new(0, 0)) } } /// Cancel ongoing training #[tauri::command] pub async fn neural_operator_cancel_training( state: State<'_, AppState>, ) -> Result<(), String> { let training = state.neural_operator_training(); let guard = training.read().await; if let Some(session) = guard.as_ref() { session.cancel(); } Ok(()) } /// Check if training is currently active #[tauri::command] pub async fn neural_operator_is_training( state: State<'_, AppState>, ) -> Result { let training = state.neural_operator_training(); let guard = training.read().await; if let Some(session) = guard.as_ref() { Ok(session.is_training()) } else { Ok(false) } } // ============================================================================= // FNO BENCHMARK COMMANDS // ============================================================================= use rtx_neural_operator_demo::{ BenchmarkConfig, BenchmarkPDEType, BenchmarkResult as FnoBenchmarkResult, BenchmarkRunner, BenchmarkSummary, }; /// DTO for benchmark configuration from frontend #[derive(Debug, Clone, serde::Deserialize)] pub struct BenchmarkConfigDto { /// Resolutions to test pub resolutions: Vec, /// Number of problems per resolution pub n_problems: usize, /// PDE type (poisson, heat, darcy) pub pde_type: String, } /// DTO for benchmark result to frontend #[derive(Debug, Clone, serde::Serialize)] pub struct BenchmarkResultDto { /// Solver method name pub method: String, /// Grid resolution pub resolution: usize, /// Solve time in milliseconds pub solve_time_ms: f64, /// L2 error vs reference pub l2_error: Option, /// Memory usage in MB pub memory_mb: f64, /// Number of iterations pub iterations: Option, /// PDE type pub pde_type: String, } impl From for BenchmarkResultDto { fn from(r: FnoBenchmarkResult) -> Self { Self { method: r.method, resolution: r.resolution, solve_time_ms: r.solve_time_ms, l2_error: r.l2_error, memory_mb: r.memory_mb, iterations: r.iterations, pde_type: r.pde_type.name().to_string(), } } } /// DTO for benchmark summary to frontend #[derive(Debug, Clone, serde::Serialize)] pub struct BenchmarkSummaryDto { /// Solver method name pub method: String, /// Resolution pub resolution: usize, /// Average solve time (ms) pub avg_time_ms: f64, /// Standard deviation of solve time pub std_time_ms: f64, /// Min/max times pub min_time_ms: f64, pub max_time_ms: f64, /// Average L2 error pub avg_l2_error: Option, /// Average memory usage pub avg_memory_mb: f64, /// Number of runs pub n_runs: usize, } impl From for BenchmarkSummaryDto { fn from(s: BenchmarkSummary) -> Self { Self { method: s.method, resolution: s.resolution, avg_time_ms: s.avg_time_ms, std_time_ms: s.std_time_ms, min_time_ms: s.min_time_ms, max_time_ms: s.max_time_ms, avg_l2_error: s.avg_l2_error, avg_memory_mb: s.avg_memory_mb, n_runs: s.n_runs, } } } /// Run classical (FDM/FEM) benchmarks #[tauri::command] pub async fn benchmark_run_classical( config: BenchmarkConfigDto, ) -> Result, String> { let pde_type = match config.pde_type.to_lowercase().as_str() { "poisson" => BenchmarkPDEType::Poisson, "heat" => BenchmarkPDEType::Heat, "darcy" => BenchmarkPDEType::Darcy, _ => return Err(format!("Unknown PDE type: {}", config.pde_type)), }; let bench_config = BenchmarkConfig { resolutions: config.resolutions, n_problems: config.n_problems, pde_type, use_reference: true, reference_resolution: 512, }; let runner = BenchmarkRunner::new(bench_config); // Run in a blocking task since benchmarks are CPU-intensive let results = tokio::task::spawn_blocking(move || { runner.run_classical_benchmarks() }) .await .map_err(|e| format!("Benchmark task failed: {}", e))?; Ok(results.into_iter().map(BenchmarkResultDto::from).collect()) } /// Run all benchmarks including FNO (if model is trained) #[tauri::command] pub async fn benchmark_run_all( state: State<'_, AppState>, config: BenchmarkConfigDto, ) -> Result, String> { let pde_type = match config.pde_type.to_lowercase().as_str() { "poisson" => BenchmarkPDEType::Poisson, "heat" => BenchmarkPDEType::Heat, "darcy" => BenchmarkPDEType::Darcy, _ => return Err(format!("Unknown PDE type: {}", config.pde_type)), }; let bench_config = BenchmarkConfig { resolutions: config.resolutions, n_problems: config.n_problems, pde_type, use_reference: true, reference_resolution: 512, }; // Check if we have a trained model let demo = state.neural_operator_demo(); let demo_guard = demo.read().await; let has_model = demo_guard.is_initialized(); drop(demo_guard); let runner = BenchmarkRunner::new(bench_config); // Run benchmarks - for now just classical (FNO requires model access refactoring) let results = tokio::task::spawn_blocking(move || { runner.run_classical_benchmarks() }) .await .map_err(|e| format!("Benchmark task failed: {}", e))?; let mut dto_results: Vec = results .into_iter() .map(BenchmarkResultDto::from) .collect(); // Add a note if FNO model is available but not benchmarked if has_model { tracing::info!("FNO model available - FNO benchmark would show ~100-1000x speedup"); } Ok(dto_results) } /// Run benchmarks with statistics (multiple runs) #[tauri::command] pub async fn benchmark_run_with_stats( config: BenchmarkConfigDto, n_runs: usize, ) -> Result, String> { let pde_type = match config.pde_type.to_lowercase().as_str() { "poisson" => BenchmarkPDEType::Poisson, "heat" => BenchmarkPDEType::Heat, "darcy" => BenchmarkPDEType::Darcy, _ => return Err(format!("Unknown PDE type: {}", config.pde_type)), }; let bench_config = BenchmarkConfig { resolutions: config.resolutions, n_problems: config.n_problems, pde_type, use_reference: true, reference_resolution: 512, }; let runner = BenchmarkRunner::new(bench_config); let summaries = tokio::task::spawn_blocking(move || { runner.run_with_statistics(n_runs) }) .await .map_err(|e| format!("Benchmark task failed: {}", e))?; Ok(summaries.into_iter().map(BenchmarkSummaryDto::from).collect()) } /// Get quick benchmark for a single resolution #[tauri::command] pub async fn benchmark_quick( resolution: usize, pde_type: String, ) -> Result, String> { let pde = match pde_type.to_lowercase().as_str() { "poisson" => BenchmarkPDEType::Poisson, "heat" => BenchmarkPDEType::Heat, "darcy" => BenchmarkPDEType::Darcy, _ => return Err(format!("Unknown PDE type: {}", pde_type)), }; let config = BenchmarkConfig { resolutions: vec![resolution], n_problems: 1, pde_type: pde, use_reference: true, reference_resolution: resolution * 4, }; let runner = BenchmarkRunner::new(config); let results = tokio::task::spawn_blocking(move || { runner.run_classical_benchmarks() }) .await .map_err(|e| format!("Benchmark task failed: {}", e))?; Ok(results.into_iter().map(BenchmarkResultDto::from).collect()) } // ============================================================================= // PIDDM (PHYSICS-INFORMED DIFFUSION) DEMO COMMANDS // ============================================================================= use rtx_piddm_shared::{ PdeType, PiddmTrainingConfig, PiddmSamplingConfig, TrainingProgress as PiddmTrainingProgress, SamplingProgress as PiddmSamplingProgress, TrainingResult as PiddmTrainingResult, SamplingResult as PiddmSamplingResult, }; use rtx_piddm_demo::{PiddmTrainer, PiddmSampler}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc as StdArc; // Static storage for training/sampling progress and results static PIDDM_TRAINING_ACTIVE: AtomicBool = AtomicBool::new(false); static PIDDM_SAMPLING_ACTIVE: AtomicBool = AtomicBool::new(false); /// Initialize the PIDDM model with configuration #[tauri::command] pub async fn piddm_initialize( state: State<'_, AppState>, pde_type: String, _scheduler: String, resolution: u32, ) -> Result<(), String> { let pde = match pde_type.to_lowercase().as_str() { "poisson" => PdeType::Poisson, "heat" => PdeType::Heat, "darcy" => PdeType::Darcy, "burgers" => PdeType::Burgers, _ => return Err(format!("Unknown PDE type: {}", pde_type)), }; let config = PiddmTrainingConfig { pde_type: pde, resolution, ..Default::default() }; let mut trainer = PiddmTrainer::new(config); trainer.init_model().map_err(|e| e.to_string())?; let mut guard = state.piddm_trainer().write().await; *guard = Some(trainer); Ok(()) } /// Reset the PIDDM model #[tauri::command] pub async fn piddm_reset(state: State<'_, AppState>) -> Result<(), String> { let mut trainer_guard = state.piddm_trainer().write().await; *trainer_guard = None; let mut sampler_guard = state.piddm_sampler().write().await; *sampler_guard = None; PIDDM_TRAINING_ACTIVE.store(false, Ordering::SeqCst); PIDDM_SAMPLING_ACTIVE.store(false, Ordering::SeqCst); Ok(()) } /// Start PIDDM training #[tauri::command] pub async fn piddm_start_training( state: State<'_, AppState>, config: PiddmTrainingConfig, ) -> Result<(), String> { if PIDDM_TRAINING_ACTIVE.load(Ordering::SeqCst) { return Err("Training already in progress".to_string()); } let trainer = PiddmTrainer::new(config); let mut guard = state.piddm_trainer().write().await; *guard = Some(trainer); PIDDM_TRAINING_ACTIVE.store(true, Ordering::SeqCst); Ok(()) } /// Get PIDDM training progress #[tauri::command] pub async fn piddm_training_progress( _state: State<'_, AppState>, ) -> Result { // For now, return default progress. Real implementation would use channels. Ok(PiddmTrainingProgress::default()) } /// Get PIDDM training result #[tauri::command] pub async fn piddm_training_result( state: State<'_, AppState>, ) -> Result { let mut guard = state.piddm_trainer().write().await; let trainer = guard.as_mut().ok_or("No trainer initialized")?; let (tx, mut _rx) = tokio::sync::mpsc::channel(100); let result = trainer.train(tx).await.map_err(|e| e.to_string())?; PIDDM_TRAINING_ACTIVE.store(false, Ordering::SeqCst); Ok(result) } /// Cancel PIDDM training #[tauri::command] pub async fn piddm_cancel_training(_state: State<'_, AppState>) -> Result<(), String> { PIDDM_TRAINING_ACTIVE.store(false, Ordering::SeqCst); Ok(()) } /// Start PIDDM sampling #[tauri::command] pub async fn piddm_start_sampling( state: State<'_, AppState>, config: PiddmSamplingConfig, ) -> Result<(), String> { if PIDDM_SAMPLING_ACTIVE.load(Ordering::SeqCst) { return Err("Sampling already in progress".to_string()); } let pde_type = PdeType::Poisson; // Default, could be configured let mut sampler = PiddmSampler::new(config, pde_type); sampler.load_weights(None).map_err(|e| e.to_string())?; let mut guard = state.piddm_sampler().write().await; *guard = Some(sampler); PIDDM_SAMPLING_ACTIVE.store(true, Ordering::SeqCst); Ok(()) } /// Get PIDDM sampling progress #[tauri::command] pub async fn piddm_sampling_progress( _state: State<'_, AppState>, ) -> Result { Ok(PiddmSamplingProgress::default()) } /// Get PIDDM sampling result #[tauri::command] pub async fn piddm_sampling_result( state: State<'_, AppState>, ) -> Result { let guard = state.piddm_sampler().read().await; let sampler = guard.as_ref().ok_or("No sampler initialized")?; let (tx, mut _rx) = tokio::sync::mpsc::channel(100); let result = sampler.sample(tx).await.map_err(|e| e.to_string())?; PIDDM_SAMPLING_ACTIVE.store(false, Ordering::SeqCst); Ok(result) } // ============================================================================= // DIGITAL TWIN DEMO COMMANDS // ============================================================================= use rtx_digital_twin_shared::{ SimulationConfig as DtSimulationConfig, ProbeConfig as DtProbeConfig, SimulationProgress as DtSimulationProgress, SimulationResult as DtSimulationResult, WhatIfResult as DtWhatIfResult, GeometrySummary as DtGeometrySummary, SliceData as DtSliceData, SliceOrientation as DtSliceOrientation, WhatIfRequest as DtWhatIfRequest, }; use rtx_digital_twin_demo::TwinSimulator; /// Initialize the Digital Twin with a geometry preset #[tauri::command] pub async fn digital_twin_initialize( state: State<'_, AppState>, preset: String, resolution: [usize; 3], spacing: [f32; 3], ) -> Result { let config = DtSimulationConfig { resolution: [resolution[0] as u32, resolution[1] as u32, resolution[2] as u32], spacing, ..Default::default() }; let mut simulator = TwinSimulator::new(config); let summary = simulator .init_from_preset(&preset) .map_err(|e| e.to_string())?; let mut guard = state.digital_twin_simulator().write().await; *guard = Some(simulator); Ok(summary) } /// Reset the Digital Twin #[tauri::command] pub async fn digital_twin_reset(state: State<'_, AppState>) -> Result<(), String> { let mut guard = state.digital_twin_simulator().write().await; if let Some(ref mut simulator) = *guard { simulator.reset().map_err(|e| e.to_string())?; } *guard = None; Ok(()) } /// Start a thermal simulation #[tauri::command] pub async fn digital_twin_start_simulation( state: State<'_, AppState>, _config: DtSimulationConfig, probe: DtProbeConfig, ) -> Result { let mut guard = state.digital_twin_simulator().write().await; let simulator = guard.as_mut().ok_or("Digital Twin not initialized")?; let (tx, mut _rx) = tokio::sync::mpsc::channel(100); let result = simulator .run_simulation(probe, None, false, tx) .await .map_err(|e| e.to_string())?; Ok(result) } /// Get simulation progress #[tauri::command] pub async fn digital_twin_simulation_progress( _state: State<'_, AppState>, ) -> Result { // For now, return default progress. Real implementation would use channels. Ok(DtSimulationProgress::default()) } /// Get simulation result #[tauri::command] pub async fn digital_twin_simulation_result( state: State<'_, AppState>, probe: DtProbeConfig, ) -> Result { let mut guard = state.digital_twin_simulator().write().await; let simulator = guard.as_mut().ok_or("Digital Twin not initialized")?; let (tx, mut _rx) = tokio::sync::mpsc::channel(100); let result = simulator .run_simulation(probe, None, true, tx) .await .map_err(|e| e.to_string())?; Ok(result) } /// Run what-if analysis #[tauri::command] pub async fn digital_twin_what_if( state: State<'_, AppState>, probe: DtProbeConfig, duration: f32, ) -> Result { let mut guard = state.digital_twin_simulator().write().await; let simulator = guard.as_mut().ok_or("Digital Twin not initialized")?; let request = DtWhatIfRequest { probe, duration }; let result = simulator .what_if(request) .await .map_err(|e| e.to_string())?; Ok(result) } /// Get a slice for visualization #[tauri::command] pub async fn digital_twin_get_slice( state: State<'_, AppState>, orientation: String, index: u32, ) -> Result { let guard = state.digital_twin_simulator().read().await; let simulator = guard.as_ref().ok_or("Digital Twin not initialized")?; let orientation = match orientation.to_lowercase().as_str() { "axial" => DtSliceOrientation::Axial, "coronal" => DtSliceOrientation::Coronal, "sagittal" => DtSliceOrientation::Sagittal, _ => return Err(format!("Unknown orientation: {}", orientation)), }; simulator .get_slice(orientation, index) .map_err(|e| e.to_string()) } // ============================================================================= // Image Classifier Commands // ============================================================================= use image_classifier_shared::{ ClassificationResult, ClassifierConfig, ClassifierMetrics, ClassifierStatus, ModelArchitecture, }; use rtx_image_classifier_demo::ImageClassifier; /// Initialize the image classifier with the given configuration #[tauri::command] pub async fn image_classifier_initialize( state: State<'_, AppState>, architecture: String, use_gpu: bool, ) -> Result { let arch = match architecture.to_lowercase().as_str() { "vit_base_16" | "vit-base-16" => ModelArchitecture::ViTBase16, "vit_large_16" | "vit-large-16" => ModelArchitecture::ViTLarge16, "convnext_tiny" | "convnext-tiny" => ModelArchitecture::ConvNeXtTiny, "convnext_small" | "convnext-small" => ModelArchitecture::ConvNeXtSmall, "convnext_base" | "convnext-base" => ModelArchitecture::ConvNeXtBase, _ => return Err(format!("Unknown architecture: {}", architecture)), }; let config = ClassifierConfig { architecture: arch, num_classes: 1000, image_size: 224, use_gpu, }; let classifier = ImageClassifier::new(config).map_err(|e| e.to_string())?; classifier.initialize().map_err(|e| e.to_string())?; let status = classifier.status(); let mut guard = state.image_classifier().write().await; *guard = Some(classifier); Ok(status) } /// Reset the image classifier #[tauri::command] pub async fn image_classifier_reset(state: State<'_, AppState>) -> Result<(), String> { let mut guard = state.image_classifier().write().await; *guard = None; Ok(()) } /// Classify an image from base64-encoded data #[tauri::command] pub async fn image_classifier_classify( state: State<'_, AppState>, image_data: String, top_k: Option, ) -> Result { let guard = state.image_classifier().read().await; let classifier = guard.as_ref().ok_or("Image Classifier not initialized")?; classifier .classify_base64(&image_data, top_k.unwrap_or(5)) .map_err(|e| e.to_string()) } /// Get classifier status #[tauri::command] pub async fn image_classifier_status( state: State<'_, AppState>, ) -> Result { let guard = state.image_classifier().read().await; let classifier = guard.as_ref().ok_or("Image Classifier not initialized")?; Ok(classifier.status()) } /// Get classifier metrics #[tauri::command] pub async fn image_classifier_metrics( state: State<'_, AppState>, ) -> Result { let guard = state.image_classifier().read().await; let classifier = guard.as_ref().ok_or("Image Classifier not initialized")?; Ok(classifier.metrics()) } /// Check if classifier is initialized #[tauri::command] pub async fn image_classifier_is_initialized(state: State<'_, AppState>) -> Result { let guard = state.image_classifier().read().await; Ok(guard.is_some() && guard.as_ref().map(|c| c.is_initialized()).unwrap_or(false)) } // ============================================================================= // Time Series Forecast Demo Commands // ============================================================================= use rtx_timeseries_demo::TimeSeriesForecaster; use timeseries_shared::{ DataPoint, FitMetrics, ForecastConfig, ForecastResult, ForecasterStatus, ModelType, SampleDataset, TimeSeriesData, }; /// Initialize the time series forecaster #[tauri::command] pub async fn timeseries_initialize(state: State<'_, AppState>) -> Result { let forecaster = TimeSeriesForecaster::new(); let status = forecaster.status().await; let mut guard = state.timeseries_forecaster().write().await; *guard = Some(forecaster); Ok(status) } /// Reset the time series forecaster #[tauri::command] pub async fn timeseries_reset(state: State<'_, AppState>) -> Result<(), String> { let guard = state.timeseries_forecaster().read().await; if let Some(forecaster) = guard.as_ref() { forecaster.reset().await; } Ok(()) } /// Fit a model to time series data #[tauri::command] pub async fn timeseries_fit( state: State<'_, AppState>, data: TimeSeriesData, config: ForecastConfig, ) -> Result { let guard = state.timeseries_forecaster().read().await; let forecaster = guard.as_ref().ok_or("Time Series Forecaster not initialized")?; forecaster.fit(data, config).await.map_err(|e| e.to_string()) } /// Generate forecasts from the fitted model #[tauri::command] pub async fn timeseries_forecast( state: State<'_, AppState>, ) -> Result { let guard = state.timeseries_forecaster().read().await; let forecaster = guard.as_ref().ok_or("Time Series Forecaster not initialized")?; forecaster.forecast().await.map_err(|e| e.to_string()) } /// Get forecaster status #[tauri::command] pub async fn timeseries_status(state: State<'_, AppState>) -> Result { let guard = state.timeseries_forecaster().read().await; let forecaster = guard.as_ref().ok_or("Time Series Forecaster not initialized")?; Ok(forecaster.status().await) } /// Check if forecaster is initialized #[tauri::command] pub async fn timeseries_is_initialized(state: State<'_, AppState>) -> Result { let guard = state.timeseries_forecaster().read().await; if let Some(forecaster) = guard.as_ref() { Ok(forecaster.is_initialized().await) } else { Ok(false) } } /// Generate sample data for the given dataset type #[tauri::command] pub async fn timeseries_generate_sample( dataset: SampleDataset, length: usize, ) -> Result { rtx_timeseries_demo::generate_sample_data(dataset, length).map_err(|e| e.to_string()) } /// Get available sample datasets #[tauri::command] pub fn timeseries_get_sample_datasets() -> Vec { rtx_timeseries_demo::SAMPLE_DATASETS.to_vec() } /// Get available model types #[tauri::command] pub fn timeseries_get_model_types() -> Vec { vec![ ModelType::Arima, ModelType::Sarima, ModelType::Prophet, ModelType::ExponentialSmoothing, ModelType::NeuralProphet, ModelType::Transformer, ] } // ============================================================================= // Portfolio Optimizer Demo Commands // ============================================================================= use rtx_portfolio_demo::PortfolioOptimizer; use portfolio_shared::{ EfficientFrontier, OptimizationResult, OptimizerStatus, PortfolioConfig, PortfolioPreset, }; /// Initialize the portfolio optimizer #[tauri::command] pub async fn portfolio_initialize(state: State<'_, AppState>) -> Result { let optimizer = PortfolioOptimizer::new(); let status = optimizer.status(); let mut guard = state.portfolio_optimizer().write().await; *guard = Some(optimizer); Ok(status) } /// Reset the portfolio optimizer #[tauri::command] pub async fn portfolio_reset(state: State<'_, AppState>) -> Result<(), String> { let guard = state.portfolio_optimizer().read().await; if let Some(optimizer) = guard.as_ref() { drop(guard); let mut write_guard = state.portfolio_optimizer().write().await; if let Some(opt) = write_guard.as_mut() { opt.reset(); } } Ok(()) } /// Configure portfolio for optimization #[tauri::command] pub async fn portfolio_configure( state: State<'_, AppState>, config: PortfolioConfig, ) -> Result { let guard = state.portfolio_optimizer().read().await; let mut optimizer = guard.as_ref().ok_or("Portfolio Optimizer not initialized")?; drop(guard); let mut write_guard = state.portfolio_optimizer().write().await; let opt = write_guard.as_mut().ok_or("Portfolio Optimizer not initialized")?; opt.initialize(config).map_err(|e| e.to_string())?; Ok(opt.status()) } /// Optimize portfolio #[tauri::command] pub async fn portfolio_optimize( state: State<'_, AppState>, ) -> Result { let guard = state.portfolio_optimizer().read().await; let optimizer = guard.as_ref().ok_or("Portfolio Optimizer not initialized")?; drop(guard); let mut write_guard = state.portfolio_optimizer().write().await; let opt = write_guard.as_mut().ok_or("Portfolio Optimizer not initialized")?; opt.optimize().map_err(|e| e.to_string()) } /// Compute efficient frontier #[tauri::command] pub async fn portfolio_efficient_frontier( state: State<'_, AppState>, num_points: usize, ) -> Result { let guard = state.portfolio_optimizer().read().await; let optimizer = guard.as_ref().ok_or("Portfolio Optimizer not initialized")?; optimizer.compute_efficient_frontier(num_points).map_err(|e| e.to_string()) } /// Get optimizer status #[tauri::command] pub async fn portfolio_status(state: State<'_, AppState>) -> Result { let guard = state.portfolio_optimizer().read().await; let optimizer = guard.as_ref().ok_or("Portfolio Optimizer not initialized")?; Ok(optimizer.status()) } /// Check if optimizer is initialized #[tauri::command] pub async fn portfolio_is_initialized(state: State<'_, AppState>) -> Result { let guard = state.portfolio_optimizer().read().await; Ok(guard.is_some() && guard.as_ref().map(|o| o.status().initialized).unwrap_or(false)) } /// Generate sample assets for a preset #[tauri::command] pub async fn portfolio_generate_sample( preset: PortfolioPreset, ) -> Result, String> { rtx_portfolio_demo::generate_sample_assets(preset).map_err(|e| e.to_string()) } /// Get available portfolio presets #[tauri::command] pub fn portfolio_get_presets() -> Vec { rtx_portfolio_demo::SAMPLE_PRESETS.to_vec() } // ============================================================================= // Risk Analyzer Demo Commands // ============================================================================= use rtx_risk_analyzer::RiskAnalyzer; use risk_analyzer_shared::{ RiskAnalysisRequest, RiskAnalysisResult, RiskAnalyzerStatus, }; /// Initialize the risk analyzer #[tauri::command] pub async fn risk_analyzer_initialize(state: State<'_, AppState>) -> Result { let analyzer = RiskAnalyzer::new(); let status = analyzer.status(); let mut guard = state.risk_analyzer().write().await; *guard = Some(analyzer); Ok(status) } /// Reset the risk analyzer #[tauri::command] pub async fn risk_analyzer_reset(state: State<'_, AppState>) -> Result<(), String> { let mut guard = state.risk_analyzer().write().await; if let Some(ref mut analyzer) = *guard { analyzer.reset(); } Ok(()) } /// Perform risk analysis on a portfolio #[tauri::command] pub async fn risk_analyzer_analyze( state: State<'_, AppState>, request: RiskAnalysisRequest, ) -> Result { let guard = state.risk_analyzer().read().await; let mut analyzer = guard.as_ref().ok_or("Risk Analyzer not initialized")?; drop(guard); let mut write_guard = state.risk_analyzer().write().await; let analyzer = write_guard.as_mut().ok_or("Risk Analyzer not initialized")?; analyzer.analyze(request).map_err(|e| e.to_string()) } /// Get risk analyzer status #[tauri::command] pub async fn risk_analyzer_status(state: State<'_, AppState>) -> Result { let guard = state.risk_analyzer().read().await; let analyzer = guard.as_ref().ok_or("Risk Analyzer not initialized")?; Ok(analyzer.status()) } /// Check if risk analyzer is initialized #[tauri::command] pub async fn risk_analyzer_is_initialized(state: State<'_, AppState>) -> Result { let guard = state.risk_analyzer().read().await; Ok(guard.is_some()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_vessel_params_serialization() { let params = VesselParams { length: 0.1, radius: 0.005, stenosis_ratio: Some(0.5), }; let json = serde_json::to_string(¶ms).unwrap(); let deserialized: VesselParams = serde_json::from_str(&json).unwrap(); assert!((deserialized.length - 0.1).abs() < f64::EPSILON); assert!((deserialized.radius - 0.005).abs() < f64::EPSILON); assert_eq!(deserialized.stenosis_ratio, Some(0.5)); } #[test] fn test_grid_query_params_serialization() { let params = GridQueryParams { nx: 50, ny: 20, time: 0.0, }; let json = serde_json::to_string(¶ms).unwrap(); let deserialized: GridQueryParams = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized.nx, 50); assert_eq!(deserialized.ny, 20); } #[test] fn test_status_response_serialization() { let status = StatusResponse { initialized: true, trained: false, inference_count: 100, avg_inference_time_ms: 15.5, uptime_secs: 3600, }; let json = serde_json::to_string(&status).unwrap(); let deserialized: StatusResponse = serde_json::from_str(&json).unwrap(); assert!(deserialized.initialized); assert!(!deserialized.trained); assert_eq!(deserialized.inference_count, 100); } } // ============================================================================= // PINN Benchmark Commands // ============================================================================= use pinn_benchmark_shared::config::ProblemType; use pinn_benchmark_shared::ipc::{BenchmarkResult, ComparisonResult}; /// Initialize PINN benchmark with configuration #[tauri::command] pub async fn pinn_benchmark_initialize( config: pinn_benchmark_shared::config::BenchmarkConfig, state: State<'_, AppState>, ) -> Result<(), String> { let runner = rtx_pinn_benchmark::BenchmarkRunner::new(config); let mut benchmark = state.pinn_benchmark().write().await; *benchmark = Some(runner); Ok(()) } /// Start PINN benchmark training #[tauri::command] pub async fn pinn_benchmark_start_training( state: State<'_, AppState>, ) -> Result { let mut benchmark_guard = state.pinn_benchmark().write().await; let benchmark = benchmark_guard .as_mut() .ok_or_else(|| "Benchmark not initialized".to_string())?; // Run benchmark (this will block for the duration of training) let result = benchmark .run(|_progress| { // Progress callback - could be enhanced to send events }) .map_err(|e| format!("Training failed: {e}"))?; Ok(result) } /// Get current PINN benchmark status #[tauri::command] pub async fn pinn_benchmark_get_status( state: State<'_, AppState>, ) -> Result, String> { let benchmark = state.pinn_benchmark().read().await; if let Some(runner) = benchmark.as_ref() { Ok(runner.result().cloned()) } else { Ok(None) } } /// Compare PINN with reference solver #[tauri::command] pub async fn pinn_benchmark_compare( reference_time_s: f64, state: State<'_, AppState>, ) -> Result { let benchmark = state.pinn_benchmark().read().await; let runner = benchmark .as_ref() .ok_or_else(|| "Benchmark not initialized".to_string())?; runner .compare_with_reference(reference_time_s) .map_err(|e| format!("Comparison failed: {e}")) } /// Reset PINN benchmark #[tauri::command] pub async fn pinn_benchmark_reset( state: State<'_, AppState>, ) -> Result<(), String> { let mut benchmark = state.pinn_benchmark().write().await; *benchmark = None; Ok(()) }