use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use serde::{Deserialize, Serialize};
use crate::composite::{parse_declared_field, AgentSubmission, MandateDeclarations};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct RunKey {
pub window: String,
pub seed: u64,
}
impl fmt::Display for RunKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(window `{}`, seed {})", self.window, self.seed)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RunIdentity {
#[serde(flatten)]
pub key: RunKey,
#[serde(default)]
pub periods: Vec<String>,
}
#[derive(Clone, Debug, Deserialize)]
struct RunKeysDoc {
agent_id: String,
run_keys: Option<Vec<RunIdentity>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RunIdentityError {
InvalidField(String),
EmptyField,
UnkeyedSubmission { agent_id: String },
KeyCountMismatch {
agent_id: String,
runs: usize,
keys: usize,
},
InvalidKey {
agent_id: String,
index: usize,
reason: String,
},
DuplicateKey { agent_id: String, key: RunKey },
MissingCell { agent_id: String, key: RunKey },
IncompleteGrid {
windows: usize,
seeds: usize,
submitted: usize,
first_missing: RunKey,
},
PeriodCountMismatch {
agent_id: String,
key: RunKey,
periods: usize,
returns: usize,
},
DuplicatePeriod {
agent_id: String,
key: RunKey,
period: String,
first: usize,
second: usize,
},
PeriodInTwoWindows(Box<PeriodOverlap>),
PeriodMismatch {
key: RunKey,
agent_id: String,
other_agent_id: String,
index: usize,
},
WindowOrderMismatch {
agent_id: String,
other_agent_id: String,
windows: Vec<String>,
other_windows: Vec<String>,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PeriodOverlap {
pub period: String,
pub first: RunKey,
pub first_agent_id: String,
pub second: RunKey,
pub second_agent_id: String,
}
impl fmt::Display for RunIdentityError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidField(error) => write!(f, "{error}"),
Self::EmptyField => write!(f, "run identity: the field contains no submissions"),
Self::UnkeyedSubmission { agent_id } => write!(
f,
"run identity: agent `{agent_id}` submitted an unkeyed run array; \
add a `run_keys` entry per run (window and seed). Runs are not \
aligned across agents by position"
),
Self::KeyCountMismatch {
agent_id,
runs,
keys,
} => write!(
f,
"run identity: agent `{agent_id}` declared {keys} run key(s) for {runs} run(s)"
),
Self::InvalidKey {
agent_id,
index,
reason,
} => write!(
f,
"run identity: agent `{agent_id}` run key {index} is invalid: {reason}"
),
Self::DuplicateKey { agent_id, key } => write!(
f,
"run identity: agent `{agent_id}` declared {key} more than once"
),
Self::MissingCell { agent_id, key } => write!(
f,
"run identity: agent `{agent_id}` is missing {key}, which another \
agent submitted. A partial field cannot be scored: restricting to \
shared support would rescope every other entrant's evidence"
),
Self::IncompleteGrid {
windows,
seeds,
submitted,
first_missing,
} => write!(
f,
"run identity: incomplete grid, {submitted} cell(s) submitted for \
{windows} window(s) times {seeds} seed(s); first missing {first_missing}"
),
Self::PeriodCountMismatch {
agent_id,
key,
periods,
returns,
} => write!(
f,
"run identity: agent `{agent_id}` {key} declared {periods} period \
identity/identities for {returns} return(s)"
),
Self::DuplicatePeriod {
agent_id,
key,
period,
first,
second,
} => write!(
f,
"run identity: agent `{agent_id}` {key} declares period `{period}` at \
index {first} and again at index {second}; one period cannot contribute \
two returns to a run"
),
Self::PeriodInTwoWindows(overlap) => {
let PeriodOverlap {
period,
first,
first_agent_id,
second,
second_agent_id,
} = overlap.as_ref();
write!(
f,
"run identity: period `{period}` is declared in {first} by agent \
`{first_agent_id}` and in {second} by agent `{second_agent_id}`; a \
period belongs to one window, and the pooled track would count its \
return twice. Seeds of one window may share periods, different \
windows may not"
)
}
Self::PeriodMismatch {
key,
agent_id,
other_agent_id,
index,
} => write!(
f,
"run identity: {key} has different period identities at index {index} \
for agents `{other_agent_id}` and `{agent_id}`"
),
Self::WindowOrderMismatch {
agent_id,
other_agent_id,
windows,
other_windows,
} => write!(
f,
"run identity: agent `{other_agent_id}` lists windows {other_windows:?} \
and agent `{agent_id}` lists {windows:?}. The declared order is the \
order the pooled track concatenates them in, so the field must not \
leave it to the labels' spelling"
),
}
}
}
impl std::error::Error for RunIdentityError {}
#[derive(Clone, Debug)]
pub struct KeyedField {
pub keys: Vec<RunKey>,
pub windows: Vec<String>,
pub seeds: Vec<u64>,
pub submissions: Vec<AgentSubmission>,
pub declarations: MandateDeclarations,
}
fn declared_window_order(identities: &[RunIdentity]) -> Vec<String> {
let mut seen: BTreeSet<&str> = BTreeSet::new();
let mut order = Vec::new();
for identity in identities {
if seen.insert(identity.key.window.as_str()) {
order.push(identity.key.window.clone());
}
}
order
}
fn check_window(window: &str) -> Result<(), String> {
if window.is_empty() {
return Err("window identity is empty".to_string());
}
if window.trim() != window {
return Err(format!(
"window identity `{window}` has leading or trailing whitespace, so exact \
comparison across producers is unsafe"
));
}
if window.chars().any(char::is_control) {
return Err(format!(
"window identity `{window}` contains a control character"
));
}
Ok(())
}
fn first_repeated_period(periods: &[String]) -> Option<(usize, usize)> {
let mut seen: BTreeMap<&str, usize> = BTreeMap::new();
for (second, period) in periods.iter().enumerate() {
if let Some(first) = seen.insert(period.as_str(), second) {
return Some((first, second));
}
}
None
}
pub fn parse_keyed_field(json: &str) -> Result<KeyedField, RunIdentityError> {
let (subs, declarations) =
parse_declared_field(json).map_err(RunIdentityError::InvalidField)?;
let docs: Vec<RunKeysDoc> = serde_json::from_str(json)
.map_err(|error| RunIdentityError::InvalidField(format!("invalid run keys: {error}")))?;
if subs.is_empty() {
return Err(RunIdentityError::EmptyField);
}
let mut per_agent: Vec<(usize, Vec<RunIdentity>)> = Vec::with_capacity(subs.len());
for (position, sub) in subs.iter().enumerate() {
let doc = docs
.iter()
.find(|d| d.agent_id == sub.agent_id)
.ok_or_else(|| RunIdentityError::UnkeyedSubmission {
agent_id: sub.agent_id.clone(),
})?;
let identities =
doc.run_keys
.clone()
.ok_or_else(|| RunIdentityError::UnkeyedSubmission {
agent_id: sub.agent_id.clone(),
})?;
if identities.len() != sub.runs.len() {
return Err(RunIdentityError::KeyCountMismatch {
agent_id: sub.agent_id.clone(),
runs: sub.runs.len(),
keys: identities.len(),
});
}
let mut seen = BTreeSet::new();
for (index, identity) in identities.iter().enumerate() {
check_window(&identity.key.window).map_err(|reason| RunIdentityError::InvalidKey {
agent_id: sub.agent_id.clone(),
index,
reason,
})?;
if !seen.insert(identity.key.clone()) {
return Err(RunIdentityError::DuplicateKey {
agent_id: sub.agent_id.clone(),
key: identity.key.clone(),
});
}
if !identity.periods.is_empty()
&& identity.periods.len() != sub.runs[index].returns.len()
{
return Err(RunIdentityError::PeriodCountMismatch {
agent_id: sub.agent_id.clone(),
key: identity.key.clone(),
periods: identity.periods.len(),
returns: sub.runs[index].returns.len(),
});
}
if let Some((first, second)) = first_repeated_period(&identity.periods) {
return Err(RunIdentityError::DuplicatePeriod {
agent_id: sub.agent_id.clone(),
key: identity.key.clone(),
period: identity.periods[second].clone(),
first,
second,
});
}
}
per_agent.push((position, identities));
}
let mut union: BTreeSet<RunKey> = BTreeSet::new();
for (_, identities) in &per_agent {
union.extend(identities.iter().map(|i| i.key.clone()));
}
for (position, identities) in &per_agent {
let held: BTreeSet<&RunKey> = identities.iter().map(|i| &i.key).collect();
if let Some(missing) = union.iter().find(|key| !held.contains(key)) {
return Err(RunIdentityError::MissingCell {
agent_id: subs[*position].agent_id.clone(),
key: missing.clone(),
});
}
}
let (first_position, first_identities) = &per_agent[0];
let windows = declared_window_order(first_identities);
for (position, identities) in &per_agent {
let declared = declared_window_order(identities);
if declared != windows {
return Err(RunIdentityError::WindowOrderMismatch {
agent_id: subs[*position].agent_id.clone(),
other_agent_id: subs[*first_position].agent_id.clone(),
windows: declared,
other_windows: windows,
});
}
}
let seeds: Vec<u64> = union
.iter()
.map(|key| key.seed)
.collect::<BTreeSet<_>>()
.into_iter()
.collect();
if union.len() != windows.len() * seeds.len() {
let first_missing = windows
.iter()
.flat_map(|window| {
seeds.iter().map(move |seed| RunKey {
window: window.clone(),
seed: *seed,
})
})
.find(|key| !union.contains(key))
.expect("a short product has at least one missing cell");
return Err(RunIdentityError::IncompleteGrid {
windows: windows.len(),
seeds: seeds.len(),
submitted: union.len(),
first_missing,
});
}
let mut declared_periods: BTreeMap<RunKey, (String, Vec<String>)> = BTreeMap::new();
for (position, identities) in &per_agent {
let agent_id = &subs[*position].agent_id;
for identity in identities {
if identity.periods.is_empty() {
continue;
}
match declared_periods.get(&identity.key) {
None => {
declared_periods.insert(
identity.key.clone(),
(agent_id.clone(), identity.periods.clone()),
);
}
Some((other_agent_id, periods)) => {
if let Some(index) = periods
.iter()
.zip(&identity.periods)
.position(|(a, b)| a != b)
.or_else(|| {
(periods.len() != identity.periods.len())
.then(|| periods.len().min(identity.periods.len()))
})
{
return Err(RunIdentityError::PeriodMismatch {
key: identity.key.clone(),
agent_id: agent_id.clone(),
other_agent_id: other_agent_id.clone(),
index,
});
}
}
}
}
}
let mut period_window: BTreeMap<&str, (&RunKey, &str)> = BTreeMap::new();
for (key, (agent_id, periods)) in &declared_periods {
for period in periods {
let (first, first_agent_id) = *period_window
.entry(period.as_str())
.or_insert((key, agent_id.as_str()));
if first.window != key.window {
return Err(RunIdentityError::PeriodInTwoWindows(Box::new(
PeriodOverlap {
period: period.clone(),
first: first.clone(),
first_agent_id: first_agent_id.to_string(),
second: key.clone(),
second_agent_id: agent_id.clone(),
},
)));
}
}
}
let keys: Vec<RunKey> = windows
.iter()
.flat_map(|window| {
seeds.iter().map(move |seed| RunKey {
window: window.clone(),
seed: *seed,
})
})
.collect();
let mut submissions = subs;
for (position, identities) in per_agent {
let mut by_key: BTreeMap<&RunKey, usize> = BTreeMap::new();
for (index, identity) in identities.iter().enumerate() {
by_key.insert(&identity.key, index);
}
let order: Vec<usize> = keys
.iter()
.map(|key| *by_key.get(key).expect("every cell checked present"))
.collect();
let runs = std::mem::take(&mut submissions[position].runs);
let mut slots: Vec<Option<_>> = runs.into_iter().map(Some).collect();
submissions[position].runs = order
.into_iter()
.map(|index| {
slots[index]
.take()
.expect("permutation visits each run once")
})
.collect();
}
Ok(KeyedField {
keys,
windows,
seeds,
submissions,
declarations,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::composite::{score_agent, ScoreConfig};
fn spelled_field(windows: &[String], returns: &[f64]) -> String {
let docs = serde_json::json!([{
"agent_id": "a",
"runs": returns
.iter()
.map(|r| serde_json::json!({ "returns": [r] }))
.collect::<Vec<_>>(),
"run_keys": windows
.iter()
.map(|w| serde_json::json!({ "window": w, "seed": 0 }))
.collect::<Vec<_>>(),
}]);
serde_json::to_string(&docs).expect("serialize field")
}
#[test]
fn a_pooled_statistic_does_not_change_when_the_window_labels_are_respelled() {
let mut returns = vec![-0.3, -0.3];
returns.extend(std::iter::repeat_n(0.4, 10));
let short: Vec<String> = (1..=12).map(|i| format!("w{i}")).collect();
let padded: Vec<String> = (1..=12).map(|i| format!("w{i:02}")).collect();
let cfg = ScoreConfig {
execution_seeds_per_window: 1,
..ScoreConfig::default()
};
let drawdown = |windows: &[String]| {
let keyed = parse_keyed_field(&spelled_field(windows, &returns)).expect("keyed field");
score_agent(&keyed.submissions[0], &cfg).max_drawdown
};
assert_eq!(
drawdown(&short),
drawdown(&padded),
"renaming w1..w12 to w01..w12 changed no return, so it must change no statistic"
);
}
#[test]
fn agents_that_declare_different_window_orders_are_refused() {
let json = field(&[
("a", &[("w0", 0), ("w1", 0)]),
("b", &[("w1", 0), ("w0", 0)]),
]);
let error = parse_keyed_field(&json).expect_err("contradictory window order");
assert!(
matches!(error, RunIdentityError::WindowOrderMismatch { .. }),
"{error}"
);
}
fn field(entries: &[(&str, &[(&str, u64)])]) -> String {
let docs: Vec<serde_json::Value> = entries
.iter()
.map(|(agent_id, cells)| {
serde_json::json!({
"agent_id": agent_id,
"runs": cells
.iter()
.map(|(window, seed)| serde_json::json!({
"returns": [0.01, 0.02, *seed as f64 * 0.001, window.len() as f64 * 0.001],
}))
.collect::<Vec<_>>(),
"run_keys": cells
.iter()
.map(|(window, seed)| serde_json::json!({
"window": window,
"seed": seed,
}))
.collect::<Vec<_>>(),
})
})
.collect();
serde_json::to_string(&docs).expect("serialize field")
}
#[test]
fn a_complete_keyed_grid_is_reordered_into_one_canonical_cell_order() {
let json = field(&[
("a", &[("w0", 0), ("w0", 1), ("w1", 0), ("w1", 1)]),
("b", &[("w0", 1), ("w1", 0), ("w0", 0), ("w1", 1)]),
]);
let keyed = parse_keyed_field(&json).expect("complete grid");
assert_eq!(
keyed.keys,
vec![
RunKey {
window: "w0".into(),
seed: 0
},
RunKey {
window: "w0".into(),
seed: 1
},
RunKey {
window: "w1".into(),
seed: 0
},
RunKey {
window: "w1".into(),
seed: 1
},
]
);
assert_eq!(keyed.windows, vec!["w0".to_string(), "w1".to_string()]);
assert_eq!(keyed.seeds, vec![0, 1]);
for index in 0..keyed.keys.len() {
assert_eq!(
keyed.submissions[0].runs[index].returns, keyed.submissions[1].runs[index].returns,
"cell {index} must hold the same window and seed for both agents"
);
}
}
#[test]
fn a_legacy_unkeyed_run_array_is_refused_not_positionally_aligned() {
let json = r#"[{"agent_id":"a","runs":[{"returns":[0.01,0.02]}]}]"#;
assert_eq!(
parse_keyed_field(json).unwrap_err(),
RunIdentityError::UnkeyedSubmission {
agent_id: "a".to_string()
}
);
assert!(parse_keyed_field(json)
.unwrap_err()
.to_string()
.contains("run_keys"));
}
#[test]
fn a_missing_cell_fails_instead_of_scoring_a_partial_field() {
let json = field(&[
("a", &[("w0", 0), ("w0", 1), ("w1", 0), ("w1", 1)]),
("b", &[("w0", 0), ("w0", 1), ("w1", 0)]),
]);
assert_eq!(
parse_keyed_field(&json).unwrap_err(),
RunIdentityError::MissingCell {
agent_id: "b".to_string(),
key: RunKey {
window: "w1".into(),
seed: 1
},
}
);
}
#[test]
fn an_incomplete_cartesian_grid_fails_even_when_every_agent_agrees() {
let json = field(&[
("a", &[("w0", 0), ("w0", 1), ("w1", 0)]),
("b", &[("w0", 0), ("w0", 1), ("w1", 0)]),
]);
assert_eq!(
parse_keyed_field(&json).unwrap_err(),
RunIdentityError::IncompleteGrid {
windows: 2,
seeds: 2,
submitted: 3,
first_missing: RunKey {
window: "w1".into(),
seed: 1
},
}
);
}
#[test]
fn a_duplicated_cell_fails_instead_of_inflating_the_grid() {
let json = field(&[("a", &[("w0", 0), ("w0", 0)])]);
assert_eq!(
parse_keyed_field(&json).unwrap_err(),
RunIdentityError::DuplicateKey {
agent_id: "a".to_string(),
key: RunKey {
window: "w0".into(),
seed: 0
},
}
);
}
#[test]
fn key_count_must_match_run_count() {
let json = serde_json::json!([{
"agent_id": "a",
"runs": [{"returns": [0.01, 0.02]}, {"returns": [0.03, 0.04]}],
"run_keys": [{"window": "w0", "seed": 0}],
}])
.to_string();
assert_eq!(
parse_keyed_field(&json).unwrap_err(),
RunIdentityError::KeyCountMismatch {
agent_id: "a".to_string(),
runs: 2,
keys: 1,
}
);
}
#[test]
fn a_whitespace_padded_window_identity_is_refused() {
let json = field(&[("a", &[(" w0", 0)])]);
assert!(matches!(
parse_keyed_field(&json),
Err(RunIdentityError::InvalidKey { .. })
));
}
#[test]
fn declared_period_identities_must_index_the_returns_and_agree_across_agents() {
let one = serde_json::json!([{
"agent_id": "a",
"runs": [{"returns": [0.01, 0.02]}],
"run_keys": [{"window": "w0", "seed": 0, "periods": ["d1"]}],
}])
.to_string();
assert_eq!(
parse_keyed_field(&one).unwrap_err(),
RunIdentityError::PeriodCountMismatch {
agent_id: "a".to_string(),
key: RunKey {
window: "w0".into(),
seed: 0
},
periods: 1,
returns: 2,
}
);
let two = serde_json::json!([
{
"agent_id": "a",
"runs": [{"returns": [0.01, 0.02]}],
"run_keys": [{"window": "w0", "seed": 0, "periods": ["d1", "d2"]}],
},
{
"agent_id": "b",
"runs": [{"returns": [0.03, 0.04]}],
"run_keys": [{"window": "w0", "seed": 0, "periods": ["d1", "d3"]}],
},
])
.to_string();
assert_eq!(
parse_keyed_field(&two).unwrap_err(),
RunIdentityError::PeriodMismatch {
key: RunKey {
window: "w0".into(),
seed: 0
},
agent_id: "b".to_string(),
other_agent_id: "a".to_string(),
index: 1,
}
);
}
fn periods_field(entries: &[(&str, &[&str])]) -> String {
let docs: Vec<serde_json::Value> = entries
.iter()
.map(|(agent_id, periods)| {
let returns: Vec<f64> = (0..periods.len().max(2))
.map(|i| 0.001 * (i as f64 + 1.0))
.collect();
let mut key = serde_json::json!({"window": "w0", "seed": 0});
if !periods.is_empty() {
key["periods"] = serde_json::json!(periods);
}
serde_json::json!({
"agent_id": agent_id,
"runs": [{"returns": returns}],
"run_keys": [key],
})
})
.collect();
serde_json::to_string(&docs).expect("serialize field")
}
fn w0() -> RunKey {
RunKey {
window: "w0".into(),
seed: 0,
}
}
#[test]
fn a_run_repeating_a_period_is_refused_naming_the_period_and_both_indices() {
let distinct = periods_field(&[("a", &["d1", "d2", "d3", "d4"])]);
assert!(parse_keyed_field(&distinct).is_ok());
let repeated = periods_field(&[("a", &["d1", "d2", "d3", "d2"])]);
let error = parse_keyed_field(&repeated).unwrap_err();
assert_eq!(
error,
RunIdentityError::DuplicatePeriod {
agent_id: "a".to_string(),
key: w0(),
period: "d2".to_string(),
first: 1,
second: 3,
}
);
assert_eq!(
error.to_string(),
"run identity: agent `a` (window `w0`, seed 0) declares period `d2` at \
index 1 and again at index 3; one period cannot contribute two returns \
to a run"
);
}
#[test]
fn the_first_repeat_is_the_one_reported() {
assert_eq!(first_repeated_period(&[]), None);
let periods: Vec<String> = ["d1", "d2", "d3", "d3", "d1"]
.iter()
.map(|p| p.to_string())
.collect();
assert_eq!(first_repeated_period(&periods), Some((2, 3)));
}
#[test]
fn a_repeated_period_is_refused_even_when_every_agent_repeats_it() {
let shared = periods_field(&[("a", &["d1", "d1"]), ("b", &["d1", "d1"])]);
assert_eq!(
parse_keyed_field(&shared).unwrap_err(),
RunIdentityError::DuplicatePeriod {
agent_id: "a".to_string(),
key: w0(),
period: "d1".to_string(),
first: 0,
second: 1,
}
);
let one_declares = periods_field(&[("a", &[]), ("b", &["d1", "d1"])]);
assert_eq!(
parse_keyed_field(&one_declares).unwrap_err(),
RunIdentityError::DuplicatePeriod {
agent_id: "b".to_string(),
key: w0(),
period: "d1".to_string(),
first: 0,
second: 1,
}
);
}
type Cell<'a> = (&'a str, u64, &'a [&'a str]);
fn cells_field(entries: &[(&str, &[Cell<'_>])]) -> String {
let docs: Vec<serde_json::Value> = entries
.iter()
.map(|(agent_id, cells)| {
serde_json::json!({
"agent_id": agent_id,
"runs": cells
.iter()
.map(|(_, seed, periods)| serde_json::json!({
"returns": (0..periods.len().max(2))
.map(|i| 0.001 * (i as f64 + 1.0) + *seed as f64 * 0.0001)
.collect::<Vec<_>>(),
}))
.collect::<Vec<_>>(),
"run_keys": cells
.iter()
.map(|(window, seed, periods)| {
let mut key = serde_json::json!({"window": window, "seed": seed});
if !periods.is_empty() {
key["periods"] = serde_json::json!(periods);
}
key
})
.collect::<Vec<_>>(),
})
})
.collect();
serde_json::to_string(&docs).expect("serialize field")
}
fn cell(window: &str, seed: u64) -> RunKey {
RunKey {
window: window.into(),
seed,
}
}
fn overlap(
period: &str,
first: RunKey,
first_agent_id: &str,
second: RunKey,
second_agent_id: &str,
) -> RunIdentityError {
RunIdentityError::PeriodInTwoWindows(Box::new(PeriodOverlap {
period: period.to_string(),
first,
first_agent_id: first_agent_id.to_string(),
second,
second_agent_id: second_agent_id.to_string(),
}))
}
#[test]
fn seeds_of_one_window_share_its_periods() {
let both: &[Cell<'_>] = &[
("w0", 0, &["a", "b"]),
("w0", 1, &["a", "b"]),
("w1", 0, &["c", "d"]),
("w1", 1, &["c", "d"]),
];
let keyed = parse_keyed_field(&cells_field(&[("x", both), ("y", both)]))
.expect("replicate seeds of one window are the grid, not a repeat");
assert_eq!(keyed.seeds, vec![0, 1]);
assert_eq!(keyed.windows, vec!["w0".to_string(), "w1".to_string()]);
}
#[test]
fn a_period_declared_in_two_windows_is_refused() {
let overlapping: &[Cell<'_>] = &[("w0", 0, &["a", "b"]), ("w1", 0, &["b", "c"])];
let error =
parse_keyed_field(&cells_field(&[("x", overlapping), ("y", overlapping)])).unwrap_err();
assert_eq!(error, overlap("b", cell("w0", 0), "x", cell("w1", 0), "x"));
assert_eq!(
error.to_string(),
"run identity: period `b` is declared in (window `w0`, seed 0) by agent `x` \
and in (window `w1`, seed 0) by agent `x`; a period belongs to one window, \
and the pooled track would count its return twice. Seeds of one window may \
share periods, different windows may not"
);
let copied: &[Cell<'_>] = &[("w0", 0, &["a", "b"]), ("w1", 0, &["a", "b"])];
assert_eq!(
parse_keyed_field(&cells_field(&[("x", copied)])).unwrap_err(),
overlap("a", cell("w0", 0), "x", cell("w1", 0), "x")
);
let disjoint: &[Cell<'_>] = &[("w0", 0, &["a", "b"]), ("w1", 0, &["c", "d"])];
assert!(parse_keyed_field(&cells_field(&[("x", disjoint)])).is_ok());
}
#[test]
fn a_period_in_two_windows_is_refused_whatever_the_seeds() {
let crossed: &[Cell<'_>] = &[
("w0", 0, &["a", "b"]),
("w0", 1, &["c", "d"]),
("w1", 0, &["c", "d"]),
("w1", 1, &["a", "b"]),
];
assert_eq!(
parse_keyed_field(&cells_field(&[("x", crossed)])).unwrap_err(),
overlap("c", cell("w0", 1), "x", cell("w1", 0), "x")
);
}
#[test]
fn a_period_in_two_windows_is_refused_when_different_agents_declare_them() {
let x: &[Cell<'_>] = &[("w0", 0, &["a", "b"]), ("w1", 0, &[])];
let y: &[Cell<'_>] = &[("w0", 0, &[]), ("w1", 0, &["b", "c"])];
assert_eq!(
parse_keyed_field(&cells_field(&[("x", x), ("y", y)])).unwrap_err(),
overlap("b", cell("w0", 0), "x", cell("w1", 0), "y")
);
}
#[test]
fn a_keyed_field_still_parses_as_a_plain_submission_array() {
let json = field(&[("a", &[("w0", 0)])]);
let subs: Vec<AgentSubmission> = serde_json::from_str(&json).expect("plain parse");
assert_eq!(subs.len(), 1);
assert_eq!(subs[0].runs.len(), 1);
}
#[test]
fn an_empty_field_has_no_grid() {
assert_eq!(
parse_keyed_field("[]").unwrap_err(),
RunIdentityError::EmptyField
);
}
}