1920 lines
57 KiB
Rust
1920 lines
57 KiB
Rust
//! 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<f64>,
|
|
}
|
|
|
|
/// Query result for field values
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct FieldQueryResult {
|
|
/// Query points
|
|
pub points: Vec<Point2D>,
|
|
/// U velocity component at each point
|
|
pub u: Vec<f64>,
|
|
/// V velocity component at each point
|
|
pub v: Vec<f64>,
|
|
/// Pressure at each point
|
|
pub p: Vec<f64>,
|
|
/// Wall shear stress at boundary points (optional)
|
|
pub wss: Option<Vec<f64>>,
|
|
/// 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<f64>,
|
|
/// V velocity field (row-major order)
|
|
pub v_field: Vec<f64>,
|
|
/// Pressure field (row-major order)
|
|
pub p_field: Vec<f64>,
|
|
/// Interior mask (true if inside vessel)
|
|
pub mask: Vec<bool>,
|
|
/// 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<StatusResponse, String> {
|
|
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<StatusResponse, String> {
|
|
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<Point2D>,
|
|
time: f64,
|
|
) -> Result<FieldQueryResult, String> {
|
|
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<GridQueryResponse, String> {
|
|
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<StatusResponse, String> {
|
|
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<PerformanceMetrics, String> {
|
|
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<SimulationState, String> {
|
|
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<Vec<Point2D>, 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<Vec<Point2D>, 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<MreStatus, String> {
|
|
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<LossRecord, String> {
|
|
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<Vec<LossRecord>, 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<MreSnapshot, String> {
|
|
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<MreStatus, String> {
|
|
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<SlidescopeStatus, String> {
|
|
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<SlideMetadata, String> {
|
|
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<SlideFilter>,
|
|
) -> Result<Vec<SlideMetadata>, 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<SlideMetadata, String> {
|
|
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<TileResponse, String> {
|
|
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<JobStatus, String> {
|
|
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<JobStatus, String> {
|
|
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<NmfResult, String> {
|
|
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<SlidescopeStatus, String> {
|
|
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<String>,
|
|
}
|
|
|
|
/// Get the active compute backend based on runtime device detection
|
|
#[tauri::command]
|
|
pub async fn get_compute_backend() -> Result<ComputeBackendInfo, String> {
|
|
use rtx_tensor::Device;
|
|
|
|
let devices = Device::available_devices();
|
|
let available: Vec<String> = 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<BioheatStatus, String> {
|
|
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<BioheatStatus, String> {
|
|
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<BioheatLossRecord, String> {
|
|
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<Vec<BioheatLossRecord>, 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<BioheatSnapshot, String> {
|
|
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<SliceData, String> {
|
|
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<f32, String> {
|
|
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<f32, String> {
|
|
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<BioheatStatus, String> {
|
|
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<Vec<BioheatLossRecord>, 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<ModelInfo, String> {
|
|
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<f32>,
|
|
) -> Result<SolutionData, String> {
|
|
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<NeuralOperatorMetrics, String> {
|
|
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<Option<ModelInfo>, 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<bool, String> {
|
|
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<TrainingProgress, String> {
|
|
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<bool, String> {
|
|
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<usize>,
|
|
/// 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<f64>,
|
|
/// Memory usage in MB
|
|
pub memory_mb: f64,
|
|
/// Number of iterations
|
|
pub iterations: Option<usize>,
|
|
/// PDE type
|
|
pub pde_type: String,
|
|
}
|
|
|
|
impl From<FnoBenchmarkResult> 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<f64>,
|
|
/// Average memory usage
|
|
pub avg_memory_mb: f64,
|
|
/// Number of runs
|
|
pub n_runs: usize,
|
|
}
|
|
|
|
impl From<BenchmarkSummary> 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<Vec<BenchmarkResultDto>, 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<Vec<BenchmarkResultDto>, 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<BenchmarkResultDto> = 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<Vec<BenchmarkSummaryDto>, 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<Vec<BenchmarkResultDto>, 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<PiddmTrainingProgress, String> {
|
|
// 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<PiddmTrainingResult, String> {
|
|
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<PiddmSamplingProgress, String> {
|
|
Ok(PiddmSamplingProgress::default())
|
|
}
|
|
|
|
/// Get PIDDM sampling result
|
|
#[tauri::command]
|
|
pub async fn piddm_sampling_result(
|
|
state: State<'_, AppState>,
|
|
) -> Result<PiddmSamplingResult, String> {
|
|
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<DtGeometrySummary, String> {
|
|
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<DtSimulationResult, String> {
|
|
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<DtSimulationProgress, String> {
|
|
// 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<DtSimulationResult, String> {
|
|
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<DtWhatIfResult, String> {
|
|
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<DtSliceData, String> {
|
|
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<ClassifierStatus, String> {
|
|
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<usize>,
|
|
) -> Result<ClassificationResult, String> {
|
|
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<ClassifierStatus, String> {
|
|
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<ClassifierMetrics, String> {
|
|
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<bool, String> {
|
|
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<ForecasterStatus, String> {
|
|
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<FitMetrics, String> {
|
|
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<ForecastResult, String> {
|
|
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<ForecasterStatus, String> {
|
|
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<bool, String> {
|
|
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<TimeSeriesData, String> {
|
|
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<SampleDataset> {
|
|
rtx_timeseries_demo::SAMPLE_DATASETS.to_vec()
|
|
}
|
|
|
|
/// Get available model types
|
|
#[tauri::command]
|
|
pub fn timeseries_get_model_types() -> Vec<ModelType> {
|
|
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<OptimizerStatus, String> {
|
|
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<OptimizerStatus, String> {
|
|
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<OptimizationResult, String> {
|
|
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<EfficientFrontier, String> {
|
|
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<OptimizerStatus, String> {
|
|
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<bool, String> {
|
|
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<Vec<portfolio_shared::Asset>, 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<PortfolioPreset> {
|
|
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<RiskAnalyzerStatus, String> {
|
|
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<RiskAnalysisResult, String> {
|
|
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<RiskAnalyzerStatus, String> {
|
|
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<bool, String> {
|
|
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<BenchmarkResult, String> {
|
|
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<Option<BenchmarkResult>, 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<ComparisonResult, String> {
|
|
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(())
|
|
}
|