use std::collections::BTreeSet;
use serde::{Deserialize, Serialize};
use crate::composite::{AgentSubmission, Run};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TaggedRun {
pub window_id: String,
pub run: Run,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TaggedSubmission {
pub agent_id: String,
pub runs: Vec<TaggedRun>,
#[serde(default)]
pub in_sample_trials: u32,
#[serde(default)]
pub candidates: Vec<Vec<f64>>,
}
impl TaggedSubmission {
pub fn completed_windows(&self) -> BTreeSet<String> {
self.runs.iter().map(|r| r.window_id.clone()).collect()
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ComparisonSet {
pub roster: Vec<String>,
pub shared_windows: Vec<String>,
}
pub fn comparison_set(roster: &[String], subs: &[TaggedSubmission]) -> ComparisonSet {
let mut shared: Option<BTreeSet<String>> = None;
for agent_id in roster {
let completed = subs
.iter()
.find(|s| &s.agent_id == agent_id)
.map(TaggedSubmission::completed_windows)
.unwrap_or_default();
shared = Some(match shared {
None => completed,
Some(acc) => acc.intersection(&completed).cloned().collect(),
});
}
ComparisonSet {
roster: roster.to_vec(),
shared_windows: shared.unwrap_or_default().into_iter().collect(),
}
}
pub fn qualifies(set: &ComparisonSet, sub: &TaggedSubmission, min_shared_windows: usize) -> bool {
let completed = sub.completed_windows();
let shared_completed = set
.shared_windows
.iter()
.filter(|w| completed.contains(*w))
.count();
shared_completed >= min_shared_windows
}
pub fn restrict_to_shared(set: &ComparisonSet, sub: &TaggedSubmission) -> AgentSubmission {
let shared: BTreeSet<&str> = set.shared_windows.iter().map(String::as_str).collect();
let runs = sub
.runs
.iter()
.filter(|r| shared.contains(r.window_id.as_str()))
.map(|r| r.run.clone())
.collect();
AgentSubmission {
agent_id: sub.agent_id.clone(),
runs,
in_sample_trials: sub.in_sample_trials,
candidates: sub.candidates.clone(),
}
}
pub fn restrict_field(roster: &[String], subs: &[TaggedSubmission]) -> Vec<AgentSubmission> {
let set = comparison_set(roster, subs);
roster
.iter()
.map(
|agent_id| match subs.iter().find(|s| &s.agent_id == agent_id) {
Some(sub) => restrict_to_shared(&set, sub),
None => AgentSubmission {
agent_id: agent_id.clone(),
runs: Vec::new(),
in_sample_trials: 0,
candidates: Vec::new(),
},
},
)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn run(mean_ret: f64, n: usize) -> Run {
Run {
returns: (0..n)
.map(|i| mean_ret + 0.0005 * (i as f64 * 0.7).sin())
.collect(),
trace: Default::default(),
confidences: Vec::new(),
outcomes: Vec::new(),
cost: 0.0,
}
}
fn tagged(agent_id: &str, windows: &[&str]) -> TaggedSubmission {
TaggedSubmission {
agent_id: agent_id.to_string(),
runs: windows
.iter()
.map(|w| TaggedRun {
window_id: (*w).to_string(),
run: run(0.002, 40),
})
.collect(),
in_sample_trials: 0,
candidates: Vec::new(),
}
}
#[test]
fn shared_is_intersection_sorted() {
let veteran = tagged("vet", &["w3", "w1", "w2", "w4"]);
let entrant = tagged("new", &["w2", "w3"]);
let roster = vec!["vet".to_string(), "new".to_string()];
let set = comparison_set(&roster, &[veteran, entrant]);
assert_eq!(set.shared_windows, vec!["w2".to_string(), "w3".to_string()]);
assert_eq!(set.roster, roster);
}
#[test]
fn missing_roster_member_empties_shared() {
let veteran = tagged("vet", &["w1", "w2"]);
let roster = vec!["vet".to_string(), "ghost".to_string()];
let set = comparison_set(&roster, &[veteran]);
assert!(set.shared_windows.is_empty());
}
#[test]
fn restrict_keeps_only_shared_runs() {
let veteran = tagged("vet", &["w1", "w2", "w3"]);
let entrant = tagged("new", &["w2", "w3", "w9"]);
let roster = vec!["vet".to_string(), "new".to_string()];
let field = restrict_field(&roster, &[veteran, entrant]);
assert_eq!(field.len(), 2);
assert_eq!(field[0].agent_id, "vet");
assert_eq!(field[0].runs.len(), 2);
assert_eq!(field[1].runs.len(), 2);
}
#[test]
fn qualifies_on_min_shared() {
let veteran = tagged("vet", &["w1", "w2", "w3"]);
let entrant = tagged("new", &["w2", "w3"]);
let roster = vec!["vet".to_string(), "new".to_string()];
let set = comparison_set(&roster, &[veteran, entrant.clone()]);
assert!(qualifies(&set, &entrant, 2));
assert!(!qualifies(&set, &entrant, 3));
let thin = tagged("thin", &["w2", "w7"]);
assert!(!qualifies(&set, &thin, 2));
assert!(qualifies(&set, &thin, 1));
}
#[test]
fn empty_roster_yields_empty_set() {
let set = comparison_set(&[], &[]);
assert!(set.shared_windows.is_empty());
assert!(set.roster.is_empty());
assert!(restrict_field(&[], &[]).is_empty());
}
#[test]
fn multiple_runs_per_window_all_survive() {
let mut sub = tagged("multi", &["w1", "w1", "w2"]);
let other = tagged("other", &["w1", "w2"]);
let roster = vec!["multi".to_string(), "other".to_string()];
let set = comparison_set(&roster, &[sub.clone(), other]);
assert_eq!(set.shared_windows, vec!["w1".to_string(), "w2".to_string()]);
let filtered = restrict_to_shared(&set, &sub);
assert_eq!(filtered.runs.len(), 3);
sub.runs.push(TaggedRun {
window_id: "w9".to_string(),
run: run(0.002, 40),
});
assert_eq!(restrict_to_shared(&set, &sub).runs.len(), 3);
}
}