aria_router_algorithm/
elo.rs1use std::collections::HashMap;
4use std::sync::Mutex;
5
6#[derive(Debug, Default)]
8pub struct EloTable {
9 inner: Mutex<HashMap<String, f32>>,
10}
11
12impl EloTable {
13 pub fn rating(&self, model: &str) -> f32 {
14 self.inner
15 .lock()
16 .unwrap()
17 .get(model)
18 .copied()
19 .unwrap_or(1000.0)
20 }
21
22 pub fn set(&self, model: &str, rating: f32) {
23 self.inner.lock().unwrap().insert(model.to_string(), rating);
24 }
25
26 pub fn update_pair(&self, winner: &str, loser: &str) {
28 let mut g = self.inner.lock().unwrap();
29 let rw = *g.get(winner).unwrap_or(&1000.0);
30 let rl = *g.get(loser).unwrap_or(&1000.0);
31 let ew = 1.0 / (1.0 + 10f32.powf((rl - rw) / 400.0));
32 let el = 1.0 - ew;
33 let k = 24.0;
34 g.insert(winner.to_string(), rw + k * (1.0 - ew));
35 g.insert(loser.to_string(), rl + k * (0.0 - el));
36 }
37
38 pub fn observe_latency(&self, model: &str, latency_ms: f32, peers: &[String]) {
40 if peers.is_empty() {
41 return;
42 }
43 let mut g = self.inner.lock().unwrap();
44 let r = *g.get(model).unwrap_or(&1000.0);
45 let score = if latency_ms < 100.0 {
47 1.0
48 } else if latency_ms < 500.0 {
49 0.5
50 } else {
51 0.0
52 };
53 let expected = 0.5;
54 g.insert(model.to_string(), r + 16.0 * (score - expected));
55 let _ = peers;
56 }
57}
58
59pub fn global_elo() -> &'static EloTable {
61 use std::sync::OnceLock;
62 static TABLE: OnceLock<EloTable> = OnceLock::new();
63 TABLE.get_or_init(EloTable::default)
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn update_pair_and_latency() {
72 let t = EloTable::default();
73 assert!((t.rating("a") - 1000.0).abs() < 1e-3);
74 t.update_pair("a", "b");
75 assert!(t.rating("a") > t.rating("b"));
76 t.set("c", 1000.0);
77 t.observe_latency("c", 50.0, &["peer".into()]);
78 let fast = t.rating("c");
79 assert!(fast > 1000.0);
80 t.observe_latency("c", 600.0, &["peer".into()]);
81 assert!(t.rating("c") < fast);
82 t.observe_latency("d", 10.0, &[]); assert!((t.rating("d") - 1000.0).abs() < 1e-3);
84 let _ = global_elo().rating("x");
85 }
86}