aboutsummaryrefslogtreecommitdiff
path: root/src/learner.rs
diff options
context:
space:
mode:
authortslil <tslil@posteo.de>2026-07-14 20:22:56 +0100
committertslil <tslil@posteo.de>2026-07-14 21:25:04 +0100
commitfc5789e026473ea82237be847672ecb350e64479 (patch)
tree6210659eb424124b55bd67a63397a37931e50b5f /src/learner.rs
parent156cbfe2b0383521f540109682d98d35c88d65f8 (diff)
license, simplify code, add README, lose "learner" naming
Diffstat (limited to 'src/learner.rs')
-rw-r--r--src/learner.rs158
1 files changed, 0 insertions, 158 deletions
diff --git a/src/learner.rs b/src/learner.rs
deleted file mode 100644
index 942d52e..0000000
--- a/src/learner.rs
+++ /dev/null
@@ -1,158 +0,0 @@
-use std::collections::{HashMap, HashSet};
-
-use rand::{self, seq::IndexedRandom};
-use serde::{Deserialize, Serialize};
-
-use crate::{config::Config, trajectory::Action};
-
-fn compute_weight(past: &HashSet<String>, now: &HashSet<&String>) -> f32 {
- let inter = now.iter().filter(|&&p| past.contains(p)).count();
- if inter == 0 {
- return 0.0;
- }
- (inter as f32) / ((now.len() as f32).sqrt() * (past.len() as f32).sqrt())
-}
-
-fn softmax_sample(items: &[(String, f32)], temperature: f32) -> Option<(String, f32)> {
- if items.is_empty() {
- return None;
- }
- let mut rng = rand::rng();
-
- let max_val = items
- .iter()
- .map(|(_, v)| *v)
- .fold(f32::NEG_INFINITY, f32::max);
-
- let exp_values: Vec<_> = items
- .iter()
- .map(|(k, val)| (k, ((val - max_val) / temperature).exp()))
- .collect();
-
- let sum: f32 = exp_values.iter().map(|(_, v)| *v).sum();
-
- let norm_values: Vec<_> = exp_values.iter().map(|&(k, v)| (k, v / sum)).collect();
-
- norm_values
- .choose_weighted(&mut rng, |item| item.1)
- .map(|p| (p.0.clone(), p.1))
- .ok()
-}
-
-#[derive(Serialize, Deserialize)]
-enum Episode {
- Continue {
- group: HashSet<String>,
- avoid: Option<String>,
- committed: String,
- },
- Escape {
- from: HashSet<String>,
- to: String,
- committed: String,
- },
-}
-
-#[derive(Serialize, Deserialize, Default)]
-pub struct Learner {
- history: Vec<Episode>,
-}
-
-pub enum Learning {
- SkipToMore(Vec<String>, String),
- MoreToSkip(Vec<String>, String),
-}
-
-impl Learner {
- pub fn learn(&mut self, learning: &Learning) {
- let stamp = chrono::Utc::now().format("%+").to_string();
- match learning {
- Learning::SkipToMore(trajectory, new) => {
- self.history.push(Episode::Escape {
- from: trajectory.iter().map(String::clone).collect(),
- to: new.clone(),
- committed: stamp,
- });
- }
- Learning::MoreToSkip(trajectory, new) => {
- self.history.push(Episode::Continue {
- group: trajectory.iter().map(String::clone).collect(),
- avoid: Some(new.clone()),
- committed: stamp,
- });
- }
- }
- }
-
- pub fn sample(
- &self,
- trajectory: &[String],
- action: &Action,
- candidates: &HashSet<String>,
- config: &Config,
- ) -> Option<(String, f32)> {
- let trajectory: HashSet<_> = trajectory.iter().collect();
-
- let candidates: Vec<_> = candidates
- .iter()
- .filter(|c| !trajectory.contains(c))
- .collect();
-
- if candidates.is_empty() {
- return None;
- }
-
- let mut items: HashMap<String, f32> =
- candidates.into_iter().map(|c| (c.clone(), 0.0)).collect();
-
- for episode in &self.history {
- match (episode, action) {
- (Episode::Escape { from, to, .. }, Action::Skip) => {
- let w = compute_weight(from, &trajectory);
- if let Some(to_w) = items.get_mut(to) {
- *to_w += config.max_weight * w;
- }
- for f in from {
- if let Some(from_weight) = items.get_mut(f) {
- *from_weight -= config.mid_weight * w;
- }
- }
- }
- (Episode::Escape { from, to, .. }, Action::More) => {
- let w = compute_weight(from, &trajectory);
- if trajectory.contains(to) {
- for f in from {
- if let Some(f_w) = items.get_mut(f) {
- *f_w -= config.low_weight * w;
- }
- }
- }
- }
- (Episode::Continue { group, avoid, .. }, Action::More) => {
- let w = compute_weight(group, &trajectory);
- for g in group {
- if let Some(g_w) = items.get_mut(g) {
- *g_w += config.max_weight * w;
- }
- }
- if let Some(a) = avoid
- && let Some(a_w) = items.get_mut(a)
- {
- *a_w -= config.low_weight * w;
- }
- }
- (Episode::Continue { group, .. }, Action::Skip) => {
- let w = compute_weight(group, &trajectory);
- for g in group {
- if let Some(v) = items.get_mut(g) {
- *v -= config.high_weight * w;
- }
- }
- }
- }
- }
-
- let pairs: Vec<_> = items.into_iter().collect();
- softmax_sample(&pairs, config.temperature)
- }
-}