use crate::distance::cosine_similarity_fast;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DLMWeights {
pub depth: f32,
pub sibling: f32,
pub homogeneity: f32,
pub temporal: f32,
pub complexity: f32,
}
impl DLMWeights {
#[inline]
pub fn new(depth: f32, sibling: f32, homogeneity: f32, temporal: f32, complexity: f32) -> Self {
let total = depth + sibling + homogeneity + temporal + complexity;
if total > 0.0 {
Self {
depth: depth / total,
sibling: sibling / total,
homogeneity: homogeneity / total,
temporal: temporal / total,
complexity: complexity / total,
}
} else {
Self::default()
}
}
#[inline]
pub fn from_array(weights: [f32; 5]) -> Self {
Self::new(weights[0], weights[1], weights[2], weights[3], weights[4])
}
#[inline]
pub fn to_array(&self) -> [f32; 5] {
[self.depth, self.sibling, self.homogeneity, self.temporal, self.complexity]
}
#[inline]
pub fn semantic_focused() -> Self {
Self::new(0.15, 0.10, 0.50, 0.15, 0.10)
}
#[inline]
pub fn structural_focused() -> Self {
Self::new(0.35, 0.25, 0.15, 0.15, 0.10)
}
#[inline]
pub fn temporal_focused() -> Self {
Self::new(0.15, 0.10, 0.20, 0.45, 0.10)
}
#[inline]
pub fn equal() -> Self {
Self::new(0.2, 0.2, 0.2, 0.2, 0.2)
}
}
impl Default for DLMWeights {
fn default() -> Self {
Self {
depth: 0.25,
sibling: 0.15,
homogeneity: 0.30,
temporal: 0.20,
complexity: 0.10,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TrajectoryCoordinate {
pub depth: u32,
pub sibling_order: u32,
pub homogeneity: f32,
pub temporal: f32,
}
impl TrajectoryCoordinate {
#[inline]
pub fn new(depth: u32, sibling_order: u32, homogeneity: f32, temporal: f32) -> Self {
debug_assert!(
(0.0..=1.0).contains(&homogeneity),
"homogeneity must be in [0, 1], got {}",
homogeneity
);
debug_assert!(
(0.0..=1.0).contains(&temporal),
"temporal must be in [0, 1], got {}",
temporal
);
Self {
depth,
sibling_order,
homogeneity,
temporal,
}
}
#[inline]
pub fn root() -> Self {
Self {
depth: 0,
sibling_order: 0,
homogeneity: 1.0,
temporal: 0.0,
}
}
#[inline]
pub fn distance(&self, other: &Self) -> f32 {
let d_depth = (self.depth as f32 - other.depth as f32).abs();
let d_sibling = (self.sibling_order as f32 - other.sibling_order as f32).abs();
let d_homo = self.homogeneity - other.homogeneity;
let d_time = self.temporal - other.temporal;
(d_depth.powi(2) + d_sibling.powi(2) + d_homo.powi(2) + d_time.powi(2)).sqrt()
}
#[inline]
pub fn weighted_distance(&self, other: &Self, weights: (f32, f32, f32, f32)) -> f32 {
let d_depth = (self.depth as f32 - other.depth as f32).abs() * weights.0;
let d_sibling = (self.sibling_order as f32 - other.sibling_order as f32).abs() * weights.1;
let d_homo = (self.homogeneity - other.homogeneity).abs() * weights.2;
let d_time = (self.temporal - other.temporal).abs() * weights.3;
(d_depth.powi(2) + d_sibling.powi(2) + d_homo.powi(2) + d_time.powi(2)).sqrt()
}
#[inline]
pub fn manhattan_distance(&self, other: &Self) -> f32 {
let d_depth = (self.depth as i64 - other.depth as i64).unsigned_abs() as f32;
let d_sibling = (self.sibling_order as i64 - other.sibling_order as i64).unsigned_abs() as f32;
let d_homo = (self.homogeneity - other.homogeneity).abs();
let d_time = (self.temporal - other.temporal).abs();
d_depth + d_sibling + d_homo + d_time
}
pub fn from_episode(
depth: u32,
sibling_order: u32,
embedding: &[f32],
parent_embedding: Option<&[f32]>,
timestamp: i64,
trajectory_start: i64,
trajectory_end: i64,
) -> Self {
let homogeneity = parent_embedding
.map(|parent| cosine_similarity_fast(embedding, parent).clamp(0.0, 1.0))
.unwrap_or(1.0);
let temporal = if trajectory_end > trajectory_start {
let duration = (trajectory_end - trajectory_start) as f64;
let elapsed = (timestamp - trajectory_start) as f64;
(elapsed / duration).clamp(0.0, 1.0) as f32
} else {
0.0 };
Self {
depth,
sibling_order,
homogeneity,
temporal,
}
}
#[inline]
pub fn normalize_depth(&self, max_depth: u32) -> f32 {
if max_depth == 0 {
0.0
} else {
self.depth as f32 / max_depth as f32
}
}
#[inline]
pub fn normalize_sibling(&self, total_siblings: u32) -> f32 {
if total_siblings <= 1 {
0.0
} else {
self.sibling_order as f32 / (total_siblings - 1) as f32
}
}
pub fn normalized(&self, max_depth: u32, total_siblings: u32) -> NormalizedCoordinate {
NormalizedCoordinate {
depth: self.normalize_depth(max_depth),
sibling_order: self.normalize_sibling(total_siblings),
homogeneity: self.homogeneity,
temporal: self.temporal,
}
}
}
impl Default for TrajectoryCoordinate {
fn default() -> Self {
Self::root()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NormalizedCoordinate {
pub depth: f32,
pub sibling_order: f32,
pub homogeneity: f32,
pub temporal: f32,
}
impl NormalizedCoordinate {
#[inline]
pub fn distance(&self, other: &Self) -> f32 {
let d_depth = self.depth - other.depth;
let d_sibling = self.sibling_order - other.sibling_order;
let d_homo = self.homogeneity - other.homogeneity;
let d_time = self.temporal - other.temporal;
(d_depth.powi(2) + d_sibling.powi(2) + d_homo.powi(2) + d_time.powi(2)).sqrt()
}
#[inline]
pub fn to_array(&self) -> [f32; 4] {
[self.depth, self.sibling_order, self.homogeneity, self.temporal]
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TrajectoryCoordinate5D {
pub depth: u32,
pub sibling_order: u32,
pub homogeneity: f32,
pub temporal: f32,
pub complexity: u32,
}
impl TrajectoryCoordinate5D {
#[inline]
pub fn new(depth: u32, sibling_order: u32, homogeneity: f32, temporal: f32, complexity: u32) -> Self {
debug_assert!(
(0.0..=1.0).contains(&homogeneity),
"homogeneity must be in [0, 1], got {}",
homogeneity
);
debug_assert!(
(0.0..=1.0).contains(&temporal),
"temporal must be in [0, 1], got {}",
temporal
);
debug_assert!(complexity >= 1, "complexity must be at least 1");
Self {
depth,
sibling_order,
homogeneity,
temporal,
complexity,
}
}
#[inline]
pub fn from_4d(coord: &TrajectoryCoordinate) -> Self {
Self {
depth: coord.depth,
sibling_order: coord.sibling_order,
homogeneity: coord.homogeneity,
temporal: coord.temporal,
complexity: 1,
}
}
#[inline]
pub fn from_4d_with_complexity(coord: &TrajectoryCoordinate, complexity: u32) -> Self {
Self {
depth: coord.depth,
sibling_order: coord.sibling_order,
homogeneity: coord.homogeneity,
temporal: coord.temporal,
complexity: complexity.max(1),
}
}
#[inline]
pub fn to_4d(&self) -> TrajectoryCoordinate {
TrajectoryCoordinate {
depth: self.depth,
sibling_order: self.sibling_order,
homogeneity: self.homogeneity,
temporal: self.temporal,
}
}
#[inline]
pub fn root() -> Self {
Self {
depth: 0,
sibling_order: 0,
homogeneity: 1.0,
temporal: 0.0,
complexity: 1,
}
}
#[inline]
pub fn distance(&self, other: &Self) -> f32 {
let d_depth = (self.depth as f32 - other.depth as f32).abs();
let d_sibling = (self.sibling_order as f32 - other.sibling_order as f32).abs();
let d_homo = self.homogeneity - other.homogeneity;
let d_time = self.temporal - other.temporal;
let d_complexity = ((self.complexity as f32).ln_1p() - (other.complexity as f32).ln_1p()).abs();
(d_depth.powi(2) + d_sibling.powi(2) + d_homo.powi(2) + d_time.powi(2) + d_complexity.powi(2)).sqrt()
}
#[inline]
pub fn weighted_distance(&self, other: &Self, weights: (f32, f32, f32, f32, f32)) -> f32 {
let d_depth = (self.depth as f32 - other.depth as f32).abs() * weights.0;
let d_sibling = (self.sibling_order as f32 - other.sibling_order as f32).abs() * weights.1;
let d_homo = (self.homogeneity - other.homogeneity).abs() * weights.2;
let d_time = (self.temporal - other.temporal).abs() * weights.3;
let d_complexity = ((self.complexity as f32).ln_1p() - (other.complexity as f32).ln_1p()).abs() * weights.4;
(d_depth.powi(2) + d_sibling.powi(2) + d_homo.powi(2) + d_time.powi(2) + d_complexity.powi(2)).sqrt()
}
#[inline]
pub fn dlm_distance(&self, other: &Self, weights: &DLMWeights) -> f32 {
self.weighted_distance(
other,
(weights.depth, weights.sibling, weights.homogeneity, weights.temporal, weights.complexity),
)
}
#[inline]
pub fn weighted_manhattan(&self, other: &Self, weights: &DLMWeights) -> f32 {
let d_depth = (self.depth as f32 - other.depth as f32).abs() * weights.depth;
let d_sibling = (self.sibling_order as f32 - other.sibling_order as f32).abs() * weights.sibling;
let d_homo = (self.homogeneity - other.homogeneity).abs() * weights.homogeneity;
let d_time = (self.temporal - other.temporal).abs() * weights.temporal;
let d_complexity = ((self.complexity as f32).ln_1p() - (other.complexity as f32).ln_1p()).abs() * weights.complexity;
d_depth + d_sibling + d_homo + d_time + d_complexity
}
#[inline]
pub fn manhattan_distance(&self, other: &Self) -> f32 {
let d_depth = (self.depth as i64 - other.depth as i64).unsigned_abs() as f32;
let d_sibling = (self.sibling_order as i64 - other.sibling_order as i64).unsigned_abs() as f32;
let d_homo = (self.homogeneity - other.homogeneity).abs();
let d_time = (self.temporal - other.temporal).abs();
let d_complexity = ((self.complexity as f32).ln_1p() - (other.complexity as f32).ln_1p()).abs();
d_depth + d_sibling + d_homo + d_time + d_complexity
}
#[inline]
pub fn as_vector(&self) -> [f32; 5] {
[
self.depth as f32,
self.sibling_order as f32,
self.homogeneity,
self.temporal,
(self.complexity as f32).ln_1p(),
]
}
#[inline]
pub fn as_normalized_vector(&self, max_depth: u32, max_siblings: u32, max_complexity: u32) -> [f32; 5] {
let depth_norm = if max_depth > 0 { self.depth as f32 / max_depth as f32 } else { 0.0 };
let sibling_norm = if max_siblings > 0 { self.sibling_order as f32 / max_siblings as f32 } else { 0.0 };
let complexity_norm = if max_complexity > 1 {
(self.complexity as f32).ln_1p() / (max_complexity as f32).ln_1p()
} else {
0.0
};
[depth_norm, sibling_norm, self.homogeneity, self.temporal, complexity_norm]
}
#[inline]
pub fn cosine_similarity(&self, other: &Self) -> f32 {
let a = self.as_vector();
let b = other.as_vector();
let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm_a > 0.0 && norm_b > 0.0 {
dot / (norm_a * norm_b)
} else {
0.0
}
}
#[inline]
pub fn hybrid_distance(&self, other: &Self, weights: &DLMWeights, cosine_weight: f32) -> f32 {
let euclidean = self.dlm_distance(other, weights);
let cosine_dist = 1.0 - self.cosine_similarity(other);
let cw = cosine_weight.clamp(0.0, 1.0);
(1.0 - cw) * euclidean + cw * cosine_dist
}
#[inline]
pub fn complexity_factor(&self) -> f32 {
(self.complexity as f32).ln_1p() / 10.0_f32.ln_1p()
}
#[inline]
pub fn is_multimodal(&self) -> bool {
self.complexity > 1
}
pub fn normalized(&self, max_depth: u32, total_siblings: u32, max_complexity: u32) -> NormalizedCoordinate5D {
let depth = if max_depth == 0 {
0.0
} else {
self.depth as f32 / max_depth as f32
};
let sibling_order = if total_siblings <= 1 {
0.0
} else {
self.sibling_order as f32 / (total_siblings - 1) as f32
};
let complexity = if max_complexity <= 1 {
0.0
} else {
(self.complexity as f32).ln_1p() / (max_complexity as f32).ln_1p()
};
NormalizedCoordinate5D {
depth,
sibling_order,
homogeneity: self.homogeneity,
temporal: self.temporal,
complexity,
}
}
}
impl Default for TrajectoryCoordinate5D {
fn default() -> Self {
Self::root()
}
}
impl From<TrajectoryCoordinate> for TrajectoryCoordinate5D {
fn from(coord: TrajectoryCoordinate) -> Self {
Self::from_4d(&coord)
}
}
impl From<&TrajectoryCoordinate> for TrajectoryCoordinate5D {
fn from(coord: &TrajectoryCoordinate) -> Self {
Self::from_4d(coord)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NormalizedCoordinate5D {
pub depth: f32,
pub sibling_order: f32,
pub homogeneity: f32,
pub temporal: f32,
pub complexity: f32,
}
impl NormalizedCoordinate5D {
#[inline]
pub fn distance(&self, other: &Self) -> f32 {
let d_depth = self.depth - other.depth;
let d_sibling = self.sibling_order - other.sibling_order;
let d_homo = self.homogeneity - other.homogeneity;
let d_time = self.temporal - other.temporal;
let d_complexity = self.complexity - other.complexity;
(d_depth.powi(2) + d_sibling.powi(2) + d_homo.powi(2) + d_time.powi(2) + d_complexity.powi(2)).sqrt()
}
#[inline]
pub fn to_array(&self) -> [f32; 5] {
[self.depth, self.sibling_order, self.homogeneity, self.temporal, self.complexity]
}
}
pub fn compute_trajectory_coordinates(
episodes: &[(u32, u32, &[f32], i64)],
parent_indices: &[Option<usize>],
) -> Vec<TrajectoryCoordinate> {
if episodes.is_empty() {
return Vec::new();
}
let trajectory_start = episodes.iter().map(|(_, _, _, t)| *t).min().unwrap_or(0);
let trajectory_end = episodes.iter().map(|(_, _, _, t)| *t).max().unwrap_or(0);
let mut coordinates = Vec::with_capacity(episodes.len());
for (i, (depth, sibling_order, embedding, timestamp)) in episodes.iter().enumerate() {
let parent_embedding = parent_indices.get(i)
.and_then(|p| *p)
.and_then(|p| episodes.get(p))
.map(|(_, _, e, _)| *e);
let coord = TrajectoryCoordinate::from_episode(
*depth,
*sibling_order,
embedding,
parent_embedding,
*timestamp,
trajectory_start,
trajectory_end,
);
coordinates.push(coord);
}
coordinates
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_coordinate() {
let coord = TrajectoryCoordinate::new(3, 1, 0.85, 0.5);
assert_eq!(coord.depth, 3);
assert_eq!(coord.sibling_order, 1);
assert!((coord.homogeneity - 0.85).abs() < 1e-6);
assert!((coord.temporal - 0.5).abs() < 1e-6);
}
#[test]
fn test_root_coordinate() {
let root = TrajectoryCoordinate::root();
assert_eq!(root.depth, 0);
assert_eq!(root.sibling_order, 0);
assert!((root.homogeneity - 1.0).abs() < 1e-6);
assert!((root.temporal - 0.0).abs() < 1e-6);
}
#[test]
fn test_distance_identical() {
let coord = TrajectoryCoordinate::new(5, 2, 0.7, 0.3);
assert!((coord.distance(&coord) - 0.0).abs() < 1e-6);
}
#[test]
fn test_distance_different() {
let a = TrajectoryCoordinate::new(0, 0, 1.0, 0.0);
let b = TrajectoryCoordinate::new(1, 0, 1.0, 0.0);
assert!((a.distance(&b) - 1.0).abs() < 1e-6);
}
#[test]
fn test_distance_all_dimensions() {
let a = TrajectoryCoordinate::new(0, 0, 0.0, 0.0);
let b = TrajectoryCoordinate::new(1, 1, 1.0, 1.0);
assert!((a.distance(&b) - 2.0).abs() < 1e-6);
}
#[test]
fn test_manhattan_distance() {
let a = TrajectoryCoordinate::new(0, 0, 0.0, 0.0);
let b = TrajectoryCoordinate::new(1, 1, 1.0, 1.0);
assert!((a.manhattan_distance(&b) - 4.0).abs() < 1e-6);
}
#[test]
fn test_weighted_distance() {
let a = TrajectoryCoordinate::new(0, 0, 0.5, 0.5);
let b = TrajectoryCoordinate::new(2, 0, 0.5, 0.5);
let weights = (0.5, 1.0, 1.0, 1.0);
assert!((a.weighted_distance(&b, weights) - 1.0).abs() < 1e-6);
}
#[test]
fn test_normalize_depth() {
let coord = TrajectoryCoordinate::new(5, 0, 0.5, 0.5);
assert!((coord.normalize_depth(10) - 0.5).abs() < 1e-6);
assert!((coord.normalize_depth(5) - 1.0).abs() < 1e-6);
assert!((coord.normalize_depth(0) - 0.0).abs() < 1e-6);
}
#[test]
fn test_normalize_sibling() {
let coord = TrajectoryCoordinate::new(0, 2, 0.5, 0.5);
assert!((coord.normalize_sibling(5) - 0.5).abs() < 1e-6); assert!((coord.normalize_sibling(3) - 1.0).abs() < 1e-6); assert!((coord.normalize_sibling(1) - 0.0).abs() < 1e-6); }
#[test]
fn test_normalized_coordinate() {
let coord = TrajectoryCoordinate::new(5, 2, 0.8, 0.6);
let normalized = coord.normalized(10, 5);
assert!((normalized.depth - 0.5).abs() < 1e-6);
assert!((normalized.sibling_order - 0.5).abs() < 1e-6);
assert!((normalized.homogeneity - 0.8).abs() < 1e-6);
assert!((normalized.temporal - 0.6).abs() < 1e-6);
}
#[test]
fn test_from_episode() {
let parent_embedding = vec![1.0, 0.0, 0.0];
let child_embedding = vec![0.8, 0.6, 0.0];
let coord = TrajectoryCoordinate::from_episode(
3,
1,
&child_embedding,
Some(&parent_embedding),
500,
0,
1000,
);
assert_eq!(coord.depth, 3);
assert_eq!(coord.sibling_order, 1);
assert!((coord.homogeneity - 0.8).abs() < 0.01);
assert!((coord.temporal - 0.5).abs() < 1e-6);
}
#[test]
fn test_5d_new_coordinate() {
let coord = TrajectoryCoordinate5D::new(3, 1, 0.85, 0.5, 2);
assert_eq!(coord.depth, 3);
assert_eq!(coord.sibling_order, 1);
assert!((coord.homogeneity - 0.85).abs() < 1e-6);
assert!((coord.temporal - 0.5).abs() < 1e-6);
assert_eq!(coord.complexity, 2);
}
#[test]
fn test_5d_root_coordinate() {
let root = TrajectoryCoordinate5D::root();
assert_eq!(root.depth, 0);
assert_eq!(root.sibling_order, 0);
assert!((root.homogeneity - 1.0).abs() < 1e-6);
assert!((root.temporal - 0.0).abs() < 1e-6);
assert_eq!(root.complexity, 1);
}
#[test]
fn test_5d_from_4d() {
let coord_4d = TrajectoryCoordinate::new(2, 1, 0.7, 0.4);
let coord_5d = TrajectoryCoordinate5D::from_4d(&coord_4d);
assert_eq!(coord_5d.depth, 2);
assert_eq!(coord_5d.sibling_order, 1);
assert!((coord_5d.homogeneity - 0.7).abs() < 1e-6);
assert!((coord_5d.temporal - 0.4).abs() < 1e-6);
assert_eq!(coord_5d.complexity, 1); }
#[test]
fn test_5d_from_4d_with_complexity() {
let coord_4d = TrajectoryCoordinate::new(2, 1, 0.7, 0.4);
let coord_5d = TrajectoryCoordinate5D::from_4d_with_complexity(&coord_4d, 5);
assert_eq!(coord_5d.complexity, 5);
}
#[test]
fn test_5d_to_4d() {
let coord_5d = TrajectoryCoordinate5D::new(3, 2, 0.8, 0.6, 4);
let coord_4d = coord_5d.to_4d();
assert_eq!(coord_4d.depth, 3);
assert_eq!(coord_4d.sibling_order, 2);
assert!((coord_4d.homogeneity - 0.8).abs() < 1e-6);
assert!((coord_4d.temporal - 0.6).abs() < 1e-6);
}
#[test]
fn test_5d_distance_identical() {
let coord = TrajectoryCoordinate5D::new(5, 2, 0.7, 0.3, 3);
assert!((coord.distance(&coord) - 0.0).abs() < 1e-6);
}
#[test]
fn test_5d_distance_same_complexity() {
let a = TrajectoryCoordinate5D::new(0, 0, 1.0, 0.0, 1);
let b = TrajectoryCoordinate5D::new(1, 0, 1.0, 0.0, 1);
assert!((a.distance(&b) - 1.0).abs() < 1e-6);
}
#[test]
fn test_5d_distance_complexity_affects() {
let a = TrajectoryCoordinate5D::new(0, 0, 1.0, 0.0, 1);
let b = TrajectoryCoordinate5D::new(0, 0, 1.0, 0.0, 10);
let d = a.distance(&b);
assert!(d > 1.0); assert!(d < 3.0); }
#[test]
fn test_5d_complexity_factor() {
let simple = TrajectoryCoordinate5D::new(0, 0, 1.0, 0.0, 1);
let medium = TrajectoryCoordinate5D::new(0, 0, 1.0, 0.0, 5);
let complex = TrajectoryCoordinate5D::new(0, 0, 1.0, 0.0, 10);
assert!(simple.complexity_factor() < 0.3);
assert!(medium.complexity_factor() > 0.4);
assert!(medium.complexity_factor() < 0.8);
assert!(complex.complexity_factor() > 0.85);
}
#[test]
fn test_5d_is_multimodal() {
let simple = TrajectoryCoordinate5D::new(0, 0, 1.0, 0.0, 1);
let multimodal = TrajectoryCoordinate5D::new(0, 0, 1.0, 0.0, 3);
assert!(!simple.is_multimodal());
assert!(multimodal.is_multimodal());
}
#[test]
fn test_5d_from_trait() {
let coord_4d = TrajectoryCoordinate::new(1, 0, 0.9, 0.2);
let coord_5d: TrajectoryCoordinate5D = coord_4d.into();
assert_eq!(coord_5d.depth, 1);
assert_eq!(coord_5d.complexity, 1);
}
#[test]
fn test_5d_normalized() {
let coord = TrajectoryCoordinate5D::new(5, 2, 0.8, 0.6, 5);
let normalized = coord.normalized(10, 5, 10);
assert!((normalized.depth - 0.5).abs() < 1e-6);
assert!((normalized.sibling_order - 0.5).abs() < 1e-6);
assert!((normalized.homogeneity - 0.8).abs() < 1e-6);
assert!((normalized.temporal - 0.6).abs() < 1e-6);
assert!(normalized.complexity > 0.7);
assert!(normalized.complexity < 0.8);
}
#[test]
fn test_normalized_5d_distance() {
let a = NormalizedCoordinate5D {
depth: 0.0,
sibling_order: 0.0,
homogeneity: 0.0,
temporal: 0.0,
complexity: 0.0,
};
let b = NormalizedCoordinate5D {
depth: 1.0,
sibling_order: 1.0,
homogeneity: 1.0,
temporal: 1.0,
complexity: 1.0,
};
let d = a.distance(&b);
assert!((d - 5.0_f32.sqrt()).abs() < 1e-5);
}
#[test]
fn test_normalized_5d_to_array() {
let coord = NormalizedCoordinate5D {
depth: 0.1,
sibling_order: 0.2,
homogeneity: 0.3,
temporal: 0.4,
complexity: 0.5,
};
let arr = coord.to_array();
assert_eq!(arr, [0.1, 0.2, 0.3, 0.4, 0.5]);
}
#[test]
fn test_dlm_weights_default() {
let weights = DLMWeights::default();
assert!((weights.depth - 0.25).abs() < 1e-6);
assert!((weights.sibling - 0.15).abs() < 1e-6);
assert!((weights.homogeneity - 0.30).abs() < 1e-6);
assert!((weights.temporal - 0.20).abs() < 1e-6);
assert!((weights.complexity - 0.10).abs() < 1e-6);
let total = weights.depth + weights.sibling + weights.homogeneity + weights.temporal + weights.complexity;
assert!((total - 1.0).abs() < 1e-6);
}
#[test]
fn test_dlm_weights_normalization() {
let weights = DLMWeights::new(1.0, 1.0, 1.0, 1.0, 1.0);
assert!((weights.depth - 0.2).abs() < 1e-6);
assert!((weights.sibling - 0.2).abs() < 1e-6);
assert!((weights.homogeneity - 0.2).abs() < 1e-6);
assert!((weights.temporal - 0.2).abs() < 1e-6);
assert!((weights.complexity - 0.2).abs() < 1e-6);
}
#[test]
fn test_dlm_weights_presets() {
let semantic = DLMWeights::semantic_focused();
assert!(semantic.homogeneity > semantic.depth);
assert!(semantic.homogeneity > semantic.sibling);
let structural = DLMWeights::structural_focused();
assert!(structural.depth > structural.homogeneity);
assert!(structural.sibling > structural.complexity);
let temporal = DLMWeights::temporal_focused();
assert!(temporal.temporal > temporal.depth);
assert!(temporal.temporal > temporal.homogeneity);
let equal = DLMWeights::equal();
assert!((equal.depth - 0.2).abs() < 1e-6);
assert!((equal.sibling - 0.2).abs() < 1e-6);
}
#[test]
fn test_dlm_weights_array_conversion() {
let weights = DLMWeights::default();
let arr = weights.to_array();
let reconstructed = DLMWeights::from_array(arr);
assert!((weights.depth - reconstructed.depth).abs() < 1e-6);
assert!((weights.sibling - reconstructed.sibling).abs() < 1e-6);
assert!((weights.homogeneity - reconstructed.homogeneity).abs() < 1e-6);
assert!((weights.temporal - reconstructed.temporal).abs() < 1e-6);
assert!((weights.complexity - reconstructed.complexity).abs() < 1e-6);
}
#[test]
fn test_5d_dlm_distance() {
let a = TrajectoryCoordinate5D::new(0, 0, 1.0, 0.0, 1);
let b = TrajectoryCoordinate5D::new(2, 1, 0.8, 0.3, 2);
let weights = DLMWeights::default();
let dist = a.dlm_distance(&b, &weights);
assert!(dist > 0.0);
assert!(dist < 5.0);
}
#[test]
fn test_5d_dlm_distance_identical() {
let coord = TrajectoryCoordinate5D::new(3, 2, 0.7, 0.5, 3);
let weights = DLMWeights::default();
assert!((coord.dlm_distance(&coord, &weights) - 0.0).abs() < 1e-6);
}
#[test]
fn test_5d_weighted_manhattan() {
let a = TrajectoryCoordinate5D::new(0, 0, 0.0, 0.0, 1);
let b = TrajectoryCoordinate5D::new(2, 2, 1.0, 1.0, 1);
let weights = DLMWeights::equal();
let dist = a.weighted_manhattan(&b, &weights);
assert!((dist - 1.2).abs() < 1e-6);
}
#[test]
fn test_5d_manhattan_distance() {
let a = TrajectoryCoordinate5D::new(0, 0, 0.0, 0.0, 1);
let b = TrajectoryCoordinate5D::new(2, 2, 1.0, 1.0, 1);
let dist = a.manhattan_distance(&b);
assert!((dist - 6.0).abs() < 1e-6);
}
#[test]
fn test_5d_as_vector() {
let coord = TrajectoryCoordinate5D::new(3, 2, 0.75, 0.5, 4);
let vec = coord.as_vector();
assert!((vec[0] - 3.0).abs() < 1e-6);
assert!((vec[1] - 2.0).abs() < 1e-6);
assert!((vec[2] - 0.75).abs() < 1e-6);
assert!((vec[3] - 0.5).abs() < 1e-6);
assert!((vec[4] - 5.0_f32.ln()).abs() < 1e-6);
}
#[test]
fn test_5d_as_normalized_vector() {
let coord = TrajectoryCoordinate5D::new(5, 3, 0.8, 0.6, 5);
let vec = coord.as_normalized_vector(10, 6, 10);
assert!((vec[0] - 0.5).abs() < 1e-6); assert!((vec[1] - 0.5).abs() < 1e-6); assert!((vec[2] - 0.8).abs() < 1e-6); assert!((vec[3] - 0.6).abs() < 1e-6); assert!(vec[4] > 0.7 && vec[4] < 0.8);
}
#[test]
fn test_5d_cosine_similarity_identical() {
let coord = TrajectoryCoordinate5D::new(3, 2, 0.8, 0.5, 2);
let sim = coord.cosine_similarity(&coord);
assert!((sim - 1.0).abs() < 1e-6);
}
#[test]
fn test_5d_cosine_similarity_similar() {
let a = TrajectoryCoordinate5D::new(2, 1, 0.8, 0.4, 2);
let b = TrajectoryCoordinate5D::new(4, 2, 0.8, 0.4, 2);
let sim = a.cosine_similarity(&b);
assert!(sim > 0.95);
}
#[test]
fn test_5d_cosine_similarity_different() {
let a = TrajectoryCoordinate5D::new(10, 0, 0.0, 0.0, 1);
let b = TrajectoryCoordinate5D::new(0, 10, 1.0, 1.0, 10);
let sim = a.cosine_similarity(&b);
assert!(sim < 0.5);
}
#[test]
fn test_5d_hybrid_distance() {
let a = TrajectoryCoordinate5D::new(2, 1, 0.9, 0.3, 1);
let b = TrajectoryCoordinate5D::new(4, 2, 0.7, 0.6, 3);
let weights = DLMWeights::default();
let euclidean_only = a.hybrid_distance(&b, &weights, 0.0);
let cosine_only = a.hybrid_distance(&b, &weights, 1.0);
let mixed = a.hybrid_distance(&b, &weights, 0.5);
let min = euclidean_only.min(cosine_only);
let max = euclidean_only.max(cosine_only);
assert!(mixed >= min - 1e-6 && mixed <= max + 1e-6);
}
#[test]
fn test_5d_hybrid_distance_identical() {
let coord = TrajectoryCoordinate5D::new(3, 2, 0.8, 0.5, 2);
let weights = DLMWeights::default();
let dist = coord.hybrid_distance(&coord, &weights, 0.5);
assert!((dist - 0.0).abs() < 1e-6);
}
}