rustsim-core 0.0.1

Core ABM engine: agents, models, stores, schedulers, stepping, data collection
Documentation
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rand::rngs::StdRng;
use rand::SeedableRng;
use rustsim_core::interaction::{PositionedAgent, SpaceInteraction};
use rustsim_core::prelude::*;

#[derive(Debug, Default, Clone, Copy)]
struct NothingSpace;

impl Space for NothingSpace {}

impl<A> SpaceInteraction<A> for NothingSpace
where
    A: PositionedAgent<Position = ()>,
{
    type Error = std::convert::Infallible;

    fn random_position<R: rand::RngCore>(&self, _rng: &mut R) -> A::Position {}

    fn add_agent(&mut self, _agent: &A) -> Result<(), Self::Error> {
        Ok(())
    }

    fn remove_agent(&mut self, _agent: &A) -> Result<(), Self::Error> {
        Ok(())
    }

    fn nearby_ids(&self, _position: &A::Position, _radius: usize) -> Vec<AgentId> {
        Vec::new()
    }
}

#[derive(Debug, Clone)]
struct Particle {
    id: AgentId,
    x: f32,
    vx: f32,
}

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

type ParticleModel =
    StandardModel<NothingSpace, Particle, HashMapStore<Particle>, (), StdRng, Fastest>;

fn particle_step(
    agent: &mut Particle,
    _ctx: &mut StepContext<'_, NothingSpace, Particle, (), StdRng, Fastest>,
) {
    agent.x += agent.vx;
}

fn make_model(n: u64) -> ParticleModel {
    let mut store = HashMapStore::new();
    for i in 1..=n {
        store.insert(Particle {
            id: i,
            x: 0.0,
            vx: 0.001,
        });
    }
    ParticleModel::new(
        store,
        NothingSpace,
        Fastest::new(),
        (),
        StdRng::seed_from_u64(42),
        Some(Box::new(particle_step)),
        None,
        true,
    )
}

fn bench_step_1k(c: &mut Criterion) {
    let mut model = make_model(1_000);
    c.bench_function("step 1k agents", |b| {
        b.iter(|| {
            model.step();
            black_box(model.time());
        });
    });
}

fn bench_step_100k(c: &mut Criterion) {
    let mut model = make_model(100_000);
    c.bench_function("step 100k agents", |b| {
        b.iter(|| {
            model.step();
            black_box(model.time());
        });
    });
}

fn bench_step_with_removal(c: &mut Criterion) {
    c.bench_function("step 10k with 50% deferred removal", |b| {
        b.iter_batched(
            || {
                fn remove_evens(
                    agent: &mut Particle,
                    ctx: &mut StepContext<'_, NothingSpace, Particle, (), StdRng, Fastest>,
                ) {
                    if agent.id.is_multiple_of(2) {
                        ctx.defer_remove_agent(agent.id());
                    } else {
                        agent.x += agent.vx;
                    }
                }

                let mut store = HashMapStore::new();
                for i in 1..=10_000u64 {
                    store.insert(Particle {
                        id: i,
                        x: 0.0,
                        vx: 0.001,
                    });
                }
                ParticleModel::new(
                    store,
                    NothingSpace,
                    Fastest::new(),
                    (),
                    StdRng::seed_from_u64(42),
                    Some(Box::new(remove_evens)),
                    None,
                    true,
                )
            },
            |mut model| {
                model.step();
                black_box(model.agents_len());
            },
            criterion::BatchSize::SmallInput,
        );
    });
}

criterion_group!(
    benches,
    bench_step_1k,
    bench_step_100k,
    bench_step_with_removal
);
criterion_main!(benches);