use crate::types::Atom;
use serde::{Deserialize, Serialize};
pub const DEFAULT_SELF_IMPROVING_DECAY: f32 = 0.001;
fn now_micros() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_micros() as u64)
.unwrap_or_default()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DAVOAtom {
Static(Atom),
Decaying {
value: Atom,
stored_at: u64,
decay_rate: f32,
decay_context: Vec<f32>,
},
SelfImproving {
value: Atom,
stored_at: u64,
predictor_id: String,
observations: Vec<DecayObservation>,
},
Thunk {
thunk_id: u64,
created_at: u64,
probation_deadline: u64,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecayObservation {
pub query_time: u64,
pub predicted_freshness: f32,
pub actual_error: f32,
}
#[derive(Debug, Clone)]
pub struct DecayedValue {
pub value: Atom,
pub freshness: f32,
pub age_micros: u64,
pub decay_rate: f32,
}
impl DAVOAtom {
pub fn static_value(value: Atom) -> Self {
DAVOAtom::Static(value)
}
pub fn decaying(value: Atom, decay_rate: f32) -> Self {
DAVOAtom::Decaying {
value,
stored_at: now_micros(),
decay_rate,
decay_context: Vec::new(),
}
}
pub fn freshness_at(&self, query_time: u64) -> f32 {
match self {
DAVOAtom::Static(_) => 1.0,
DAVOAtom::Decaying {
stored_at,
decay_rate,
..
} => {
let age = query_time.saturating_sub(*stored_at);
let age_secs = age as f32 / 1_000_000.0;
(-decay_rate * age_secs).exp()
}
DAVOAtom::SelfImproving { stored_at, .. } => {
let age = query_time.saturating_sub(*stored_at);
let age_secs = age as f32 / 1_000_000.0;
(-DEFAULT_SELF_IMPROVING_DECAY * age_secs).exp()
}
DAVOAtom::Thunk {
probation_deadline, ..
} => {
if query_time > *probation_deadline {
0.0
} else {
1.0
}
}
}
}
pub fn freshness_at_with_predictor(
&self,
query_time: u64,
predictors: &std::collections::HashMap<String, super::DecayPredictor>,
) -> f32 {
match self {
DAVOAtom::SelfImproving {
stored_at,
predictor_id,
..
} => {
let decay_rate = predictors
.get(predictor_id)
.map(|p| p.predict())
.unwrap_or(DEFAULT_SELF_IMPROVING_DECAY);
let age = query_time.saturating_sub(*stored_at);
let age_secs = age as f32 / 1_000_000.0;
(-decay_rate * age_secs).exp()
}
_ => self.freshness_at(query_time),
}
}
pub fn value(&self) -> Option<&Atom> {
match self {
DAVOAtom::Static(v) => Some(v),
DAVOAtom::Decaying { value, .. } => Some(value),
DAVOAtom::SelfImproving { value, .. } => Some(value),
DAVOAtom::Thunk { .. } => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_static_freshness() {
let atom = DAVOAtom::static_value(Atom::Float(3.14));
assert_eq!(atom.freshness_at(0), 1.0);
assert_eq!(atom.freshness_at(u64::MAX), 1.0);
}
#[test]
fn test_decaying_freshness() {
let atom = DAVOAtom::Decaying {
value: Atom::Float(3.14),
stored_at: 0,
decay_rate: 1.0, decay_context: vec![],
};
assert!((atom.freshness_at(0) - 1.0).abs() < 0.001);
assert!((atom.freshness_at(1_000_000) - 0.368).abs() < 0.01);
}
}