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},
    interaction::{PositionedAgent, SpaceInteraction},
    prelude::{Agent, AgentStore, HashMapStore},
    types::AgentId,
};
mod support;
use support::{Grid2D, GridPos2, NothingSpace};

#[derive(Debug, Clone)]
struct TimedAgent {
    id: AgentId,
    log: Vec<(usize, u64)>,
}

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

type TimedModel = EventQueueModel<NothingSpace, TimedAgent, HashMapStore<TimedAgent>, (), StdRng>;

#[derive(Debug, Clone)]
struct TimedGridAgent {
    id: AgentId,
    pos: GridPos2,
}

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

impl PositionedAgent for TimedGridAgent {
    type Position = GridPos2;

    fn position(&self) -> &Self::Position {
        &self.pos
    }

    fn set_position(&mut self, position: Self::Position) {
        self.pos = position;
    }
}

type TimedGridModel =
    EventQueueModel<Grid2D, TimedGridAgent, HashMapStore<TimedGridAgent>, (), StdRng>;

fn action_a(
    agent: &mut TimedAgent,
    ctx: &mut EventContext<'_, NothingSpace, TimedAgent, (), StdRng>,
) {
    agent.log.push((0, ctx.time() as u64));
}

fn action_b(
    agent: &mut TimedAgent,
    ctx: &mut EventContext<'_, NothingSpace, TimedAgent, (), StdRng>,
) {
    agent.log.push((1, ctx.time() as u64));
}

fn spatial_action(
    agent: &mut TimedGridAgent,
    ctx: &mut EventContext<'_, Grid2D, TimedGridAgent, (), StdRng>,
) {
    if agent.id == 1 {
        ctx.defer_remove_agent(2);
        ctx.defer_insert_agent(TimedGridAgent { id: 3, pos: (4, 4) });
    }
}

#[test]
fn event_queue_processes_in_time_order() {
    let mut store = HashMapStore::new();
    store.insert(TimedAgent {
        id: 1,
        log: Vec::new(),
    });

    let actions: Vec<
        fn(&mut TimedAgent, &mut EventContext<'_, NothingSpace, TimedAgent, (), StdRng>),
    > = vec![action_a, action_b];

    let mut model = TimedModel::new(store, NothingSpace, (), StdRng::seed_from_u64(42), actions);

    model.add_event(1, 1, 5.0);
    model.add_event(1, 0, 2.0);
    model.add_event(1, 0, 8.0);

    assert_eq!(model.queue_len(), 3);

    model.step_event();
    assert!((model.time_f64() - 2.0).abs() < 1e-10);

    model.step_event();
    assert!((model.time_f64() - 5.0).abs() < 1e-10);

    model.step_event();
    assert!((model.time_f64() - 8.0).abs() < 1e-10);

    let agent = model.agent(1).unwrap();
    assert_eq!(agent.log.len(), 3);
    assert_eq!(agent.log[0], (0, 2));
    assert_eq!(agent.log[1], (1, 5));
    assert_eq!(agent.log[2], (0, 8));
}

#[test]
fn event_queue_step_until_stops_at_boundary() {
    let mut store = HashMapStore::new();
    store.insert(TimedAgent {
        id: 1,
        log: Vec::new(),
    });

    let actions: Vec<
        fn(&mut TimedAgent, &mut EventContext<'_, NothingSpace, TimedAgent, (), StdRng>),
    > = vec![action_a];

    let mut model = TimedModel::new(store, NothingSpace, (), StdRng::seed_from_u64(0), actions);

    model.add_event(1, 0, 1.0);
    model.add_event(1, 0, 3.0);
    model.add_event(1, 0, 7.0);

    model.step_until(5.0);

    assert!((model.time_f64() - 5.0).abs() < 1e-10);

    let agent = model.agent(1).unwrap();
    assert_eq!(agent.log.len(), 2);

    assert_eq!(model.queue_len(), 1);
}

#[test]
fn event_queue_deterministic_tiebreak() {
    let mut store = HashMapStore::new();
    store.insert(TimedAgent {
        id: 1,
        log: Vec::new(),
    });

    let actions: Vec<
        fn(&mut TimedAgent, &mut EventContext<'_, NothingSpace, TimedAgent, (), StdRng>),
    > = vec![action_a, action_b];

    let mut model = TimedModel::new(store, NothingSpace, (), StdRng::seed_from_u64(0), actions);

    model.add_event(1, 0, 1.0);
    model.add_event(1, 1, 1.0);

    model.run_events(2);

    let agent = model.agent(1).unwrap();
    assert_eq!(agent.log.len(), 2);
    assert_eq!(agent.log[0].0, 0);
    assert_eq!(agent.log[1].0, 1);
}

#[test]
fn event_queue_skips_removed_agent() {
    let mut store = HashMapStore::new();
    store.insert(TimedAgent {
        id: 1,
        log: Vec::new(),
    });
    store.insert(TimedAgent {
        id: 2,
        log: Vec::new(),
    });

    let actions: Vec<
        fn(&mut TimedAgent, &mut EventContext<'_, NothingSpace, TimedAgent, (), StdRng>),
    > = vec![action_a];

    let mut model = TimedModel::new(store, NothingSpace, (), StdRng::seed_from_u64(0), actions);

    model.add_event(1, 0, 1.0);
    model.add_event(2, 0, 2.0);

    model.remove_agent(1);

    model.step_event();
    assert!((model.time_f64() - 1.0).abs() < 1e-10);

    model.step_event();
    assert!((model.time_f64() - 2.0).abs() < 1e-10);

    let agent2 = model.agent(2).unwrap();
    assert_eq!(agent2.log.len(), 1);
}

#[test]
fn event_queue_spatial_applies_deferred_actions_to_store_and_space() {
    let store = HashMapStore::new();
    let actions: Vec<
        fn(&mut TimedGridAgent, &mut EventContext<'_, Grid2D, TimedGridAgent, (), StdRng>),
    > = vec![spatial_action];
    let mut model = TimedGridModel::new(
        store,
        Grid2D::new(10, 10, false),
        (),
        StdRng::seed_from_u64(0),
        actions,
    );

    model
        .insert_positioned_agent(TimedGridAgent { id: 1, pos: (1, 1) })
        .unwrap();
    model
        .insert_positioned_agent(TimedGridAgent { id: 2, pos: (2, 2) })
        .unwrap();
    model.add_event(1, 0, 1.0);

    assert!(model.step_event_spatial().unwrap());
    model.validate_space_index().unwrap();

    assert!(model.agent(1).is_some());
    assert!(model.agent(2).is_none());
    assert!(model.agent(3).is_some());

    let old_ids =
        <Grid2D as SpaceInteraction<TimedGridAgent>>::nearby_ids(model.space(), &(2, 2), 0);
    assert!(!old_ids.contains(&2));

    let new_ids =
        <Grid2D as SpaceInteraction<TimedGridAgent>>::nearby_ids(model.space(), &(4, 4), 0);
    assert_eq!(new_ids, vec![3]);
}