refactor(api): rename Agent to Competitor and .player field to .rating
Competitor holds dynamic per-history state (message, last_time) for someone competing; its configuration lives in a Rating. AgentStore renamed to CompetitorStore to match. The internal `clean()` free function's parameter name changed from `agents` to `competitors` for consistency. Local variable names (agent_idx, this_agent) inside history.rs are left unchanged — they represent abstract identifiers, not Competitor instances. Part of T2 of docs/superpowers/specs/2026-04-23-trueskill-engine-redesign-design.md.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
use criterion::{Criterion, criterion_group, criterion_main};
|
use criterion::{Criterion, criterion_group, criterion_main};
|
||||||
use trueskill_tt::{
|
use trueskill_tt::{
|
||||||
BETA, GAMMA, KeyTable, MU, P_DRAW, Rating, SIGMA, agent::Agent, batch::Batch,
|
BETA, Competitor, GAMMA, KeyTable, MU, P_DRAW, Rating, SIGMA, batch::Batch,
|
||||||
drift::ConstantDrift, gaussian::Gaussian, storage::AgentStore,
|
drift::ConstantDrift, gaussian::Gaussian, storage::CompetitorStore,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn criterion_benchmark(criterion: &mut Criterion) {
|
fn criterion_benchmark(criterion: &mut Criterion) {
|
||||||
@@ -11,13 +11,13 @@ fn criterion_benchmark(criterion: &mut Criterion) {
|
|||||||
let b = index_map.get_or_create("b");
|
let b = index_map.get_or_create("b");
|
||||||
let c = index_map.get_or_create("c");
|
let c = index_map.get_or_create("c");
|
||||||
|
|
||||||
let mut agents: AgentStore<ConstantDrift> = AgentStore::new();
|
let mut agents: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
||||||
|
|
||||||
for agent in [a, b, c] {
|
for agent in [a, b, c] {
|
||||||
agents.insert(
|
agents.insert(
|
||||||
agent,
|
agent,
|
||||||
Agent {
|
Competitor {
|
||||||
player: Rating::new(Gaussian::from_ms(MU, SIGMA), BETA, ConstantDrift(GAMMA)),
|
rating: Rating::new(Gaussian::from_ms(MU, SIGMA), BETA, ConstantDrift(GAMMA)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
47
src/agent.rs
47
src/agent.rs
@@ -1,47 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
N_INF,
|
|
||||||
drift::{ConstantDrift, Drift},
|
|
||||||
gaussian::Gaussian,
|
|
||||||
rating::Rating,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Agent<D: Drift = ConstantDrift> {
|
|
||||||
pub player: Rating<D>,
|
|
||||||
pub message: Gaussian,
|
|
||||||
pub last_time: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: Drift> Agent<D> {
|
|
||||||
pub(crate) fn receive(&self, elapsed: i64) -> Gaussian {
|
|
||||||
if self.message != N_INF {
|
|
||||||
self.message
|
|
||||||
.forget(self.player.drift.variance_delta(elapsed))
|
|
||||||
} else {
|
|
||||||
self.player.prior
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Agent<ConstantDrift> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
player: Rating::default(),
|
|
||||||
message: N_INF,
|
|
||||||
last_time: i64::MIN,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn clean<'a, D: Drift + 'a, A: Iterator<Item = &'a mut Agent<D>>>(
|
|
||||||
agents: A,
|
|
||||||
last_time: bool,
|
|
||||||
) {
|
|
||||||
for a in agents {
|
|
||||||
a.message = N_INF;
|
|
||||||
|
|
||||||
if last_time {
|
|
||||||
a.last_time = i64::MIN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
45
src/batch.rs
45
src/batch.rs
@@ -7,7 +7,7 @@ use crate::{
|
|||||||
game::Game,
|
game::Game,
|
||||||
gaussian::Gaussian,
|
gaussian::Gaussian,
|
||||||
rating::Rating,
|
rating::Rating,
|
||||||
storage::{AgentStore, SkillStore},
|
storage::{CompetitorStore, SkillStore},
|
||||||
tuple_gt, tuple_max,
|
tuple_gt, tuple_max,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -50,9 +50,9 @@ impl Item {
|
|||||||
online: bool,
|
online: bool,
|
||||||
forward: bool,
|
forward: bool,
|
||||||
skills: &SkillStore,
|
skills: &SkillStore,
|
||||||
agents: &AgentStore<D>,
|
agents: &CompetitorStore<D>,
|
||||||
) -> Rating<D> {
|
) -> Rating<D> {
|
||||||
let r = &agents[self.agent].player;
|
let r = &agents[self.agent].rating;
|
||||||
let skill = skills.get(self.agent).unwrap();
|
let skill = skills.get(self.agent).unwrap();
|
||||||
|
|
||||||
if online {
|
if online {
|
||||||
@@ -91,7 +91,7 @@ impl Event {
|
|||||||
online: bool,
|
online: bool,
|
||||||
forward: bool,
|
forward: bool,
|
||||||
skills: &SkillStore,
|
skills: &SkillStore,
|
||||||
agents: &AgentStore<D>,
|
agents: &CompetitorStore<D>,
|
||||||
) -> Vec<Vec<Rating<D>>> {
|
) -> Vec<Vec<Rating<D>>> {
|
||||||
self.teams
|
self.teams
|
||||||
.iter()
|
.iter()
|
||||||
@@ -130,7 +130,7 @@ impl Batch {
|
|||||||
composition: Vec<Vec<Vec<Index>>>,
|
composition: Vec<Vec<Vec<Index>>>,
|
||||||
results: Vec<Vec<f64>>,
|
results: Vec<Vec<f64>>,
|
||||||
weights: Vec<Vec<Vec<f64>>>,
|
weights: Vec<Vec<Vec<f64>>>,
|
||||||
agents: &AgentStore<D>,
|
agents: &CompetitorStore<D>,
|
||||||
) {
|
) {
|
||||||
let mut unique = Vec::with_capacity(10);
|
let mut unique = Vec::with_capacity(10);
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ impl Batch {
|
|||||||
.collect::<HashMap<_, _>>()
|
.collect::<HashMap<_, _>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iteration<D: Drift>(&mut self, from: usize, agents: &AgentStore<D>) {
|
pub fn iteration<D: Drift>(&mut self, from: usize, agents: &CompetitorStore<D>) {
|
||||||
for event in self.events.iter_mut().skip(from) {
|
for event in self.events.iter_mut().skip(from) {
|
||||||
let teams = event.within_priors(false, false, &self.skills, agents);
|
let teams = event.within_priors(false, false, &self.skills, agents);
|
||||||
let result = event.outputs();
|
let result = event.outputs();
|
||||||
@@ -237,7 +237,7 @@ impl Batch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn convergence<D: Drift>(&mut self, agents: &AgentStore<D>) -> usize {
|
pub(crate) fn convergence<D: Drift>(&mut self, agents: &CompetitorStore<D>) -> usize {
|
||||||
let epsilon = 1e-6;
|
let epsilon = 1e-6;
|
||||||
let iterations = 20;
|
let iterations = 20;
|
||||||
|
|
||||||
@@ -269,21 +269,21 @@ impl Batch {
|
|||||||
pub(crate) fn backward_prior_out<D: Drift>(
|
pub(crate) fn backward_prior_out<D: Drift>(
|
||||||
&self,
|
&self,
|
||||||
agent: &Index,
|
agent: &Index,
|
||||||
agents: &AgentStore<D>,
|
agents: &CompetitorStore<D>,
|
||||||
) -> Gaussian {
|
) -> Gaussian {
|
||||||
let skill = self.skills.get(*agent).unwrap();
|
let skill = self.skills.get(*agent).unwrap();
|
||||||
let n = skill.likelihood * skill.backward;
|
let n = skill.likelihood * skill.backward;
|
||||||
n.forget(agents[*agent].player.drift.variance_delta(skill.elapsed))
|
n.forget(agents[*agent].rating.drift.variance_delta(skill.elapsed))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_backward_info<D: Drift>(&mut self, agents: &AgentStore<D>) {
|
pub(crate) fn new_backward_info<D: Drift>(&mut self, agents: &CompetitorStore<D>) {
|
||||||
for (agent, skill) in self.skills.iter_mut() {
|
for (agent, skill) in self.skills.iter_mut() {
|
||||||
skill.backward = agents[agent].message;
|
skill.backward = agents[agent].message;
|
||||||
}
|
}
|
||||||
self.iteration(0, agents);
|
self.iteration(0, agents);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_forward_info<D: Drift>(&mut self, agents: &AgentStore<D>) {
|
pub(crate) fn new_forward_info<D: Drift>(&mut self, agents: &CompetitorStore<D>) {
|
||||||
for (agent, skill) in self.skills.iter_mut() {
|
for (agent, skill) in self.skills.iter_mut() {
|
||||||
skill.forward = agents[agent].receive(skill.elapsed);
|
skill.forward = agents[agent].receive(skill.elapsed);
|
||||||
}
|
}
|
||||||
@@ -295,7 +295,7 @@ impl Batch {
|
|||||||
online: bool,
|
online: bool,
|
||||||
targets: &[Index],
|
targets: &[Index],
|
||||||
forward: bool,
|
forward: bool,
|
||||||
agents: &AgentStore<D>,
|
agents: &CompetitorStore<D>,
|
||||||
) -> f64 {
|
) -> f64 {
|
||||||
// log_evidence is infrequent; a local arena avoids needing &mut self.
|
// log_evidence is infrequent; a local arena avoids needing &mut self.
|
||||||
let mut arena = ScratchArena::new();
|
let mut arena = ScratchArena::new();
|
||||||
@@ -400,7 +400,8 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
KeyTable, agent::Agent, drift::ConstantDrift, rating::Rating, storage::AgentStore,
|
KeyTable, competitor::Competitor, drift::ConstantDrift, rating::Rating,
|
||||||
|
storage::CompetitorStore,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -414,13 +415,13 @@ mod tests {
|
|||||||
let e = index_map.get_or_create("e");
|
let e = index_map.get_or_create("e");
|
||||||
let f = index_map.get_or_create("f");
|
let f = index_map.get_or_create("f");
|
||||||
|
|
||||||
let mut agents: AgentStore<ConstantDrift> = AgentStore::new();
|
let mut agents: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
||||||
|
|
||||||
for agent in [a, b, c, d, e, f] {
|
for agent in [a, b, c, d, e, f] {
|
||||||
agents.insert(
|
agents.insert(
|
||||||
agent,
|
agent,
|
||||||
Agent {
|
Competitor {
|
||||||
player: Rating::new(
|
rating: Rating::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
@@ -490,13 +491,13 @@ mod tests {
|
|||||||
let e = index_map.get_or_create("e");
|
let e = index_map.get_or_create("e");
|
||||||
let f = index_map.get_or_create("f");
|
let f = index_map.get_or_create("f");
|
||||||
|
|
||||||
let mut agents: AgentStore<ConstantDrift> = AgentStore::new();
|
let mut agents: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
||||||
|
|
||||||
for agent in [a, b, c, d, e, f] {
|
for agent in [a, b, c, d, e, f] {
|
||||||
agents.insert(
|
agents.insert(
|
||||||
agent,
|
agent,
|
||||||
Agent {
|
Competitor {
|
||||||
player: Rating::new(
|
rating: Rating::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
@@ -569,13 +570,13 @@ mod tests {
|
|||||||
let e = index_map.get_or_create("e");
|
let e = index_map.get_or_create("e");
|
||||||
let f = index_map.get_or_create("f");
|
let f = index_map.get_or_create("f");
|
||||||
|
|
||||||
let mut agents: AgentStore<ConstantDrift> = AgentStore::new();
|
let mut agents: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
||||||
|
|
||||||
for agent in [a, b, c, d, e, f] {
|
for agent in [a, b, c, d, e, f] {
|
||||||
agents.insert(
|
agents.insert(
|
||||||
agent,
|
agent,
|
||||||
Agent {
|
Competitor {
|
||||||
player: Rating::new(
|
rating: Rating::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
|
|||||||
50
src/competitor.rs
Normal file
50
src/competitor.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
use crate::{
|
||||||
|
N_INF,
|
||||||
|
drift::{ConstantDrift, Drift},
|
||||||
|
gaussian::Gaussian,
|
||||||
|
rating::Rating,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Per-history, temporal state for someone competing.
|
||||||
|
///
|
||||||
|
/// Renamed from `Agent` in T2; the former `.player` field is now
|
||||||
|
/// `.rating` to match the `Player → Rating` rename.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Competitor<D: Drift = ConstantDrift> {
|
||||||
|
pub rating: Rating<D>,
|
||||||
|
pub message: Gaussian,
|
||||||
|
pub last_time: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Drift> Competitor<D> {
|
||||||
|
pub(crate) fn receive(&self, elapsed: i64) -> Gaussian {
|
||||||
|
if self.message != N_INF {
|
||||||
|
self.message
|
||||||
|
.forget(self.rating.drift.variance_delta(elapsed))
|
||||||
|
} else {
|
||||||
|
self.rating.prior
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Competitor<ConstantDrift> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
rating: Rating::default(),
|
||||||
|
message: N_INF,
|
||||||
|
last_time: i64::MIN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clean<'a, D: Drift + 'a, C: Iterator<Item = &'a mut Competitor<D>>>(
|
||||||
|
competitors: C,
|
||||||
|
last_time: bool,
|
||||||
|
) {
|
||||||
|
for c in competitors {
|
||||||
|
c.message = N_INF;
|
||||||
|
if last_time {
|
||||||
|
c.last_time = i64::MIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,13 +2,13 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
BETA, GAMMA, Index, MU, N_INF, P_DRAW, SIGMA,
|
BETA, GAMMA, Index, MU, N_INF, P_DRAW, SIGMA,
|
||||||
agent::{self, Agent},
|
|
||||||
batch::{self, Batch},
|
batch::{self, Batch},
|
||||||
|
competitor::{self, Competitor},
|
||||||
drift::{ConstantDrift, Drift},
|
drift::{ConstantDrift, Drift},
|
||||||
gaussian::Gaussian,
|
gaussian::Gaussian,
|
||||||
rating::Rating,
|
rating::Rating,
|
||||||
sort_time,
|
sort_time,
|
||||||
storage::AgentStore,
|
storage::CompetitorStore,
|
||||||
tuple_gt, tuple_max,
|
tuple_gt, tuple_max,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ impl<D: Drift> HistoryBuilder<D> {
|
|||||||
History {
|
History {
|
||||||
size: 0,
|
size: 0,
|
||||||
batches: Vec::new(),
|
batches: Vec::new(),
|
||||||
agents: AgentStore::new(),
|
agents: CompetitorStore::new(),
|
||||||
time: self.time,
|
time: self.time,
|
||||||
mu: self.mu,
|
mu: self.mu,
|
||||||
sigma: self.sigma,
|
sigma: self.sigma,
|
||||||
@@ -106,7 +106,7 @@ impl Default for HistoryBuilder<ConstantDrift> {
|
|||||||
pub struct History<D: Drift = ConstantDrift> {
|
pub struct History<D: Drift = ConstantDrift> {
|
||||||
size: usize,
|
size: usize,
|
||||||
pub(crate) batches: Vec<Batch>,
|
pub(crate) batches: Vec<Batch>,
|
||||||
agents: AgentStore<D>,
|
agents: CompetitorStore<D>,
|
||||||
time: bool,
|
time: bool,
|
||||||
mu: f64,
|
mu: f64,
|
||||||
sigma: f64,
|
sigma: f64,
|
||||||
@@ -121,7 +121,7 @@ impl Default for History<ConstantDrift> {
|
|||||||
Self {
|
Self {
|
||||||
size: 0,
|
size: 0,
|
||||||
batches: Vec::new(),
|
batches: Vec::new(),
|
||||||
agents: AgentStore::new(),
|
agents: CompetitorStore::new(),
|
||||||
time: true,
|
time: true,
|
||||||
mu: MU,
|
mu: MU,
|
||||||
sigma: SIGMA,
|
sigma: SIGMA,
|
||||||
@@ -143,7 +143,7 @@ impl<D: Drift> History<D> {
|
|||||||
fn iteration(&mut self) -> (f64, f64) {
|
fn iteration(&mut self) -> (f64, f64) {
|
||||||
let mut step = (0.0, 0.0);
|
let mut step = (0.0, 0.0);
|
||||||
|
|
||||||
agent::clean(self.agents.values_mut(), false);
|
competitor::clean(self.agents.values_mut(), false);
|
||||||
|
|
||||||
for j in (0..self.batches.len() - 1).rev() {
|
for j in (0..self.batches.len() - 1).rev() {
|
||||||
for agent in self.batches[j + 1].skills.keys() {
|
for agent in self.batches[j + 1].skills.keys() {
|
||||||
@@ -162,7 +162,7 @@ impl<D: Drift> History<D> {
|
|||||||
.fold(step, |step, (a, old)| tuple_max(step, old.delta(new[a])));
|
.fold(step, |step, (a, old)| tuple_max(step, old.delta(new[a])));
|
||||||
}
|
}
|
||||||
|
|
||||||
agent::clean(self.agents.values_mut(), false);
|
competitor::clean(self.agents.values_mut(), false);
|
||||||
|
|
||||||
for j in 1..self.batches.len() {
|
for j in 1..self.batches.len() {
|
||||||
for agent in self.batches[j - 1].skills.keys() {
|
for agent in self.batches[j - 1].skills.keys() {
|
||||||
@@ -287,7 +287,7 @@ impl<D: Drift> History<D> {
|
|||||||
"(length(weights) > 0) & (length(composition) != length(weights))"
|
"(length(weights) > 0) & (length(composition) != length(weights))"
|
||||||
);
|
);
|
||||||
|
|
||||||
agent::clean(self.agents.values_mut(), true);
|
competitor::clean(self.agents.values_mut(), true);
|
||||||
|
|
||||||
let mut this_agent = Vec::with_capacity(1024);
|
let mut this_agent = Vec::with_capacity(1024);
|
||||||
|
|
||||||
@@ -301,8 +301,8 @@ impl<D: Drift> History<D> {
|
|||||||
if !self.agents.contains(*agent) {
|
if !self.agents.contains(*agent) {
|
||||||
self.agents.insert(
|
self.agents.insert(
|
||||||
*agent,
|
*agent,
|
||||||
Agent {
|
Competitor {
|
||||||
player: priors.remove(agent).unwrap_or_else(|| {
|
rating: priors.remove(agent).unwrap_or_else(|| {
|
||||||
Rating::new(
|
Rating::new(
|
||||||
Gaussian::from_ms(self.mu, self.sigma),
|
Gaussian::from_ms(self.mu, self.sigma),
|
||||||
self.beta,
|
self.beta,
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ use std::{
|
|||||||
f64::consts::{FRAC_1_SQRT_2, FRAC_2_SQRT_PI, SQRT_2},
|
f64::consts::{FRAC_1_SQRT_2, FRAC_2_SQRT_PI, SQRT_2},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod agent;
|
|
||||||
#[cfg(feature = "approx")]
|
#[cfg(feature = "approx")]
|
||||||
mod approx;
|
mod approx;
|
||||||
pub(crate) mod arena;
|
pub(crate) mod arena;
|
||||||
pub mod batch;
|
pub mod batch;
|
||||||
|
mod competitor;
|
||||||
pub mod drift;
|
pub mod drift;
|
||||||
mod error;
|
mod error;
|
||||||
pub(crate) mod factor;
|
pub(crate) mod factor;
|
||||||
@@ -20,6 +20,7 @@ mod rating;
|
|||||||
pub(crate) mod schedule;
|
pub(crate) mod schedule;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
|
|
||||||
|
pub use competitor::Competitor;
|
||||||
pub use drift::{ConstantDrift, Drift};
|
pub use drift::{ConstantDrift, Drift};
|
||||||
pub use error::InferenceError;
|
pub use error::InferenceError;
|
||||||
pub use game::Game;
|
pub use game::Game;
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
use crate::{Index, agent::Agent, drift::Drift};
|
|
||||||
|
|
||||||
/// Dense Vec-backed store for agent state in History.
|
|
||||||
///
|
|
||||||
/// Indexed directly by Index.0, eliminating HashMap hashing in the
|
|
||||||
/// forward/backward sweep. Uses `Vec<Option<Agent<D>>>` so slots can be
|
|
||||||
/// absent without an explicit present mask.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct AgentStore<D: Drift> {
|
|
||||||
agents: Vec<Option<Agent<D>>>,
|
|
||||||
n_present: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: Drift> Default for AgentStore<D> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
agents: Vec::new(),
|
|
||||||
n_present: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: Drift> AgentStore<D> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_capacity(&mut self, idx: usize) {
|
|
||||||
if idx >= self.agents.len() {
|
|
||||||
self.agents.resize_with(idx + 1, || None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(&mut self, idx: Index, agent: Agent<D>) {
|
|
||||||
self.ensure_capacity(idx.0);
|
|
||||||
if self.agents[idx.0].is_none() {
|
|
||||||
self.n_present += 1;
|
|
||||||
}
|
|
||||||
self.agents[idx.0] = Some(agent);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, idx: Index) -> Option<&Agent<D>> {
|
|
||||||
self.agents.get(idx.0).and_then(|slot| slot.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_mut(&mut self, idx: Index) -> Option<&mut Agent<D>> {
|
|
||||||
self.agents.get_mut(idx.0).and_then(|slot| slot.as_mut())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contains(&self, idx: Index) -> bool {
|
|
||||||
self.get(idx).is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.n_present
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.n_present == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (Index, &Agent<D>)> {
|
|
||||||
self.agents
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(i, slot)| slot.as_ref().map(|a| (Index(i), a)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (Index, &mut Agent<D>)> {
|
|
||||||
self.agents
|
|
||||||
.iter_mut()
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(i, slot)| slot.as_mut().map(|a| (Index(i), a)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Agent<D>> {
|
|
||||||
self.agents.iter_mut().filter_map(|s| s.as_mut())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: Drift> std::ops::Index<Index> for AgentStore<D> {
|
|
||||||
type Output = Agent<D>;
|
|
||||||
fn index(&self, idx: Index) -> &Agent<D> {
|
|
||||||
self.get(idx).expect("agent not found at index")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: Drift> std::ops::IndexMut<Index> for AgentStore<D> {
|
|
||||||
fn index_mut(&mut self, idx: Index) -> &mut Agent<D> {
|
|
||||||
self.get_mut(idx).expect("agent not found at index")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::{agent::Agent, drift::ConstantDrift};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn insert_then_get() {
|
|
||||||
let mut store: AgentStore<ConstantDrift> = AgentStore::new();
|
|
||||||
let idx = Index(7);
|
|
||||||
store.insert(idx, Agent::default());
|
|
||||||
assert!(store.contains(idx));
|
|
||||||
assert_eq!(store.len(), 1);
|
|
||||||
assert!(store.get(idx).is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn iter_in_index_order() {
|
|
||||||
let mut store: AgentStore<ConstantDrift> = AgentStore::new();
|
|
||||||
store.insert(Index(2), Agent::default());
|
|
||||||
store.insert(Index(0), Agent::default());
|
|
||||||
store.insert(Index(5), Agent::default());
|
|
||||||
let keys: Vec<Index> = store.iter().map(|(i, _)| i).collect();
|
|
||||||
assert_eq!(keys, vec![Index(0), Index(2), Index(5)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn index_operator_works() {
|
|
||||||
let mut store: AgentStore<ConstantDrift> = AgentStore::new();
|
|
||||||
store.insert(Index(3), Agent::default());
|
|
||||||
let _ = &store[Index(3)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
127
src/storage/competitor_store.rs
Normal file
127
src/storage/competitor_store.rs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
use crate::{Index, competitor::Competitor, drift::Drift};
|
||||||
|
|
||||||
|
/// Dense Vec-backed store for competitor state in History.
|
||||||
|
///
|
||||||
|
/// Indexed directly by Index.0, eliminating HashMap hashing in the
|
||||||
|
/// forward/backward sweep. Uses `Vec<Option<Competitor<D>>>` so slots can be
|
||||||
|
/// absent without an explicit present mask.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CompetitorStore<D: Drift> {
|
||||||
|
competitors: Vec<Option<Competitor<D>>>,
|
||||||
|
n_present: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Drift> Default for CompetitorStore<D> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
competitors: Vec::new(),
|
||||||
|
n_present: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Drift> CompetitorStore<D> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_capacity(&mut self, idx: usize) {
|
||||||
|
if idx >= self.competitors.len() {
|
||||||
|
self.competitors.resize_with(idx + 1, || None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, idx: Index, competitor: Competitor<D>) {
|
||||||
|
self.ensure_capacity(idx.0);
|
||||||
|
if self.competitors[idx.0].is_none() {
|
||||||
|
self.n_present += 1;
|
||||||
|
}
|
||||||
|
self.competitors[idx.0] = Some(competitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, idx: Index) -> Option<&Competitor<D>> {
|
||||||
|
self.competitors.get(idx.0).and_then(|slot| slot.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, idx: Index) -> Option<&mut Competitor<D>> {
|
||||||
|
self.competitors
|
||||||
|
.get_mut(idx.0)
|
||||||
|
.and_then(|slot| slot.as_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, idx: Index) -> bool {
|
||||||
|
self.get(idx).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.n_present
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.n_present == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (Index, &Competitor<D>)> {
|
||||||
|
self.competitors
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, slot)| slot.as_ref().map(|a| (Index(i), a)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = (Index, &mut Competitor<D>)> {
|
||||||
|
self.competitors
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, slot)| slot.as_mut().map(|a| (Index(i), a)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Competitor<D>> {
|
||||||
|
self.competitors.iter_mut().filter_map(|s| s.as_mut())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Drift> std::ops::Index<Index> for CompetitorStore<D> {
|
||||||
|
type Output = Competitor<D>;
|
||||||
|
fn index(&self, idx: Index) -> &Competitor<D> {
|
||||||
|
self.get(idx).expect("competitor not found at index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Drift> std::ops::IndexMut<Index> for CompetitorStore<D> {
|
||||||
|
fn index_mut(&mut self, idx: Index) -> &mut Competitor<D> {
|
||||||
|
self.get_mut(idx).expect("competitor not found at index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{competitor::Competitor, drift::ConstantDrift};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_then_get() {
|
||||||
|
let mut store: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
||||||
|
let idx = Index(7);
|
||||||
|
store.insert(idx, Competitor::default());
|
||||||
|
assert!(store.contains(idx));
|
||||||
|
assert_eq!(store.len(), 1);
|
||||||
|
assert!(store.get(idx).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iter_in_index_order() {
|
||||||
|
let mut store: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
||||||
|
store.insert(Index(2), Competitor::default());
|
||||||
|
store.insert(Index(0), Competitor::default());
|
||||||
|
store.insert(Index(5), Competitor::default());
|
||||||
|
let keys: Vec<Index> = store.iter().map(|(i, _)| i).collect();
|
||||||
|
assert_eq!(keys, vec![Index(0), Index(2), Index(5)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn index_operator_works() {
|
||||||
|
let mut store: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
||||||
|
store.insert(Index(3), Competitor::default());
|
||||||
|
let _ = &store[Index(3)];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
mod agent_store;
|
mod competitor_store;
|
||||||
mod skill_store;
|
mod skill_store;
|
||||||
|
|
||||||
pub use agent_store::AgentStore;
|
pub use competitor_store::CompetitorStore;
|
||||||
pub(crate) use skill_store::SkillStore;
|
pub(crate) use skill_store::SkillStore;
|
||||||
|
|||||||
Reference in New Issue
Block a user