1pub type Vec32 = Vec<f32>;
7
8fn dot(a: &[f32], b: &[f32]) -> f32 {
9 let mut s = 0.0;
10 for i in 0..a.len() {
11 s += a[i] * b[i];
12 }
13 s
14}
15
16fn l2(v: &[f32]) -> Vec32 {
17 let mut n = 0.0f32;
18 for &x in v {
19 n += x * x;
20 }
21 n = n.sqrt() + 1e-9;
22 v.iter().map(|x| x / n).collect()
23}
24
25pub fn kmeans(x: &[Vec32], k: usize, iters: usize, seed: u32) -> Vec<Vec32> {
27 let k = k.max(2).min((x.len() / 4).max(2));
28 let dim = x[0].len();
29 let mut s = seed;
30 let mut rnd = || {
31 s = s.wrapping_mul(1664525).wrapping_add(1013904223);
32 s as f64 / 4294967296.0
33 };
34 let mut c: Vec<Vec32> = vec![x[(rnd() * x.len() as f64) as usize].clone()];
35 while c.len() < k {
36 let d2: Vec<f32> = x
37 .iter()
38 .map(|xi| {
39 let best = c.iter().fold(-1.0f32, |b, cj| b.max(dot(xi, cj)));
40 (1.0 - best).max(1e-6)
41 })
42 .collect();
43 let tot: f32 = d2.iter().sum();
44 let mut r = rnd() as f32 * tot;
45 let mut idx = 0;
46 while idx < d2.len() && {
47 r -= d2[idx];
48 r > 0.0
49 } {
50 idx += 1;
51 }
52 c.push(x[idx.min(x.len() - 1)].clone());
53 }
54 for _ in 0..iters {
55 let mut sum = vec![vec![0.0f32; dim]; k];
56 let mut cnt = vec![0usize; k];
57 for xi in x {
58 let mut a = 0;
59 let mut bestv = f32::NEG_INFINITY;
60 for j in 0..k {
61 let d = dot(xi, &c[j]);
62 if d > bestv {
63 bestv = d;
64 a = j;
65 }
66 }
67 cnt[a] += 1;
68 for i in 0..dim {
69 sum[a][i] += xi[i];
70 }
71 }
72 let mut moved = false;
73 for j in 0..k {
74 if cnt[j] == 0 {
75 continue;
76 }
77 let nc = l2(&sum[j]);
78 if 1.0 - dot(&nc, &c[j]) > 1e-5 {
79 moved = true;
80 }
81 c[j] = nc;
82 }
83 if !moved {
84 break;
85 }
86 }
87 c
88}
89
90pub fn sinkhorn(m: &[Vec<f32>], eps: f32, iters: usize) -> (Vec<Vec<f32>>, f32) {
95 let n = m.len();
96 let k = m[0].len();
97 let kmat: Vec<Vec<f32>> = m.iter().map(|row| row.iter().map(|v| (-v / eps).exp()).collect()).collect();
98 let (a, b) = (1.0 / n as f32, 1.0 / k as f32);
99 let mut u = vec![1.0f32; n];
100 let mut v = vec![1.0f32; k];
101 for _ in 0..iters {
102 for i in 0..n {
103 let mut s = 0.0;
104 for j in 0..k {
105 s += kmat[i][j] * v[j];
106 }
107 u[i] = a / (s + 1e-12);
108 }
109 for j in 0..k {
110 let mut s = 0.0;
111 for i in 0..n {
112 s += kmat[i][j] * u[i];
113 }
114 v[j] = b / (s + 1e-12);
115 }
116 }
117 let mut cost = 0.0;
118 let pi: Vec<Vec<f32>> = (0..n)
119 .map(|i| {
120 (0..k)
121 .map(|j| {
122 let p = u[i] * kmat[i][j] * v[j];
123 cost += p * m[i][j];
124 p
125 })
126 .collect()
127 })
128 .collect();
129 (pi, cost)
130}
131
132pub fn sinkhorn_weighted(m: &[Vec<f32>], eps: f32, iters: usize, support: &[f32]) -> (Vec<Vec<f32>>, f32) {
153 let n = m.len();
154 let k = m[0].len();
155 let kmat: Vec<Vec<f32>> = m.iter().map(|row| row.iter().map(|v| (-v / eps).exp()).collect()).collect();
156
157 let mut a: Vec<f32> = (0..n)
160 .map(|i| support.get(i).copied().unwrap_or(0.0).max(0.0).sqrt())
161 .collect();
162 let total: f32 = a.iter().sum();
163 if total <= 0.0 {
164 a = vec![1.0 / n as f32; n];
165 } else {
166 for x in a.iter_mut() {
167 *x /= total;
168 }
169 }
170 let b = 1.0 / k as f32;
171
172 let mut u = vec![1.0f32; n];
173 let mut v = vec![1.0f32; k];
174 for _ in 0..iters {
175 for i in 0..n {
176 let mut s = 0.0;
177 for j in 0..k {
178 s += kmat[i][j] * v[j];
179 }
180 u[i] = a[i] / (s + 1e-12);
181 }
182 for j in 0..k {
183 let mut s = 0.0;
184 for i in 0..n {
185 s += kmat[i][j] * u[i];
186 }
187 v[j] = b / (s + 1e-12);
188 }
189 }
190 let mut cost = 0.0;
191 let pi: Vec<Vec<f32>> = (0..n)
192 .map(|i| {
193 (0..k)
194 .map(|j| {
195 let p = u[i] * kmat[i][j] * v[j];
196 cost += p * m[i][j];
197 p
198 })
199 .collect()
200 })
201 .collect();
202 (pi, cost)
203}
204
205pub fn codebook(x: &[Vec32], k: usize, eps: f32) -> (Vec<Vec32>, Vec<usize>, f32) {
207 if x.len() < 2 {
208 return (x.to_vec(), vec![0; x.len()], 0.0);
209 }
210 let protos = kmeans(x, k, 60, 1);
211 let m: Vec<Vec<f32>> = x.iter().map(|xi| protos.iter().map(|c| 1.0 - dot(xi, c)).collect()).collect();
212 let (pi, cost) = sinkhorn(&m, eps, 200);
213 let assign = pi
214 .iter()
215 .map(|row| {
216 let mut a = 0;
217 let mut bv = f32::NEG_INFINITY;
218 for (j, &p) in row.iter().enumerate() {
219 if p > bv {
220 bv = p;
221 a = j;
222 }
223 }
224 a
225 })
226 .collect();
227 (protos, assign, cost)
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn weighted_sinkhorn_respects_hyperedge_support() {
236 let cost = vec![vec![0.05f32, 0.95], vec![0.10, 0.90], vec![0.95, 0.05]];
237 let support = vec![100.0f32, 50.0, 2.0];
238 let (uni, _) = sinkhorn(&cost, 0.1, 80);
239 let (wei, _) = sinkhorn_weighted(&cost, 0.1, 80, &support);
240 let row_mass = |p: &Vec<Vec<f32>>, i: usize| p[i].iter().sum::<f32>();
241 assert!((row_mass(&uni, 0) - row_mass(&uni, 2)).abs() < 1e-4);
242 assert!(row_mass(&wei, 0) > row_mass(&wei, 2) * 3.0);
243 assert!(wei[0][0] > wei[0][1]);
244 assert!(wei[2][1] > wei[2][0]);
245 }
246
247 #[test]
248 fn weighted_sinkhorn_falls_back_to_uniform_without_support() {
249 let cost = vec![vec![0.1f32, 0.9], vec![0.9, 0.1]];
250 let (a, _) = sinkhorn(&cost, 0.1, 50);
251 let (b, _) = sinkhorn_weighted(&cost, 0.1, 50, &[0.0, 0.0]);
252 for i in 0..2 { for j in 0..2 { assert!((a[i][j]-b[i][j]).abs() < 1e-5); } }
253 }
254}