rustsim-core 0.0.1

Core ABM engine: agents, models, stores, schedulers, stepping, data collection
Documentation
#![allow(clippy::type_complexity)]

use rand::rngs::StdRng;
use rand::SeedableRng;
use rustsim_core::{
    event_queue::{EventContext, EventQueueModel},
    prelude::{Agent, AgentStore, HashMapStore},
    types::AgentId,
};
mod support;
use support::NothingSpace;

#[derive(Debug, Clone)]
struct Dummy {
    id: AgentId,
}

impl Agent for Dummy {
    fn id(&self) -> AgentId {
        self.id
    }
}

type DummyModel = EventQueueModel<NothingSpace, Dummy, HashMapStore<Dummy>, (), StdRng>;

fn noop_action(_agent: &mut Dummy, _ctx: &mut EventContext<'_, NothingSpace, Dummy, (), StdRng>) {}

fn make_model() -> DummyModel {
    let mut store = HashMapStore::new();
    store.insert(Dummy { id: 1 });
    let actions: Vec<fn(&mut Dummy, &mut EventContext<'_, NothingSpace, Dummy, (), StdRng>)> =
        vec![noop_action];
    EventQueueModel::new(store, NothingSpace, (), StdRng::seed_from_u64(0), actions)
}

#[test]
#[should_panic(expected = "event dt must be finite and non-negative")]
fn add_event_rejects_nan_dt() {
    let mut model = make_model();
    model.add_event(1, 0, f64::NAN);
}

#[test]
#[should_panic(expected = "event dt must be finite and non-negative")]
fn add_event_rejects_negative_dt() {
    let mut model = make_model();
    model.add_event(1, 0, -1.0);
}

#[test]
#[should_panic(expected = "event dt must be finite and non-negative")]
fn add_event_rejects_positive_infinity() {
    let mut model = make_model();
    model.add_event(1, 0, f64::INFINITY);
}

#[test]
#[should_panic(expected = "event dt must be finite and non-negative")]
fn add_event_rejects_negative_infinity() {
    let mut model = make_model();
    model.add_event(1, 0, f64::NEG_INFINITY);
}

#[test]
fn add_event_accepts_zero_dt() {
    let mut model = make_model();
    model.add_event(1, 0, 0.0);
    assert_eq!(model.queue_len(), 1);
}

#[test]
fn step_event_returns_false_on_empty_queue() {
    let mut model = make_model();
    assert!(!model.step_event());
}

#[test]
fn step_event_skips_removed_agent_gracefully() {
    let mut model = make_model();
    model.add_event(1, 0, 1.0);
    model.remove_agent(1);
    // Should return true (event popped) but not invoke the action
    assert!(model.step_event());
    assert!(model.queue_is_empty());
}

#[test]
fn step_event_skips_out_of_range_event_idx() {
    let mut model = make_model();
    // event_idx 99 doesn't exist in actions vec (only idx 0)
    model.add_event(1, 99, 1.0);
    assert!(model.step_event());
    assert!(model.queue_is_empty());
}

#[test]
fn insert_duplicate_agent_returns_err() {
    let mut model = make_model();
    let duplicate = Dummy { id: 1 };
    let result = model.insert_agent(duplicate);
    assert!(result.is_err());
    let rejected = result.unwrap_err();
    assert_eq!(rejected.id, 1);
}

#[test]
fn remove_nonexistent_agent_returns_none() {
    let mut model = make_model();
    assert!(model.remove_agent(999).is_none());
}