mirror of
https://github.com/ruvnet/RuView
synced 2026-07-31 18:51:42 +00:00
004a63e82d
- Upgrade openssl to 0.10.78 (CVE-2026-41676), jsonwebtoken to 9.4 - Suppress unmaintained-only/no-CVE advisories in .cargo/audit.toml with per-entry rationale - Fix all `cargo clippy --all-targets -- -D warnings` errors across 35 crates: derivable_impls, needless_range_loop, map_or→is_some_and/ is_none_or, await_holding_lock (drop MutexGuard before .await), ptr_arg (&mut Vec→&mut [T]), useless_conversion, approximate_constant (2.718→E, 3.14→PI), field_reassign_with_default, manual_inspect, useless_vec, lines_filter_map_ok, print_literal, dead_code - Apply `cargo fmt --all` - Pre-existing test failure in wifi-densepose-signal (test_estimate_occupancy_noise_only) is not introduced by this PR
302 lines
9.6 KiB
Rust
302 lines
9.6 KiB
Rust
//! Depth estimation through debris layers.
|
|
|
|
use crate::domain::{DebrisMaterial, DebrisProfile, DepthEstimate, MoistureLevel};
|
|
|
|
/// Configuration for depth estimation
|
|
#[derive(Debug, Clone)]
|
|
pub struct DepthEstimatorConfig {
|
|
/// Maximum depth to estimate (meters)
|
|
pub max_depth: f64,
|
|
/// Minimum signal attenuation to consider (dB)
|
|
pub min_attenuation: f64,
|
|
/// WiFi frequency in GHz
|
|
pub frequency_ghz: f64,
|
|
/// Free space path loss at 1 meter (dB)
|
|
pub free_space_loss_1m: f64,
|
|
}
|
|
|
|
impl Default for DepthEstimatorConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
max_depth: 10.0,
|
|
min_attenuation: 3.0,
|
|
frequency_ghz: 5.8, // 5.8 GHz WiFi
|
|
free_space_loss_1m: 47.0, // FSPL at 1m for 5.8 GHz
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Estimator for survivor depth through debris
|
|
pub struct DepthEstimator {
|
|
config: DepthEstimatorConfig,
|
|
}
|
|
|
|
impl DepthEstimator {
|
|
/// Create a new depth estimator
|
|
pub fn new(config: DepthEstimatorConfig) -> Self {
|
|
Self { config }
|
|
}
|
|
|
|
/// Create with default configuration
|
|
pub fn with_defaults() -> Self {
|
|
Self::new(DepthEstimatorConfig::default())
|
|
}
|
|
|
|
/// Estimate depth from signal attenuation
|
|
pub fn estimate_depth(
|
|
&self,
|
|
signal_attenuation: f64, // Total attenuation in dB
|
|
distance_2d: f64, // Horizontal distance in meters
|
|
debris_profile: &DebrisProfile,
|
|
) -> Option<DepthEstimate> {
|
|
if signal_attenuation < self.config.min_attenuation {
|
|
// Very little attenuation - probably not buried
|
|
return Some(DepthEstimate {
|
|
depth: 0.0,
|
|
uncertainty: 0.5,
|
|
debris_profile: debris_profile.clone(),
|
|
confidence: 0.9,
|
|
});
|
|
}
|
|
|
|
// Calculate free space path loss for horizontal distance
|
|
let fspl = self.free_space_path_loss(distance_2d);
|
|
|
|
// Debris attenuation = total - free space loss
|
|
let debris_attenuation = (signal_attenuation - fspl).max(0.0);
|
|
|
|
// Get attenuation coefficient for debris type
|
|
let attenuation_per_meter = debris_profile.attenuation_factor();
|
|
|
|
if attenuation_per_meter < 0.1 {
|
|
return None;
|
|
}
|
|
|
|
// Estimate depth
|
|
let depth = debris_attenuation / attenuation_per_meter;
|
|
|
|
// Clamp to maximum
|
|
if depth > self.config.max_depth {
|
|
return None;
|
|
}
|
|
|
|
// Calculate uncertainty (increases with depth and material variability)
|
|
let base_uncertainty = 0.3;
|
|
let depth_uncertainty = depth * 0.15;
|
|
let material_uncertainty = self.material_uncertainty(debris_profile);
|
|
let uncertainty = base_uncertainty + depth_uncertainty + material_uncertainty;
|
|
|
|
// Calculate confidence (decreases with depth)
|
|
let confidence = (1.0 - depth / self.config.max_depth).max(0.3);
|
|
|
|
Some(DepthEstimate {
|
|
depth,
|
|
uncertainty,
|
|
debris_profile: debris_profile.clone(),
|
|
confidence,
|
|
})
|
|
}
|
|
|
|
/// Estimate debris profile from signal characteristics
|
|
pub fn estimate_debris_profile(
|
|
&self,
|
|
signal_variance: f64,
|
|
signal_multipath: f64,
|
|
moisture_indicator: f64,
|
|
) -> DebrisProfile {
|
|
// Estimate material based on signal characteristics
|
|
let primary_material = if signal_variance > 0.5 {
|
|
// High variance suggests heterogeneous material
|
|
DebrisMaterial::Mixed
|
|
} else if signal_multipath > 0.7 {
|
|
// High multipath suggests reflective surfaces
|
|
DebrisMaterial::HeavyConcrete
|
|
} else if signal_multipath < 0.3 {
|
|
// Low multipath suggests absorptive material
|
|
DebrisMaterial::Soil
|
|
} else {
|
|
DebrisMaterial::LightConcrete
|
|
};
|
|
|
|
// Estimate void fraction from multipath
|
|
let void_fraction = signal_multipath.clamp(0.1, 0.5);
|
|
|
|
// Estimate moisture from signal characteristics
|
|
let moisture_content = if moisture_indicator > 0.7 {
|
|
MoistureLevel::Wet
|
|
} else if moisture_indicator > 0.4 {
|
|
MoistureLevel::Damp
|
|
} else {
|
|
MoistureLevel::Dry
|
|
};
|
|
|
|
DebrisProfile {
|
|
primary_material,
|
|
void_fraction,
|
|
moisture_content,
|
|
metal_content: crate::domain::MetalContent::Low,
|
|
}
|
|
}
|
|
|
|
/// Calculate free space path loss
|
|
fn free_space_path_loss(&self, distance: f64) -> f64 {
|
|
// FSPL = 20*log10(d) + 20*log10(f) + 20*log10(4*pi/c)
|
|
// Simplified: FSPL(d) = FSPL(1m) + 20*log10(d)
|
|
|
|
if distance <= 0.0 {
|
|
return 0.0;
|
|
}
|
|
|
|
self.config.free_space_loss_1m + 20.0 * distance.log10()
|
|
}
|
|
|
|
/// Calculate uncertainty based on material properties
|
|
fn material_uncertainty(&self, profile: &DebrisProfile) -> f64 {
|
|
// Mixed materials have higher uncertainty
|
|
let material_factor = match profile.primary_material {
|
|
DebrisMaterial::Mixed => 0.4,
|
|
DebrisMaterial::HeavyConcrete => 0.2,
|
|
DebrisMaterial::LightConcrete => 0.2,
|
|
DebrisMaterial::Soil => 0.3,
|
|
DebrisMaterial::Wood => 0.15,
|
|
DebrisMaterial::Snow => 0.1,
|
|
DebrisMaterial::Metal => 0.5, // Very unpredictable
|
|
};
|
|
|
|
// Moisture adds uncertainty
|
|
let moisture_factor = match profile.moisture_content {
|
|
MoistureLevel::Dry => 0.0,
|
|
MoistureLevel::Damp => 0.1,
|
|
MoistureLevel::Wet => 0.2,
|
|
MoistureLevel::Saturated => 0.3,
|
|
};
|
|
|
|
material_factor + moisture_factor
|
|
}
|
|
|
|
/// Estimate depth from multiple signal paths
|
|
pub fn estimate_from_multipath(
|
|
&self,
|
|
direct_path_attenuation: f64,
|
|
reflected_paths: &[(f64, f64)], // (attenuation, delay)
|
|
debris_profile: &DebrisProfile,
|
|
) -> Option<DepthEstimate> {
|
|
// Use path differences to estimate depth
|
|
if reflected_paths.is_empty() {
|
|
return self.estimate_depth(direct_path_attenuation, 0.0, debris_profile);
|
|
}
|
|
|
|
// Average extra path length from reflections
|
|
const SPEED_OF_LIGHT: f64 = 299_792_458.0;
|
|
let avg_extra_path: f64 = reflected_paths
|
|
.iter()
|
|
.map(|(_, delay)| delay * SPEED_OF_LIGHT / 2.0) // Round trip
|
|
.sum::<f64>()
|
|
/ reflected_paths.len() as f64;
|
|
|
|
// Extra path length is approximately related to depth
|
|
// (reflections bounce off debris layers)
|
|
let estimated_depth = avg_extra_path / 4.0; // Empirical factor
|
|
|
|
let attenuation_per_meter = debris_profile.attenuation_factor();
|
|
let attenuation_based_depth = direct_path_attenuation / attenuation_per_meter;
|
|
|
|
// Combine estimates
|
|
let depth = (estimated_depth + attenuation_based_depth) / 2.0;
|
|
|
|
if depth > self.config.max_depth {
|
|
return None;
|
|
}
|
|
|
|
let uncertainty = 0.5 + depth * 0.2;
|
|
let confidence = (1.0 - depth / self.config.max_depth).max(0.3);
|
|
|
|
Some(DepthEstimate {
|
|
depth,
|
|
uncertainty,
|
|
debris_profile: debris_profile.clone(),
|
|
confidence,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn default_debris() -> DebrisProfile {
|
|
DebrisProfile {
|
|
primary_material: DebrisMaterial::Mixed,
|
|
void_fraction: 0.25,
|
|
moisture_content: MoistureLevel::Dry,
|
|
metal_content: crate::domain::MetalContent::Low,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_low_attenuation_surface() {
|
|
let estimator = DepthEstimator::with_defaults();
|
|
|
|
let result = estimator.estimate_depth(1.0, 5.0, &default_debris());
|
|
assert!(result.is_some());
|
|
|
|
let estimate = result.unwrap();
|
|
assert!(estimate.depth < 0.1);
|
|
assert!(estimate.confidence > 0.8);
|
|
}
|
|
|
|
#[test]
|
|
fn test_depth_increases_with_attenuation() {
|
|
let estimator = DepthEstimator::with_defaults();
|
|
let debris = default_debris();
|
|
|
|
let low = estimator.estimate_depth(10.0, 0.0, &debris);
|
|
let high = estimator.estimate_depth(30.0, 0.0, &debris);
|
|
|
|
assert!(low.is_some() && high.is_some());
|
|
assert!(high.unwrap().depth > low.unwrap().depth);
|
|
}
|
|
|
|
#[test]
|
|
fn test_confidence_decreases_with_depth() {
|
|
let estimator = DepthEstimator::with_defaults();
|
|
let debris = default_debris();
|
|
|
|
let shallow = estimator.estimate_depth(5.0, 0.0, &debris);
|
|
let deep = estimator.estimate_depth(40.0, 0.0, &debris);
|
|
|
|
if let (Some(s), Some(d)) = (shallow, deep) {
|
|
assert!(s.confidence > d.confidence);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_debris_profile_estimation() {
|
|
let estimator = DepthEstimator::with_defaults();
|
|
|
|
// High variance = mixed materials
|
|
let profile = estimator.estimate_debris_profile(0.7, 0.5, 0.3);
|
|
assert!(matches!(profile.primary_material, DebrisMaterial::Mixed));
|
|
|
|
// High multipath = concrete
|
|
let profile2 = estimator.estimate_debris_profile(0.2, 0.8, 0.3);
|
|
assert!(matches!(
|
|
profile2.primary_material,
|
|
DebrisMaterial::HeavyConcrete
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn test_free_space_path_loss() {
|
|
let estimator = DepthEstimator::with_defaults();
|
|
|
|
// FSPL increases with distance
|
|
let fspl_1m = estimator.free_space_path_loss(1.0);
|
|
let fspl_10m = estimator.free_space_path_loss(10.0);
|
|
|
|
assert!(fspl_10m > fspl_1m);
|
|
// Should be about 20 dB difference (20*log10(10))
|
|
assert!((fspl_10m - fspl_1m - 20.0).abs() < 1.0);
|
|
}
|
|
}
|