pub mod policy;
pub mod replay;
pub mod summary;
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::attention;
use crate::conscience::{self, Challenge};
use crate::outcome;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Policy {
Fixed,
Cap(usize),
Attention,
Learned,
}
impl Policy {
#[must_use]
pub fn name(self) -> &'static str {
match self {
Policy::Fixed => "fixed",
Policy::Cap(_) => "cap",
Policy::Attention => "attention",
Policy::Learned => "learned",
}
}
}
#[must_use]
pub fn policy() -> Policy {
precedence(std::env::var("BORG_SELECT").ok().as_deref(), conscience::ask_cap(), attention::on())
}
#[must_use]
pub fn precedence(select: Option<&str>, cap: Option<usize>, attention: bool) -> Policy {
match (select, cap) {
(Some("fixed"), _) => Policy::Fixed,
(_, Some(n)) => Policy::Cap(n),
_ if attention => Policy::Attention,
_ => Policy::Learned,
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Route {
Ask,
Free,
Placebo,
#[default]
Not,
}
impl Route {
#[must_use]
pub fn name(self) -> &'static str {
match self {
Route::Ask => "ask",
Route::Free => "free",
Route::Placebo => "placebo",
Route::Not => "not",
}
}
#[must_use]
pub const fn arm(self) -> usize {
match self {
Route::Ask => 0,
Route::Free => 1,
Route::Placebo | Route::Not => 2,
}
}
}
pub const ARMS: usize = 3;
#[derive(Debug, Clone, PartialEq)]
pub struct Unit<'a> {
pub lesson: &'a str,
pub grade: u8,
pub rank: usize,
}
#[must_use]
pub fn units<'a>(applied: &[(&'a Challenge, u8)]) -> Vec<Unit<'a>> {
applied.iter().enumerate().map(|(rank, (c, g))| Unit { lesson: c.id.as_str(), grade: *g, rank }).collect()
}
pub struct Ctx<'a> {
pub session: &'a str,
pub check: &'a str,
pub channel: &'static str,
pub lessons: &'a Path,
pub summary: &'a summary::Summary,
pub tokens: u32,
pub had: &'a [String],
pub alpha: f64,
}
#[must_use]
pub fn shown_tokens(shown: &[&Challenge]) -> u32 {
let sum: usize = shown.iter().map(|c| crate::whole::cost(c)).sum();
sum.try_into().unwrap_or(u32::MAX)
}
#[must_use]
pub fn nofree(applied: &[(&Challenge, u8)], had: &[String]) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
for (c, _) in applied {
if had.contains(&c.id) && !out.contains(&c.id) {
out.push(c.id.clone());
}
}
out
}
#[must_use]
pub fn logged_alpha(alpha: f64) -> Option<f64> {
(alpha != policy::ALPHA).then_some(alpha)
}
#[must_use]
pub fn alpha_of(logged: Option<f64>) -> f64 {
logged.unwrap_or(policy::ALPHA)
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Log {
pub policy: &'static str,
pub source: &'static str,
pub xk: Option<u32>,
pub xs: Option<u32>,
pub alpha: Option<f64>,
pub fit: String,
pub gate: f64,
pub risk: f64,
pub attention: Option<String>,
pub psi: f64,
}
impl Log {
#[must_use]
pub fn line(&self, applied: usize, check: &str) -> String {
let fit = if self.fit.is_empty() { "-" } else { self.fit.as_str() };
format!("#borg-select policy={} source={} fit={fit} gate={:.4} applied={applied} @{check}", self.policy, self.source, self.gate)
}
}
#[derive(Debug, Clone)]
pub struct Routed<'a> {
pub ask: Vec<&'a Challenge>,
pub free: Vec<&'a Challenge>,
pub placebo: Vec<&'a Challenge>,
pub p: Vec<f64>,
pub pf: Vec<f64>,
pub log: Log,
}
impl Routed<'_> {
#[must_use]
pub fn route(&self, id: &str) -> Route {
let has = |v: &[&Challenge]| v.iter().any(|c| c.id == id);
if has(&self.ask) {
Route::Ask
} else if has(&self.free) {
Route::Free
} else if has(&self.placebo) {
Route::Placebo
} else {
Route::Not
}
}
}
#[must_use]
pub fn graded_under<'a>(policy: Policy, applied: &[(&'a Challenge, u8)], ctx: &Ctx) -> Routed<'a> {
let three = applied.iter().any(|(_, g)| *g == 3);
let fixed = |source: &'static str, placebos: bool, (ask, free): (Vec<&'a Challenge>, Vec<&'a Challenge>)| {
let (placebo, psi) = if placebos { draw_placebo(applied, &ask, &free, ctx) } else { (Vec::new(), 0.0) };
Routed {
p: attention::fixed_p(applied, &ask),
pf: attention::fixed_p(applied, &free),
ask,
free,
placebo,
log: Log { policy: policy.name(), source, fit: ctx.summary.fit.key.clone(), psi, ..Log::default() },
}
};
let mut r = match policy {
Policy::Attention if three => by_cut(applied, ctx),
Policy::Cap(n) => fixed("cap", false, conscience::graded_without(applied, Some(n), ctx.had)),
Policy::Learned => learned(applied, ctx),
Policy::Fixed => fixed("fixed", true, conscience::graded_without(applied, None, ctx.had)),
Policy::Attention => fixed("fixed", false, conscience::graded_without(applied, None, ctx.had)),
};
r.log.alpha = logged_alpha(ctx.alpha);
r
}
pub const PLACEBO_SLOTS: usize = 2;
fn draw_placebo<'a>(applied: &[(&'a Challenge, u8)], ask: &[&Challenge], free: &[&Challenge], ctx: &Ctx) -> (Vec<&'a Challenge>, f64) {
let delivered = |id: &str| ask.iter().chain(free).any(|c| c.id == id);
let mut later: Vec<usize> = Vec::new();
for (j, (c, _)) in applied.iter().enumerate() {
if !delivered(&c.id) && !ctx.had.contains(&c.id) && !later.iter().any(|&i| applied[i].0.id == c.id) {
later.push(j);
}
}
let mut drawn = later.clone();
let mut rng = crate::memory::Rng::new(seed(ctx) ^ 0x706c_6163_6562_6f00);
for i in (1..drawn.len()).rev() {
drawn.swap(i, (rng.next_u64() % (i as u64 + 1)) as usize);
}
drawn.truncate(PLACEBO_SLOTS);
drawn.sort_unstable();
let psi = if later.is_empty() { 0.0 } else { outcome::round_p((PLACEBO_SLOTS as f64 / later.len() as f64).min(1.0)) };
(drawn.iter().map(|&j| applied[j].0).collect(), psi)
}
pub const REVERTED_SOURCE: &str = "reverted";
fn learned<'a>(applied: &[(&'a Challenge, u8)], ctx: &Ctx) -> Routed<'a> {
let us = units(applied);
let s = ctx.summary;
let check = policy::Check { session: ctx.session, stop: ctx.channel == "stop", tokens: ctx.tokens };
let d = match s.monitor.reverted {
true => {
let base = policy::fixed_routes(&us, ctx.had);
let p = |r: Route| base.iter().map(|x| f64::from(u8::from(*x == r))).collect();
policy::Decision { p: p(Route::Ask), pf: p(Route::Free), routes: base, source: REVERTED_SOURCE, gate: 0.0, risk: 0.0, xk: None, xs: None }
}
false => policy::choose(&us, &check, &s.fit, &s.gate, ctx.alpha, seed(ctx), ctx.had),
};
let (ask, free) = ordered(applied, &d.routes);
let (placebo, psi) = draw_placebo(applied, &ask, &free, ctx);
Routed {
ask,
free,
placebo,
p: d.p,
pf: d.pf,
log: Log {
policy: Policy::Learned.name(),
source: d.source,
fit: s.fit.key.clone(),
gate: d.gate,
risk: d.risk,
xk: d.xk,
xs: d.xs,
alpha: None,
attention: None,
psi,
},
}
}
fn by_cut<'a>(applied: &[(&'a Challenge, u8)], ctx: &Ctx) -> Routed<'a> {
let outcomes = outcome::read(&outcome::path_beside(ctx.lessons));
let base = attention::cut(&attention::labels(&outcomes));
let (p, pf) = (attention::propensities(applied.len(), base.k), free_p(applied.len(), base.k));
let checks = outcomes.iter().filter(|o| o.kind == outcome::GRADED && !o.lessons.is_empty() && !o.failed).count();
let cut = match attention::explores(checks) {
true => attention::Cut { k: attention::RAIL, source: "explore", ..base },
false => base,
};
let (ask, free) = attention::split(applied, &cut);
let log = Log {
policy: Policy::Attention.name(),
source: cut.source,
fit: ctx.summary.fit.key.clone(),
attention: Some(attention::line(&cut, applied.len())),
..Log::default()
};
Routed { ask, free, placebo: Vec::new(), p, pf, log }
}
fn free_p(n: usize, k: usize) -> Vec<f64> {
let q = 1.0 / attention::EXPLORE as f64;
let (cut, rail) = (k.min(attention::RAIL).min(n), attention::RAIL.min(n));
let within = |r: usize, from: usize| f64::from(u8::from(r >= from && r < from + conscience::CAP));
(0..n).map(|r| outcome::round_p((1.0 - q) * within(r, cut) + q * within(r, rail))).collect()
}
fn ordered<'a>(applied: &[(&'a Challenge, u8)], routes: &[Route]) -> (Vec<&'a Challenge>, Vec<&'a Challenge>) {
let mut order: Vec<usize> = (0..applied.len().min(routes.len())).collect();
order.sort_by_key(|&i| std::cmp::Reverse(applied[i].1));
let of = |r: Route| order.iter().filter(|&&i| routes[i] == r).map(|&i| applied[i].0).collect();
(of(Route::Ask), of(Route::Free))
}
#[must_use]
pub fn seed(ctx: &Ctx) -> u64 {
let mut h = blake3::Hasher::new();
h.update(b"borg-select-seed-v1\0");
for part in [ctx.session, ctx.channel, ctx.check] {
h.update(part.as_bytes());
h.update(b"\0");
}
let bytes = h.finalize();
const WIDTH: usize = std::mem::size_of::<u64>();
let mut first = [0u8; WIDTH];
first.copy_from_slice(&bytes.as_bytes()[..WIDTH]);
u64::from_le_bytes(first)
}
#[must_use]
pub fn pool_tokens(s: &summary::Summary, limit_ms: Option<u64>, session: &str) -> usize {
pool_tokens_under(policy(), s, limit_ms, session)
}
#[must_use]
pub fn pool_tokens_under(policy: Policy, s: &summary::Summary, limit_ms: Option<u64>, session: &str) -> usize {
policy::pool_tokens(&s.fit, pool_active(policy, s), limit_ms, policy::session_seed(session))
}
fn pool_active(policy: Policy, s: &summary::Summary) -> bool {
policy == Policy::Learned && !s.monitor.reverted && (s.gate.allowance > 0.0 || s.fit.budget.is_some())
}
#[must_use]
pub fn budget_of(lessons: &Path) -> Option<f64> {
budget_in(&crate::collective::load().ok().flatten()?, lessons)
}
fn budget_in(cfg: &crate::collective::Config, lessons: &Path) -> Option<f64> {
entry_in(cfg, lessons)?.budget()
}
#[must_use]
pub fn risk_of(lessons: &Path) -> Option<f64> {
risk_in(&crate::collective::load().ok().flatten()?, lessons)
}
fn risk_in(cfg: &crate::collective::Config, lessons: &Path) -> Option<f64> {
entry_in(cfg, lessons)?.risk()
}
#[must_use]
pub fn alpha(lessons: &Path) -> f64 {
risk_of(lessons).unwrap_or(policy::ALPHA)
}
fn entry_in<'a>(cfg: &'a crate::collective::Config, lessons: &Path) -> Option<&'a crate::collective::Entry> {
let store = lessons.parent()?;
let base = crate::collective::root_of(store);
cfg.collectives.iter().find(|c| crate::collective::store(&base, &c.name) == store)
}
pub const CHECK_HEX: usize = 12;
#[must_use]
pub fn check_id(session: &str, channel: &str, start_ms: i64, pid: u32) -> String {
let mut h = blake3::Hasher::new();
h.update(b"borg-check-v1\0");
h.update(format!("{session}\0{channel}\0{start_ms}\0{pid}").as_bytes());
h.finalize().to_hex()[..CHECK_HEX].to_string()
}
#[cfg(test)]
mod tests {
use super::*;
fn lessons(n: usize) -> Vec<Challenge> {
(0..n).map(|i| Challenge { id: format!("L{i:02}"), ..Default::default() }).collect()
}
fn ids(v: &[&Challenge]) -> Vec<String> {
v.iter().map(|c| c.id.clone()).collect()
}
fn store(name: &str, outcomes: &[outcome::Outcome]) -> std::path::PathBuf {
let d = std::env::temp_dir().join(format!("borg-select-{name}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&d);
std::fs::create_dir_all(&d).unwrap();
let text: String = outcomes.iter().map(|o| format!("{}\n", serde_json::to_string(o).unwrap())).collect();
std::fs::write(d.join("outcomes.jsonl"), text).unwrap();
d.join("lessons.jsonl")
}
fn ctx<'a>(lessons: &'a Path, s: &'a summary::Summary) -> Ctx<'a> {
Ctx { session: "s", check: "0123456789ab", channel: "stop", lessons, summary: s, tokens: 0, had: &[], alpha: policy::ALPHA }
}
fn tables(n: usize) -> Vec<Vec<u8>> {
let mut out = vec![Vec::new()];
for len in 1..=n {
out.extend((0..3usize.pow(len as u32)).map(|mut x| {
(0..len)
.map(|_| {
let g = (x % 3) as u8 + 1;
x /= 3;
g
})
.collect()
}));
}
out
}
#[test]
fn the_switches_take_precedence_in_order() {
assert_eq!(precedence(None, None, false), Policy::Learned, "the default is the learned policy");
assert_eq!(precedence(Some("learned"), None, false), Policy::Learned, "naming it changes nothing");
assert_eq!(precedence(Some("fixed"), Some(2), true), Policy::Fixed, "fixed wins over both evaluation switches");
assert_eq!(precedence(Some("learned"), Some(2), true), Policy::Cap(2), "the cap wins over attention and learned");
assert_eq!(precedence(Some("learned"), None, true), Policy::Attention, "attention wins over learned");
assert_eq!(precedence(Some("nonsense"), None, false), Policy::Learned, "an unknown BORG_SELECT is the default, the learned policy");
assert_eq!(precedence(Some("Fixed"), None, false), Policy::Learned, "the value is exact");
assert_eq!(precedence(Some("fixed"), None, false), Policy::Fixed, "BORG_SELECT=fixed is the way back");
assert_eq!([Policy::Fixed, Policy::Cap(1), Policy::Attention, Policy::Learned].map(Policy::name), ["fixed", "cap", "attention", "learned"]);
}
fn candidates(applied: &[(&Challenge, u8)], r: &Routed, had: &[String]) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
for (c, _) in applied {
if matches!(r.route(&c.id), Route::Not | Route::Placebo) && !had.contains(&c.id) && !out.contains(&c.id) {
out.push(c.id.clone());
}
}
out
}
#[test]
fn every_policy_without_a_cut_is_the_fixed_rule_on_every_grade_table() {
let ls = lessons(14);
let s = summary::Summary::default();
let none = std::path::PathBuf::from("/nonexistent/borg-select/lessons.jsonl");
let mut tables = tables(7);
tables.extend([vec![3; 12], [vec![1; 6], vec![3], vec![2; 7]].concat(), vec![2; 14], [vec![1; 11], vec![2; 3]].concat()]);
let some: Vec<String> = ["L00", "L03", "L11", "X"].map(String::from).to_vec();
for had in [&[][..], &some[..]] {
for grades in &tables {
let applied: Vec<(&Challenge, u8)> = ls.iter().zip(grades.iter().copied()).collect();
let at = Ctx { had, ..ctx(&none, &s) };
for (policy, cap) in [(Policy::Fixed, None), (Policy::Learned, None), (Policy::Cap(2), Some(2))] {
let r = graded_under(policy, &applied, &at);
let (ask, free) = conscience::graded_without(&applied, cap, had);
assert_eq!((ids(&r.ask), ids(&r.free)), (ids(&ask), ids(&free)), "{policy:?} {grades:?} {had:?}");
if had.is_empty() {
let (ask, free) = conscience::graded_capped(&applied, cap);
assert_eq!((ids(&r.ask), ids(&r.free)), (ids(&ask), ids(&free)), "no lesson had: the rule of every older record");
}
assert_eq!(r.p, attention::fixed_p(&applied, &ask), "{policy:?} {grades:?}");
assert_eq!(r.pf, attention::fixed_p(&applied, &free), "{policy:?} {grades:?}");
assert!(r.log.attention.is_none() && r.log.gate == 0.0);
assert_eq!(r.log.policy, policy.name());
let later = candidates(&applied, &r, had);
match policy {
Policy::Cap(_) => assert!(r.placebo.is_empty() && r.log.psi == 0.0),
_ => {
assert_eq!(r.placebo.len(), later.len().min(PLACEBO_SLOTS), "{policy:?} {grades:?}");
assert!(ids(&r.placebo).iter().all(|p| later.contains(p)), "{policy:?} {grades:?}");
let psi = if later.is_empty() { 0.0 } else { outcome::round_p((PLACEBO_SLOTS as f64 / later.len() as f64).min(1.0)) };
assert_eq!(r.log.psi, psi);
}
}
}
if !grades.contains(&3) {
let r = graded_under(Policy::Attention, &applied, &at);
let (ask, free) = conscience::graded_without(&applied, None, had);
assert_eq!((ids(&r.ask), ids(&r.free), r.log.source), (ids(&ask), ids(&free), "fixed"), "no 3: no cut");
assert!(r.placebo.is_empty() && r.log.psi == 0.0, "an evaluation switch draws no placebo");
}
}
}
}
#[test]
fn attention_with_a_three_is_the_cut_on_the_outcomes_beside_the_lessons() {
let ls = lessons(14);
let s = summary::Summary::default();
let grades: Vec<u8> = std::iter::once(3).chain(std::iter::repeat_n(1, 13)).collect();
let applied: Vec<(&Challenge, u8)> = ls.iter().zip(grades).collect();
let cold = store("attention-cold", &[]);
let r = graded_under(Policy::Attention, &applied, &ctx(&cold, &s));
assert_eq!((r.ask.len(), ids(&r.free)), (attention::PRIOR_K, ids(&applied[attention::PRIOR_K..].iter().map(|(c, _)| *c).collect::<Vec<_>>())));
assert_eq!(r.p, attention::propensities(14, attention::PRIOR_K));
assert_eq!(&r.pf[..attention::PRIOR_K], &[0.0; attention::PRIOR_K]);
assert_eq!(&r.pf[attention::PRIOR_K..], &[0.9; 3], "free unless the check explores, which asks them");
assert_eq!((r.log.policy, r.log.source), ("attention", "prior"));
assert_eq!(r.log.attention.as_deref(), Some("#borg-attention k=11 n=0 alpha=0.0010 source=prior applied=14"));
let graded = outcome::Outcome { kind: outcome::GRADED.into(), session: "x".into(), lessons: vec!["L0".into()], ..Default::default() };
let nine = store("attention-explore", &vec![graded.clone(); attention::EXPLORE - 1]);
let r = graded_under(Policy::Attention, &applied, &ctx(&nine, &s));
assert_eq!((r.ask.len(), r.free.len(), r.log.source), (14, 0, "explore"));
assert_eq!(r.p, attention::propensities(14, attention::PRIOR_K), "the propensities are the mixture's, not the override's");
let mut dirs = vec![cold, nine];
let (empty, failed) = (outcome::Outcome { lessons: Vec::new(), ..graded.clone() }, outcome::Outcome { failed: true, ..graded.clone() });
for (tag, ninth) in [("empty", empty), ("failed", failed)] {
let mut log = vec![graded.clone(); attention::EXPLORE - 2];
log.push(ninth);
let unmoved = store(&format!("attention-{tag}"), &log);
assert_eq!(graded_under(Policy::Attention, &applied, &ctx(&unmoved, &s)).log.source, "prior", "{tag}");
dirs.push(unmoved);
}
for d in dirs {
let _ = std::fs::remove_dir_all(d.parent().unwrap());
}
}
#[test]
fn free_propensities_mix_the_cut_and_the_explore_slice() {
let pf = free_p(35, 3);
assert_eq!(&pf[..3], &[0.0; 3]);
assert_eq!(&pf[3..13], &[0.9; 10]);
assert_eq!(&pf[13..20], &[0.0; 7]);
assert_eq!(&pf[20..30], &[0.1; 10]);
assert_eq!(&pf[30..], &[0.0; 5]);
assert_eq!(free_p(5, 11), [0.0; 5], "the cut and the rail ask all five");
let p = attention::propensities(35, 3);
assert!(p.iter().zip(&pf).all(|(a, f)| a + f <= 1.0 + 1e-9), "{p:?} {pf:?}");
}
#[test]
fn a_route_names_the_list_that_holds_the_lesson() {
let ls = lessons(3);
let applied: Vec<(&Challenge, u8)> = ls.iter().zip([3, 1, 2]).collect();
let none = std::path::PathBuf::from("/nonexistent/borg-select/lessons.jsonl");
let s = summary::Summary::default();
let mut r = graded_under(Policy::Fixed, &applied, &ctx(&none, &s));
assert_eq!(ids(&r.ask), ["L00", "L02", "L01"], "3s, then 2s, then 1s");
r.free = r.ask.split_off(2);
r.placebo = r.free.split_off(0);
assert_eq!(["L00", "L01", "L02", "L99"].map(|id| r.route(id)), [Route::Ask, Route::Placebo, Route::Ask, Route::Not]);
}
#[test]
fn the_select_line_carries_the_decision_and_its_check() {
let mut log = Log { policy: "learned", source: "fixed", ..Log::default() };
assert_eq!(log.line(4, "0123456789ab"), "#borg-select policy=learned source=fixed fit=- gate=0.0000 applied=4 @0123456789ab");
log.fit = "120-ab12".into();
log.gate = 0.25;
assert!(log.line(0, "ff").contains(" fit=120-ab12 gate=0.2500 applied=0 @ff"), "{}", log.line(0, "ff"));
assert_eq!(conscience::work_text(&format!("{}\nwork\n", log.line(1, "ab"))), "work\n");
}
fn deviating() -> summary::Summary {
let at = |name: &str| policy::NAMES.iter().position(|n| *n == name).unwrap();
let mut beta = vec![0.0; policy::P];
beta[at("intercept")] = -0.5;
beta[at("ask")] = -3.0;
beta[at("not")] = -1.0;
let mut chol = vec![0.0; policy::P * policy::P];
for k in 0..policy::P {
chol[k * policy::P + k] = 0.05;
}
let model = policy::Model { beta, chol, sd: vec![1.0; policy::P], spread: [0.1; 3], ..policy::Model::default() };
let rates = policy::Rates { free_stop: 1.0, free_live: 1.0, later: 0.0, wait: 0.0, ask_live: 1.0 };
let fit = policy::Fit { key: "9-abcdef01".into(), model: Some(model), rates, ..policy::Fit::default() };
let gate = summary::Gate { gain: vec![0.0; policy::DRAWS], allowance: 5.0, ..summary::Gate::default() };
summary::Summary { fit, gate, ..summary::Summary::default() }
}
#[test]
fn the_learned_policy_routes_by_its_fit_and_logs_it() {
let ls = lessons(4);
let applied: Vec<(&Challenge, u8)> = ls.iter().zip([3, 1, 2, 1]).collect();
let s = deviating();
let none = std::path::PathBuf::from("/nonexistent/borg-select/lessons.jsonl");
let live = Ctx { channel: "live", ..ctx(&none, &s) };
let r = graded_under(Policy::Learned, &applied, &live);
assert!(r.ask.is_empty(), "asking loses under this fit: {:?}", ids(&r.ask));
assert_eq!(ids(&r.free), ["L00", "L02", "L01", "L03"], "higher grade first, then the model's order");
assert_eq!((r.log.policy, r.log.source, r.log.fit.as_str()), ("learned", policy::EVIDENCE, "9-abcdef01"));
assert_eq!((r.log.xk, r.log.xs), (None, None), "on evidence the test does not run");
assert!(r.log.gate > 0.0);
assert!(r.pf.iter().all(|p| *p == 1.0) && r.p.iter().all(|p| *p == 0.0), "exact: {:?} {:?}", r.p, r.pf);
let r = graded_under(Policy::Learned, &applied, &ctx(&none, &s));
assert_eq!((ids(&r.ask), r.p[0]), (vec!["L00".to_string()], 1.0));
}
#[test]
fn a_live_check_under_the_test_asks_the_drawn_count_and_logs_it() {
let ls = lessons(4);
let applied: Vec<(&Challenge, u8)> = ls.iter().zip([2, 1, 2, 1]).collect();
let mut s = deviating();
if let Some(m) = s.fit.model.as_mut() {
m.beta = vec![0.0; policy::P];
m.beta[policy::NAMES.iter().position(|n| *n == "not").unwrap()] = -1.0;
}
let none = std::path::PathBuf::from("/nonexistent/borg-select/lessons.jsonl");
let order = ["L00", "L02", "L01", "L03"];
let mut seen = std::collections::BTreeSet::new();
for c in 0..40u32 {
let check = format!("{c:012x}");
let r = graded_under(Policy::Learned, &applied, &Ctx { channel: "live", check: &check, ..ctx(&none, &s) });
let (Some(k), Some(x)) = (r.log.xk, r.log.xs) else { panic!("check {c}: the test ran: {:?}", r.log) };
let x = x as usize;
assert_eq!(k, 4, "every lesson the fixed rule hands over is tested");
let named = |v: &[&str]| v.iter().map(|x| x.to_string()).collect::<Vec<_>>();
assert_eq!((ids(&r.ask), ids(&r.free)), (named(&order[..x]), named(&order[x..])), "check {c}");
assert_eq!(r.log.source, if x == 0 { policy::EXPERIMENT_CONTROL } else { policy::EXPERIMENT }, "check {c}");
assert_eq!(
(r.p.as_slice(), r.pf.as_slice()),
([0.8, 0.4, 0.6, 0.2].as_slice(), [0.2, 0.6, 0.4, 0.8].as_slice()),
"each unit's share of the five counts"
);
seen.insert(x);
}
assert!(seen.len() > 2, "the count varies with the check: {seen:?}");
let r = graded_under(Policy::Learned, &applied, &ctx(&none, &s));
assert_eq!((r.log.source, r.log.xk, r.log.xs, r.ask.len()), (policy::EVIDENCE, None, None, 0));
}
#[test]
fn a_check_decides_and_logs_under_the_tolerance_in_its_ctx() {
let ls = lessons(4);
let applied: Vec<(&Challenge, u8)> = ls.iter().zip([2, 1, 2, 1]).collect();
let mut s = deviating();
if let Some(m) = s.fit.model.as_mut() {
m.beta = vec![0.0; policy::P];
m.beta[policy::NAMES.iter().position(|n| *n == "not").unwrap()] = -1.0;
}
let none = std::path::PathBuf::from("/nonexistent/borg-select/lessons.jsonl");
assert_eq!(alpha(&none), policy::ALPHA, "the config's α for this path");
let live = Ctx { channel: "live", ..ctx(&none, &s) };
let at = |alpha: f64| graded_under(Policy::Learned, &applied, &Ctx { alpha, ..live });
let default = at(policy::ALPHA);
assert_eq!((default.log.xk, default.log.alpha), (Some(4), None), "the test runs; the default is not logged");
let off = at(0.0);
assert_eq!((off.log.source, off.log.xk, off.log.xs, off.log.alpha), (policy::EVIDENCE, None, None, Some(0.0)), "{:?}", off.log);
assert_eq!((off.ask.len(), off.free.len()), (0, 4), "the fixed rule");
let wider = at(0.3);
assert_eq!((wider.log.xk, wider.log.alpha), (Some(4), Some(0.3)));
let fixed = graded_under(Policy::Fixed, &applied, &Ctx { alpha: 0.0, ..live });
assert_eq!(fixed.log.alpha, Some(0.0), "logged under every policy, for the cold start");
for a in [0.0, 0.3, policy::ALPHA] {
assert_eq!(alpha_of(logged_alpha(a)), a);
let replayed = at(alpha_of(at(a).log.alpha));
assert_eq!((replayed.log, replayed.p, replayed.pf), (at(a).log, at(a).p, at(a).pf), "α {a} replays as decided");
}
}
#[test]
fn a_reverted_store_keeps_the_fixed_rule() {
let ls = lessons(4);
let applied: Vec<(&Challenge, u8)> = ls.iter().zip([3, 1, 2, 1]).collect();
let mut s = deviating();
s.monitor.reverted = true;
let none = std::path::PathBuf::from("/nonexistent/borg-select/lessons.jsonl");
let r = graded_under(Policy::Learned, &applied, &Ctx { channel: "live", ..ctx(&none, &s) });
let (ask, free) = conscience::graded_capped(&applied, None);
assert_eq!((ids(&r.ask), ids(&r.free)), (ids(&ask), ids(&free)));
assert_eq!((r.log.source, r.log.gate), (REVERTED_SOURCE, 0.0));
assert_eq!(r.p, attention::fixed_p(&applied, &ask));
let had = ["L01".to_string()];
let r = graded_under(Policy::Learned, &applied, &Ctx { channel: "live", had: &had, ..ctx(&none, &s) });
assert_eq!(ids(&r.free), ids(&conscience::graded_without(&applied, None, &had).1));
assert_eq!((r.log.source, r.pf[1]), (REVERTED_SOURCE, 0.0));
}
#[test]
fn a_lesson_the_session_had_is_asked_but_never_handed_over_free() {
let ls = lessons(12);
let none = std::path::PathBuf::from("/nonexistent/borg-select/lessons.jsonl");
let s = summary::Summary::default();
let soft: Vec<(&Challenge, u8)> = ls.iter().zip(std::iter::once(2).chain(std::iter::repeat_n(1, 11))).collect();
let had = ["L00".to_string(), "X9".to_string()];
let free = |had: &[String]| ids(&conscience::graded_without(&soft, None, had).1);
assert_eq!(free(&[]), ids(&ls[..10].iter().collect::<Vec<_>>()));
assert_eq!(free(&had), ids(&ls[1..11].iter().collect::<Vec<_>>()), "a 2 the session had takes no slot; the next 1 does");
for policy in [Policy::Fixed, Policy::Learned] {
let r = graded_under(policy, &soft, &Ctx { had: &had, ..ctx(&none, &s) });
assert_eq!((ids(&r.free), r.pf[0]), (free(&had), 0.0), "{policy:?}");
assert!(!ids(&r.placebo).contains(&"L00".to_string()), "{policy:?}: a lesson the session had is no placebo");
}
let block: Vec<(&Challenge, u8)> = ls.iter().take(3).zip([3, 2, 1]).collect();
let ask = |had: &[String]| ids(&conscience::graded_without(&block, None, had).0);
assert_eq!(ask(&["L01".to_string()]), ask(&[]), "may still be asked");
let open = deviating();
let applied: Vec<(&Challenge, u8)> = ls.iter().take(4).zip([2, 1, 2, 1]).collect();
let live = ctx(&none, &open);
let all = graded_under(Policy::Learned, &applied, &live);
assert_eq!((all.log.source, all.route("L01")), (policy::EVIDENCE, Route::Free), "the scenario: without it, L01 goes free");
let had = ["L01".to_string()];
let r = graded_under(Policy::Learned, &applied, &Ctx { had: &had, ..live });
assert_eq!((r.route("L01"), r.pf[1]), (Route::Not, 0.0), "{:?}", r.pf);
assert_eq!(ids(&r.free), ["L00", "L02", "L03"]);
let twice: Vec<(&Challenge, u8)> = [&ls[3], &ls[1], &ls[3], &ls[2]].into_iter().map(|c| (c, 1)).collect();
assert_eq!(nofree(&twice, &["L02".to_string(), "L03".to_string(), "L99".to_string()]), ["L03", "L02"]);
assert!(nofree(&twice, &[]).is_empty());
}
#[test]
fn the_fixed_rule_draws_its_placebos_among_what_it_delivers_neither_way() {
let ls = lessons(14);
let none = std::path::PathBuf::from("/nonexistent/borg-select/lessons.jsonl");
let s = summary::Summary::default();
fn fixed<'a>(applied: &[(&'a Challenge, u8)], had: &[String], check: &str) -> Routed<'a> {
let (s, none) = (summary::Summary::default(), std::path::PathBuf::from("/nonexistent/borg-select/lessons.jsonl"));
graded_under(Policy::Fixed, applied, &Ctx { check, had, ..ctx(&none, &s) })
}
let block: Vec<(&Challenge, u8)> = ls.iter().take(12).zip(std::iter::once(3).chain(std::iter::repeat_n(1, 11))).collect();
let r = fixed(&block, &[], "0123456789ab");
assert_eq!((r.ask.len(), ids(&r.placebo), r.log.psi), (conscience::CAP, vec!["L10".to_string(), "L11".to_string()], 1.0));
let soft: Vec<(&Challenge, u8)> = ls.iter().map(|c| (c, 1)).collect();
let mut drawn = std::collections::BTreeMap::<String, u32>::new();
let checks = 200;
for c in 0..checks {
let check = format!("{c:012x}");
let r = fixed(&soft, &[], &check);
assert_eq!((r.free.len(), r.placebo.len(), r.log.psi), (conscience::CAP, PLACEBO_SLOTS, 0.5));
assert_eq!(ids(&r.placebo), ids(&fixed(&soft, &[], &check).placebo), "the check's seed decides");
assert!(r.placebo.windows(2).all(|w| w[0].id < w[1].id), "in the applies' order");
for p in &r.placebo {
*drawn.entry(p.id.clone()).or_default() += 1;
}
}
assert_eq!(drawn.keys().cloned().collect::<Vec<_>>(), ["L10", "L11", "L12", "L13"]);
assert!(drawn.values().all(|&n| (60..=140).contains(&n)), "each about half the checks: {drawn:?}");
let had = ["L12".to_string()];
for c in 0..20 {
let r = fixed(&soft, &had, &format!("{c:012x}"));
assert_eq!(r.log.psi, outcome::round_p(2.0 / 3.0));
assert!(!ids(&r.placebo).contains(&had[0]), "{:?}", ids(&r.placebo));
}
let named: Vec<(&Challenge, u8)> = ls.iter().take(10).chain([&ls[0], &ls[12], &ls[12]]).map(|c| (c, 1)).collect();
let r = fixed(&named, &[], "0123456789ab");
assert_eq!((ids(&r.placebo), r.log.psi), (vec!["L12".to_string()], 1.0));
for policy in [Policy::Cap(1), Policy::Attention] {
let r = graded_under(policy, &soft, &ctx(&none, &s));
assert!(r.placebo.is_empty() && r.log.psi == 0.0, "{policy:?}");
}
let cold = graded_under(Policy::Learned, &soft, &ctx(&none, &s));
assert_eq!((ids(&cold.placebo), cold.log.psi), (ids(&graded_under(Policy::Fixed, &soft, &ctx(&none, &s)).placebo), 0.5));
}
#[test]
fn the_pool_is_the_learned_policys_once_its_gate_opens_or_a_budget_binds() {
let pool = policy::PoolFit {
yields: vec![(50.0, 10.0); 8],
per_lesson: 300.0,
latency: (1000.0, 0.1, 500.0),
latency_n: policy::LATENCY_MIN as u32,
not: (-1.0, 0.1),
..policy::PoolFit::default()
};
let base = crate::whole::TOKENS;
let limit = Some(60_000);
let unpriced = policy::Fit { pool: pool.clone(), ..policy::Fit::default() };
let grows = (0..20).map(|i| format!("s{i}")).find(|x| policy::pool_tokens(&unpriced, true, limit, policy::session_seed(x)) > base);
let session = grows.expect("the scenario: an active pool grows for some session");
let at = |policy: Policy, s: &summary::Summary| (pool_active(policy, s), pool_tokens_under(policy, s, limit, &session));
let mut s = summary::Summary { fit: unpriced, ..summary::Summary::default() };
assert_eq!(at(Policy::Learned, &s), (false, base), "gate closed, no budget");
s.gate.allowance = 1.0;
assert!(at(Policy::Learned, &s).0 && at(Policy::Learned, &s).1 > base, "gate open");
for policy in [Policy::Fixed, Policy::Cap(2), Policy::Attention] {
assert_eq!(at(policy, &s), (false, base), "{policy:?}");
}
s.monitor.reverted = true;
assert_eq!(at(Policy::Learned, &s), (false, base), "reverted");
let mut s = summary::Summary { fit: policy::Fit { pool, budget: Some(1.0), lambda: 1e-2, ..policy::Fit::default() }, ..summary::Summary::default() };
let (active, tokens) = at(Policy::Learned, &s);
assert!(active && tokens < base, "budget, gate closed: {tokens}");
assert_eq!(at(Policy::Fixed, &s), (false, base), "the fixed rule keeps the baseline under a budget");
s.monitor.reverted = true;
assert_eq!(at(Policy::Learned, &s), (false, base), "reverted under a budget");
}
#[test]
fn the_gate_admits_at_the_pool_its_ledger_records() {
let at = |name: &str| policy::NAMES.iter().position(|n| *n == name).unwrap();
let mut beta = vec![0.0; policy::P];
for (name, b) in [("intercept", -0.5), ("ask", -0.4), ("not", -0.3), ("ask_dilution", -0.1), ("free_dilution", -0.1), ("pool", -1.0)] {
beta[at(name)] = b;
}
let mut chol = vec![0.0; policy::P * policy::P];
for k in 0..policy::P {
chol[k * policy::P + k] = if k == at("ask") {
1.0
} else if k == at("pool") {
0.5
} else {
0.3
};
}
let model = policy::Model { beta, chol, sd: vec![1.0; policy::P], spread: [0.3; 3], ..policy::Model::default() };
let rates = policy::Rates { free_stop: 1.0, free_live: 1.0, later: 0.5, wait: 0.0, ask_live: 1.0 };
let fit = policy::Fit { key: "9-pool0001".into(), model: Some(model), rates, ..policy::Fit::default() };
let mut s = summary::Summary { fit, ..summary::Summary::default() };
let tokens = u32::try_from(crate::whole::TOKENS / 4).unwrap();
let (ls, none) = (lessons(10), std::path::PathBuf::from("/nonexistent/borg-select/lessons.jsonl"));
let mut rng = crate::memory::Rng::new(77);
let mut deviated = 0;
for c in 0..400u64 {
let (n, from) = (1 + (rng.next_u64() % 3) as usize, (rng.next_u64() % 10) as usize);
let applied: Vec<(&Challenge, u8)> = (0..n).map(|i| (&ls[(from + i) % 10], 1 + (rng.next_u64() % 3) as u8)).collect();
let (session, check, stop) = (format!("t{}", c / 10), format!("{c:012x}"), c % 5 == 0);
let had: Vec<String> = if c % 2 == 0 { vec![ls[(rng.next_u64() % 10) as usize].id.clone()] } else { Vec::new() };
let ctx = Ctx {
session: &session,
check: &check,
channel: if stop { "stop" } else { "live" },
lessons: &none,
summary: &s,
tokens,
had: &had,
alpha: policy::ALPHA,
};
let r = graded_under(Policy::Learned, &applied, &ctx);
let us = units(&applied);
let routes: Vec<Route> = applied.iter().map(|(l, _)| r.route(&l.id)).collect();
assert!(applied.iter().zip(&r.pf).all(|((l, _), pf)| !had.contains(&l.id) || (r.route(&l.id) != Route::Free && *pf == 0.0)), "check {c}");
let nofree = nofree(&applied, &had);
deviated += usize::from(routes.iter().map(|x| if *x == Route::Placebo { Route::Not } else { *x }).ne(policy::fixed_routes(&us, &nofree)));
let held = |g: &summary::Gate| policy::remaining(g) - g.allowance;
let before = held(&s.gate);
let ids = |v: &[&Challenge]| v.iter().map(|c| c.id.clone()).collect::<Vec<_>>();
let o = crate::outcome::Outcome {
kind: crate::outcome::GRADED.into(),
session: session.clone(),
check: check.clone(),
channel: ctx.channel.into(),
lessons: applied.iter().map(|(c, _)| c.id.clone()).collect(),
grades: applied.iter().map(|(_, g)| *g).collect(),
ranks: (0..n as u16).collect(),
applied: n as u16,
chosen: ids(&r.ask),
free: ids(&r.free),
placebo: ids(&r.placebo),
p: r.p.clone(),
pf: r.pf.clone(),
psi: r.log.psi,
nofree,
policy: r.log.policy.into(),
fit: r.log.fit.clone(),
source: r.log.source.into(),
tokens,
..Default::default()
};
s = summary::fold(&format!("{}\n", serde_json::to_string(&o).unwrap()), s);
assert!(
(r.log.risk - (before - held(&s.gate)).max(0.0)).abs() < 1e-9,
"check {c}: risked {} but the ledger took {}",
r.log.risk,
before - held(&s.gate)
);
assert!(policy::remaining(&s.gate) >= -1e-9, "check {c}: the gate overspent, {}", policy::remaining(&s.gate));
}
assert!(deviated > 0, "the learned policy deviated, so the gate was put to the test");
assert_eq!(policy::ledger(&s), s.gate, "the refit's ledger is the folds'");
}
#[test]
fn a_budget_is_read_for_the_collective_whose_store_it_is() {
let base = Path::new("/home/x/.local/share/borg");
let lessons = crate::collective::store(base, "team").join("lessons.jsonl");
let cfg = |entry: &str| {
serde_json::from_str::<crate::collective::Config>(&format!(r#"{{"collectives":[{{"name":"other","paths":[],"budget":5}},{entry}]}}"#)).unwrap()
};
assert_eq!(budget_in(&cfg(r#"{"name":"team","paths":[],"budget":400000}"#), &lessons), Some(400_000.0));
assert_eq!(budget_in(&cfg(r#"{"name":"team","paths":[]}"#), &lessons), None, "off by default");
assert_eq!(budget_in(&cfg(r#"{"name":"team","paths":[],"budget":0}"#), &lessons), None);
assert_eq!(budget_in(&cfg(r#"{"name":"team","paths":[],"budget":"lots"}"#), &lessons), None);
}
#[test]
fn a_risk_is_read_for_the_collective_whose_store_it_is_and_only_as_a_share() {
let base = Path::new("/home/x/.local/share/borg");
let lessons = crate::collective::store(base, "team").join("lessons.jsonl");
let cfg = |entry: &str| {
serde_json::from_str::<crate::collective::Config>(&format!(r#"{{"collectives":[{{"name":"other","paths":[],"risk":0.5}},{entry}]}}"#)).unwrap()
};
assert_eq!(risk_in(&cfg(r#"{"name":"team","paths":[],"risk":0.2}"#), &lessons), Some(0.2));
assert_eq!(risk_in(&cfg(r#"{"name":"team","paths":[],"risk":0}"#), &lessons), Some(0.0), "0 turns the test off");
assert_eq!(risk_in(&cfg(r#"{"name":"team","paths":[],"risk":1}"#), &lessons), Some(1.0));
assert_eq!(risk_in(&cfg(r#"{"name":"team","paths":[]}"#), &lessons), None, "unset: the policy's ALPHA");
for bad in ["-0.1", "1.5", "\"lots\"", "null"] {
assert_eq!(risk_in(&cfg(&format!(r#"{{"name":"team","paths":[],"risk":{bad}}}"#)), &lessons), None, "{bad}");
}
}
#[test]
fn a_check_id_is_short_stable_hex_and_differs_per_check() {
let id = check_id("s", "stop", 1_000, 42);
assert_eq!(id.len(), CHECK_HEX);
assert!(id.bytes().all(|b| b.is_ascii_hexdigit()), "{id}");
assert_eq!(id, check_id("s", "stop", 1_000, 42), "stable");
let others = [check_id("s", "live", 1_000, 42), check_id("s", "stop", 1_001, 42), check_id("s", "stop", 1_000, 43), check_id("t", "stop", 1_000, 42)];
assert!(others.iter().all(|o| *o != id), "{others:?}");
assert_ne!(check_id("ab", "c", 1, 1), check_id("a", "bc", 1, 1));
}
#[test]
fn a_seed_depends_on_session_channel_and_check() {
let (s, none) = (summary::Summary::default(), std::path::PathBuf::new());
let at = |session, channel, check| seed(&Ctx { session, check, channel, lessons: &none, summary: &s, tokens: 0, had: &[], alpha: policy::ALPHA });
assert_eq!(at("s", "stop", "a1"), at("s", "stop", "a1"));
assert_ne!(at("s", "stop", "a1"), at("s", "live", "a1"));
assert_ne!(at("s", "stop", "a1"), at("s", "stop", "a2"));
assert_ne!(at("s", "stop", "a1"), at("t", "stop", "a1"));
}
}