//! Integration tests for Medical Digital Twin Platform //! //! These tests demonstrate the complete workflow from segmentation to //! intervention planning and what-if analysis. use rtx_digital_twin::{ AblationProbe, BioheatParams, BoundaryCondition, DigitalTwin, HifuTransducer, Intervention, InterventionType, OrganGeometry, TissueDatabase, TissueLabel, TissueType, TwinConfig, }; /// Test complete workflow: Create geometry from voxel data #[test] fn test_organ_geometry_from_voxels() { // Create a 3D volume with liver and tumor let shape = [30, 30, 30]; let n = shape[0] * shape[1] * shape[2]; let mut labels = vec![0u8; n]; // Start with air // Fill center region with liver tissue for z in 5..25 { for y in 5..25 { for x in 5..25 { let idx = z * shape[0] * shape[1] + y * shape[0] + x; labels[idx] = TissueType::Liver.label(); } } } // Add a spherical tumor at center let center = [15, 15, 15]; let tumor_radius = 3; for z in 0..shape[2] { for y in 0..shape[1] { for x in 0..shape[0] { let dx = x as i32 - center[0] as i32; let dy = y as i32 - center[1] as i32; let dz = z as i32 - center[2] as i32; let dist_sq = dx * dx + dy * dy + dz * dz; if dist_sq <= tumor_radius * tumor_radius { let idx = z * shape[0] * shape[1] + y * shape[0] + x; labels[idx] = TissueType::Tumor.label(); } } } } // Create geometry let geometry = OrganGeometry::from_labels(&labels, shape, [1.0, 1.0, 1.0]) .expect("Failed to create geometry"); // Verify shape assert_eq!(geometry.shape(), shape); assert_eq!(geometry.num_voxels(), n); // Verify tissue distribution let histogram = geometry.tissue_histogram(); assert!(histogram.contains_key(&TissueType::Air), "Should have air"); assert!( histogram.contains_key(&TissueType::Liver), "Should have liver" ); assert!( histogram.contains_key(&TissueType::Tumor), "Should have tumor" ); // Verify center is tumor let center_voxel = geometry.get(center[0], center[1], center[2]).unwrap(); assert_eq!(center_voxel.label.tissue_type(), TissueType::Tumor); } /// Test tissue database with different tissue types #[test] fn test_tissue_map_properties() { let db = TissueDatabase::standard(); // Verify key tissues exist let liver = db.get(TissueType::Liver).expect("Liver should exist"); assert!(liver.thermal_conductivity > 0.5); assert!(liver.perfusion_rate > 0.01); assert!(liver.metabolic_heat > 1000.0); let tumor = db.get(TissueType::Tumor).expect("Tumor should exist"); assert!( tumor.perfusion_rate < liver.perfusion_rate, "Tumor has less perfusion" ); // Verify thermal diffusivity calculation let alpha = liver.thermal_diffusivity(); assert!( alpha > 1e-8 && alpha < 1e-6, "Reasonable thermal diffusivity" ); } /// Test boundary condition application #[test] fn test_boundary_conditions() { let mut geometry = OrganGeometry::new([10, 10, 10], [1.0, 1.0, 1.0]); // Fill with liver for z in 0..10 { for y in 0..10 { for x in 0..10 { geometry.set_label(x, y, z, TissueLabel::from(TissueType::Liver)); } } } let config = TwinConfig { bioheat_params: BioheatParams { max_iterations: 100, tolerance: 0.1, ..Default::default() }, boundary_condition: BoundaryCondition::Temperature(37.0), compute_damage: true, }; let mut twin = DigitalTwin::with_config(geometry, config); // Run baseline simulation let result = twin.simulate_baseline().expect("Baseline should succeed"); assert!(result.iterations > 0, "Should run some iterations"); assert!(result.iterations <= 100, "Should not exceed max iterations"); // Temperature should be reasonable for &temp in &result.temperature { assert!(temp >= 36.0 && temp <= 38.0, "Body temperature range"); } } /// Test digital twin simulation with ablation probe #[test] fn test_digital_twin_simulation_with_ablation() { // Create liver with tumor - larger geometry for better numerical stability let mut geometry = OrganGeometry::new([30, 30, 30], [1.0, 1.0, 1.0]); // Fill interior with liver for z in 3..27 { for y in 3..27 { for x in 3..27 { geometry.set_label(x, y, z, TissueLabel::from(TissueType::Liver)); } } } // Add tumor sphere geometry.create_sphere( [15.0, 15.0, 15.0], 4.0, TissueLabel::from(TissueType::Tumor), ); let config = TwinConfig { bioheat_params: BioheatParams { max_iterations: 500, tolerance: 0.01, dt: 0.01, ..Default::default() }, ..Default::default() }; let mut twin = DigitalTwin::with_config(geometry, config); // Create ablation probe with wider heating zone for stability // Use larger active length and diameter to distribute heat over more voxels let probe = AblationProbe::new([15.0, 15.0, 15.0], 30.0) .with_type(InterventionType::RadiofrequencyAblation) .with_active_length(10.0) .with_diameter(4.0); // Run steady-state simulation let result = twin .simulate_intervention_steady(&probe) .expect("Simulation should succeed"); // Verify results structure assert!(result.iterations > 0, "Should run iterations"); assert_eq!(result.temperature.len(), 30 * 30 * 30); assert_eq!(result.damage.len(), 30 * 30 * 30); // Temperature should be in physical range assert!( result.max_temperature >= 37.0, "Should be at least body temperature" ); // With heat source, temperature distribution should vary across volume let temp_variance: f32 = result .temperature .iter() .map(|&t| (t - 37.0).powi(2)) .sum::() / result.temperature.len() as f32; assert!( temp_variance > 0.0, "Temperature should vary with heat source" ); // Verify simulation ran and produced results assert!(result.iterations <= 500, "Should not exceed max iterations"); } /// Test what-if analysis for intervention planning #[test] fn test_what_if_analysis_workflow() { // Create realistic liver geometry let mut geometry = OrganGeometry::new([40, 40, 40], [1.0, 1.0, 1.0]); // Fill with liver for z in 5..35 { for y in 5..35 { for x in 5..35 { geometry.set_label(x, y, z, TissueLabel::from(TissueType::Liver)); } } } // Add tumor geometry.create_sphere( [20.0, 20.0, 20.0], 5.0, TissueLabel::from(TissueType::Tumor), ); let mut twin = DigitalTwin::new(geometry); // Test RFA ablation let rfa_probe = AblationProbe::new([20.0, 20.0, 20.0], 50.0) .with_type(InterventionType::RadiofrequencyAblation) .with_active_length(10.0); let rfa_result = twin .what_if(&rfa_probe, 10.0) .expect("RFA what-if should succeed"); // Verify what-if result structure assert_eq!(rfa_result.intervention_type, "RadiofrequencyAblation"); assert_eq!(rfa_result.intervention_power, 50.0); assert_eq!(rfa_result.duration, 10.0); assert!( rfa_result.max_temperature >= 37.0, "Temperature should be at least body temp" ); assert!(rfa_result.iterations > 0); assert!(rfa_result.total_damaged_volume >= 0.0); // Generate report let report = rfa_result.report(); assert!(report.contains("What-If Analysis Report")); assert!(report.contains("Radiofrequency")); // Test HIFU twin.reset_temperature(); let hifu = HifuTransducer::new([20.0, 20.0, 20.0], 100.0); let hifu_result = twin .what_if(&hifu, 5.0) .expect("HIFU what-if should succeed"); assert_eq!(hifu_result.intervention_type, "HIFU"); assert_eq!(hifu_result.intervention_power, 100.0); assert!(hifu_result.iterations > 0); } /// Test multiple interventions and scenario comparison #[test] fn test_multiple_intervention_scenarios() { let mut geometry = OrganGeometry::new([30, 30, 30], [1.0, 1.0, 1.0]); // Fill with liver for z in 3..27 { for y in 3..27 { for x in 3..27 { geometry.set_label(x, y, z, TissueLabel::from(TissueType::Liver)); } } } // Add tumor geometry.create_sphere( [15.0, 15.0, 15.0], 3.0, TissueLabel::from(TissueType::Tumor), ); let mut twin = DigitalTwin::new(geometry); // Scenario 1: Low power, longer duration let probe1 = AblationProbe::new([15.0, 15.0, 15.0], 30.0); let result1 = twin.what_if(&probe1, 15.0).expect("Scenario 1 failed"); // Scenario 2: High power, shorter duration twin.reset_temperature(); let probe2 = AblationProbe::new([15.0, 15.0, 15.0], 60.0); let result2 = twin.what_if(&probe2, 7.0).expect("Scenario 2 failed"); // Both scenarios should complete successfully assert!(result1.iterations > 0, "Scenario 1 should run"); assert!(result2.iterations > 0, "Scenario 2 should run"); assert!( result1.max_temperature >= 37.0, "Scenario 1 temperature valid" ); assert!( result2.max_temperature >= 37.0, "Scenario 2 temperature valid" ); // Both should have valid report structures let report1 = result1.report(); let report2 = result2.report(); assert!(report1.contains("What-If Analysis")); assert!(report2.contains("What-If Analysis")); } /// Test geometry summary statistics #[test] fn test_geometry_summary_statistics() { let mut geometry = OrganGeometry::new([20, 20, 20], [2.0, 2.0, 2.0]); // Fill center with liver for z in 5..15 { for y in 5..15 { for x in 5..15 { geometry.set_label(x, y, z, TissueLabel::from(TissueType::Liver)); } } } let twin = DigitalTwin::new(geometry); let summary = twin.geometry_summary(); // Verify summary assert_eq!(summary.shape, [20, 20, 20]); assert_eq!(summary.spacing, [2.0, 2.0, 2.0]); assert_eq!(summary.dimensions, [40.0, 40.0, 40.0]); assert_eq!(summary.total_voxels, 8000); assert!(summary.tissue_voxels > 0); assert!(summary.tissue_volume > 0.0); assert!(summary.num_tissue_types >= 2); // Air + Liver } /// Test temperature field updates #[test] fn test_temperature_field_operations() { let mut geometry = OrganGeometry::new([10, 10, 10], [1.0, 1.0, 1.0]); // Set all to liver for z in 0..10 { for y in 0..10 { for x in 0..10 { geometry.set_label(x, y, z, TissueLabel::from(TissueType::Liver)); } } } // Set temperature field let mut temp_field = vec![37.0f32; 1000]; temp_field[500] = 45.0; // Hot spot geometry .set_temperature_field(&temp_field) .expect("Should set temperature"); // Verify let retrieved = geometry.temperature_field(); assert_eq!(retrieved.len(), 1000); assert_eq!(retrieved[500], 45.0); } /// Test transient simulation #[test] fn test_transient_simulation() { let mut geometry = OrganGeometry::new([15, 15, 15], [1.0, 1.0, 1.0]); // Fill with liver for z in 1..14 { for y in 1..14 { for x in 1..14 { geometry.set_label(x, y, z, TissueLabel::from(TissueType::Liver)); } } } let config = TwinConfig { bioheat_params: BioheatParams { dt: 0.01, max_iterations: 1000, tolerance: 0.001, ..Default::default() }, ..Default::default() }; let mut twin = DigitalTwin::with_config(geometry, config); // Small probe for fast simulation let probe = AblationProbe::new([7.5, 7.5, 7.5], 20.0) .with_active_length(3.0) .with_diameter(1.5); // Run transient simulation for 1 second let result = twin .simulate_intervention(&probe, 1.0) .expect("Transient simulation failed"); // Verify time progression assert!( result.time > 0.9 && result.time <= 1.1, "Should simulate ~1 second" ); assert!(result.iterations > 0); // Damage should accumulate over time let max_damage = result.damage.iter().cloned().fold(0.0f32, f32::max); assert!(max_damage >= 0.0, "Damage should be non-negative"); } /// Test thermal property field extraction #[test] fn test_property_field_extraction() { let mut geometry = OrganGeometry::new([8, 8, 8], [1.0, 1.0, 1.0]); // Create heterogeneous tissue distribution for z in 0..8 { for y in 0..8 { for x in 0..8 { let tissue = if x < 4 { TissueType::Liver } else { TissueType::Muscle }; geometry.set_label(x, y, z, TissueLabel::from(tissue)); } } } // Extract property fields let k_field = geometry.thermal_conductivity_field(); let rho_field = geometry.density_field(); let c_field = geometry.specific_heat_field(); let omega_field = geometry.perfusion_field(); assert_eq!(k_field.len(), 512); assert_eq!(rho_field.len(), 512); assert_eq!(c_field.len(), 512); assert_eq!(omega_field.len(), 512); // Liver and muscle should have different properties let liver_props = geometry.tissue_db().get(TissueType::Liver).unwrap(); let muscle_props = geometry.tissue_db().get(TissueType::Muscle).unwrap(); assert!( (liver_props.thermal_conductivity - muscle_props.thermal_conductivity).abs() > 0.01, "Different tissues should have different conductivity" ); } /// Test intervention heat source generation #[test] fn test_intervention_heat_source_generation() { let geometry = OrganGeometry::new([20, 20, 20], [1.0, 1.0, 1.0]); // Test ablation probe heat source let probe = AblationProbe::new([10.0, 10.0, 10.0], 50.0) .with_active_length(6.0) .with_diameter(2.0); let heat_source = probe .generate_heat_source(&geometry) .expect("Should generate heat source"); assert_eq!(heat_source.len(), 8000); // Center should have heat let center_idx = 10 * 20 * 20 + 10 * 20 + 10; assert!(heat_source[center_idx] > 0.0, "Center should be heated"); // Corners should be zero assert_eq!(heat_source[0], 0.0, "Corner should be unheated"); // Test HIFU heat source let hifu = HifuTransducer::new([10.0, 10.0, 10.0], 100.0); let hifu_source = hifu .generate_heat_source(&geometry) .expect("Should generate HIFU source"); assert_eq!(hifu_source.len(), 8000); assert!(hifu_source[center_idx] > 0.0, "Focus should be heated"); // HIFU should have Gaussian falloff let edge_idx = 5 * 20 * 20 + 10 * 20 + 10; assert!( hifu_source[center_idx] > hifu_source[edge_idx], "Focus should be hotter than periphery" ); } /// Test damage calculation #[test] fn test_damage_calculation() { let mut geometry = OrganGeometry::new([12, 12, 12], [1.0, 1.0, 1.0]); // Fill with liver for z in 1..11 { for y in 1..11 { for x in 1..11 { geometry.set_label(x, y, z, TissueLabel::from(TissueType::Liver)); } } } let mut twin = DigitalTwin::new(geometry); // High-power ablation let probe = AblationProbe::new([6.0, 6.0, 6.0], 60.0) .with_active_length(4.0) .with_diameter(2.0); // Run for longer time to accumulate damage let result = twin .simulate_intervention(&probe, 5.0) .expect("Should simulate"); // Some voxels should have damage > 0 let damaged_count = result.damage.iter().filter(|&&d| d > 0.0).count(); assert!(damaged_count > 0, "Should cause some damage"); // Damaged volume should be reported assert!( result.damaged_volume >= 0.0, "Damaged volume should be non-negative" ); } /// Test complete clinical workflow #[test] fn test_complete_clinical_workflow() { // Step 1: Create patient-specific geometry from segmentation let shape = [50, 50, 50]; let n = shape[0] * shape[1] * shape[2]; let mut labels = vec![0u8; n]; // Liver organ for z in 10..40 { for y in 10..40 { for x in 10..40 { let idx = z * shape[0] * shape[1] + y * shape[0] + x; labels[idx] = TissueType::Liver.label(); } } } // Tumor lesion for z in 22..28 { for y in 22..28 { for x in 22..28 { let idx = z * shape[0] * shape[1] + y * shape[0] + x; labels[idx] = TissueType::Tumor.label(); } } } let geometry = OrganGeometry::from_labels(&labels, shape, [1.0, 1.0, 1.0]) .expect("Should create geometry"); // Step 2: Create digital twin let mut twin = DigitalTwin::new(geometry); // Step 3: Run baseline (no intervention) let baseline = twin.simulate_baseline().expect("Baseline should work"); assert!( baseline.max_temperature >= 36.0 && baseline.max_temperature < 39.0, "Baseline temperature in normal range" ); assert!(baseline.iterations > 0); // Step 4: Plan intervention targeting tumor twin.reset_temperature(); let probe = AblationProbe::new([25.0, 25.0, 25.0], 50.0) .with_type(InterventionType::RadiofrequencyAblation) .with_active_length(8.0); // Step 5: What-if analysis let what_if = twin.what_if(&probe, 10.0).expect("What-if analysis failed"); // Step 6: Evaluate outcome assert!( what_if.max_temperature >= 37.0, "Intervention should produce valid temperature" ); assert!( what_if.total_damaged_volume >= 0.0, "Damaged volume should be non-negative" ); assert!(what_if.iterations > 0, "Should run simulation iterations"); // Generate clinical report let report = what_if.report(); assert!(report.contains("Temperature Results")); assert!(report.contains("Damage Assessment")); }