qualia_core_db/solvers/learning/active/
density.rs1use super::{argsort_desc, ActiveError};
9
10pub fn cosine_similarity(a: &[f64], b: &[f64]) -> Result<f64, ActiveError> {
13 if a.len() != b.len() || a.is_empty() {
14 return Err(ActiveError::InvalidDimension);
15 }
16 let mut dot = 0.0;
17 let mut na = 0.0;
18 let mut nb = 0.0;
19 for i in 0..a.len() {
20 dot += a[i] * b[i];
21 na += a[i] * a[i];
22 nb += b[i] * b[i];
23 }
24 if na <= 0.0 || nb <= 0.0 {
25 return Ok(0.0);
26 }
27 Ok(dot / (na.sqrt() * nb.sqrt()))
28}
29
30pub fn representativeness(features: &[Vec<f64>]) -> Result<Vec<f64>, ActiveError> {
33 let n = features.len();
34 if n < 2 {
35 return Err(ActiveError::InsufficientData);
36 }
37 let mut rep = vec![0.0; n];
38 for i in 0..n {
39 let mut sum = 0.0;
40 for j in 0..n {
41 if i != j {
42 sum += cosine_similarity(&features[i], &features[j])?;
43 }
44 }
45 rep[i] = sum / (n - 1) as f64;
46 }
47 Ok(rep)
48}
49
50pub fn information_density(
54 uncertainty: &[f64],
55 features: &[Vec<f64>],
56 beta: f64,
57) -> Result<Vec<f64>, ActiveError> {
58 if uncertainty.len() != features.len() {
59 return Err(ActiveError::InvalidDimension);
60 }
61 let rep = representativeness(features)?;
62 Ok(uncertainty
63 .iter()
64 .zip(rep.iter())
65 .map(|(&u, &r)| u * r.max(0.0).powf(beta))
66 .collect())
67}
68
69pub fn rank_by_density(
71 uncertainty: &[f64],
72 features: &[Vec<f64>],
73 beta: f64,
74) -> Result<Vec<usize>, ActiveError> {
75 Ok(argsort_desc(&information_density(
76 uncertainty,
77 features,
78 beta,
79 )?))
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 const EPS: f64 = 1e-9;
86
87 #[test]
88 fn cosine_basic() {
89 assert!((cosine_similarity(&[1.0, 0.0], &[1.0, 0.0]).unwrap() - 1.0).abs() < EPS);
90 assert!(cosine_similarity(&[1.0, 0.0], &[0.0, 1.0]).unwrap().abs() < EPS);
91 assert!((cosine_similarity(&[1.0, 0.0], &[-1.0, 0.0]).unwrap() + 1.0).abs() < EPS);
92 assert!(cosine_similarity(&[0.0, 0.0], &[1.0, 1.0]).unwrap().abs() < EPS);
93 }
95
96 #[test]
97 fn density_demotes_an_uncertain_outlier() {
98 let features = vec![vec![1.0, 0.0], vec![0.96, 0.28], vec![-1.0, 0.0]];
101 let uncertainty = vec![0.5, 0.5, 0.5];
102 let ranked = rank_by_density(&uncertainty, &features, 1.0).unwrap();
103 assert_ne!(ranked[0], 2, "the outlier must not be the top query");
104 assert_eq!(
105 ranked[2], 2,
106 "the outlier ranks last under density weighting"
107 );
108 }
109
110 #[test]
111 fn beta_zero_is_plain_uncertainty() {
112 let features = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
113 let uncertainty = vec![0.3, 0.9];
114 let d = information_density(&uncertainty, &features, 0.0).unwrap();
115 assert!((d[0] - 0.3).abs() < EPS && (d[1] - 0.9).abs() < EPS);
117 }
118
119 #[test]
120 fn fails_closed_on_mismatch() {
121 let features = vec![vec![1.0], vec![1.0]];
122 assert_eq!(
123 information_density(&[0.5], &features, 1.0).unwrap_err(),
124 ActiveError::InvalidDimension
125 );
126 }
127}