Skip to main content

rustsim_core/
standard.rs

1//! Discrete-time standard model.
2//!
3//! [`StandardModel`] is the primary model type, mirroring Julia Agents.jl
4//! `StandardABM`. Each tick:
5//!
6//! 1. The scheduler produces an ordered list of agent IDs.
7//! 2. For each agent, the `agent_step` function is called with a [`StepContext`].
8//! 3. Deferred actions (add/remove) are applied after each agent step.
9//! 4. The `model_step` function is called once (if provided).
10//! 5. Time advances by 1.
11//!
12//! The `agents_first` flag controls whether agent steps run before or after
13//! the model step (default: agents first).
14//!
15//! # Determinism
16//!
17//! `StandardModel` is replayable for a fixed seed when all of the following are
18//! held constant:
19//! - the same crate version and user step code
20//! - the same initial agents and properties
21//! - the same scheduler semantics
22//! - the same store-dependent ID enumeration behavior where relevant
23//! - the same sequence of deferred insert/remove actions
24//!
25//! In practice:
26//! - [`crate::scheduler::ById`] provides the strongest built-in ordering guarantee
27//! - [`crate::scheduler::Fastest`] inherits store iteration order
28//! - random schedulers remain reproducible only if the pre-randomization ID list is deterministic
29//!
30//! [`StepContext`]: crate::step_context::StepContext
31
32use crate::{
33    agent::Agent,
34    interaction::{InteractionError, PositionedAgent, SpaceInteraction},
35    model::Model,
36    scheduler::Scheduler,
37    space::Space,
38    store::AgentStore,
39    types::{AgentId, Time},
40};
41use rand::RngCore;
42use tracing::{debug, trace};
43
44use std::cell::{Ref, RefCell, RefMut};
45use std::marker::PhantomData;
46
47/// Trait for types that can provide a snapshot of current agent IDs.
48///
49/// Implemented by [`StandardModel`] to allow schedulers to read agent IDs
50/// without full `Model` bounds.
51pub trait HasAgentIds {
52    /// Collect all agent IDs into a new `Vec`.
53    fn agent_ids(&self) -> Vec<AgentId>;
54
55    /// Append all agent IDs into `buf` (does not clear it first).
56    fn agent_ids_into(&self, buf: &mut Vec<AgentId>);
57}
58
59impl<S, A, Store, Props, R, Sch> StandardModel<S, A, Store, Props, R, Sch>
60where
61    A: PositionedAgent,
62    S: SpaceInteraction<A>,
63    Store: AgentStore<A>,
64    R: RngCore,
65    Sch: Scheduler<Self>,
66{
67    /// Insert a positioned agent into both the store and the space atomically.
68    pub fn insert_positioned_agent(&mut self, agent: A) -> Result<(), InteractionError<S::Error>> {
69        crate::interaction::add_agent(self, agent)
70    }
71
72    /// Remove a positioned agent from both the store and the space atomically.
73    pub fn remove_positioned_agent(
74        &mut self,
75        id: AgentId,
76    ) -> Result<Option<A>, InteractionError<S::Error>> {
77        crate::interaction::remove_agent(self, id)
78    }
79
80    /// Move a positioned agent, updating both the agent value and spatial index.
81    pub fn move_positioned_agent(
82        &mut self,
83        id: AgentId,
84        new_position: A::Position,
85    ) -> Result<(), InteractionError<S::Error>> {
86        crate::interaction::move_agent(self, id, new_position)
87    }
88
89    /// Validate that all stored agents are represented by the spatial index.
90    pub fn validate_space_index(&self) -> Result<(), InteractionError<S::Error>> {
91        crate::interaction::validate_space_index(self)
92    }
93
94    /// Advance one tick while applying deferred add/remove actions to both the
95    /// agent store and the spatial index.
96    pub fn step_spatial(&mut self) -> Result<(), InteractionError<S::Error>> {
97        let has_agent_step = self.agent_step_ctx.is_some();
98        let has_model_step = self.model_step.is_some();
99
100        if !(has_agent_step || has_model_step) {
101            trace!("spatial step skipped: no agent_step or model_step defined");
102            return Ok(());
103        }
104
105        if self.agents_first {
106            self.step_agents_spatial()?;
107            self.step_model();
108        } else {
109            self.step_model();
110            self.step_agents_spatial()?;
111        }
112
113        self.time = match self.time {
114            Time::Discrete(t) => Time::Discrete(t.saturating_add(1)),
115            Time::Continuous(t) => Time::Continuous(t + 1.0),
116        };
117
118        debug!(time = %self.time, agents = self.agents.len(), "spatial step completed");
119        Ok(())
120    }
121
122    /// Advance `n` spatially-consistent ticks.
123    pub fn run_spatial(&mut self, steps: usize) -> Result<(), InteractionError<S::Error>> {
124        for _ in 0..steps {
125            self.step_spatial()?;
126        }
127        Ok(())
128    }
129
130    fn apply_deferred_actions_spatial(
131        &mut self,
132        deferred: &mut Vec<crate::step_context::DeferredAction<A>>,
133    ) -> Result<(), InteractionError<S::Error>> {
134        use crate::step_context::DeferredAction;
135
136        if deferred.is_empty() {
137            return Ok(());
138        }
139
140        for action in deferred.drain(..) {
141            match action {
142                DeferredAction::RemoveAgent(id) => {
143                    crate::interaction::remove_agent(self, id)?;
144                }
145                DeferredAction::InsertAgent(agent) => {
146                    crate::interaction::add_agent(self, agent)?;
147                }
148            }
149        }
150        Ok(())
151    }
152
153    fn step_agents_spatial(&mut self) -> Result<(), InteractionError<S::Error>> {
154        let mut ids = std::mem::take(&mut self.schedule_buf);
155        ids.clear();
156        {
157            let mut sched = self.scheduler.borrow_mut();
158            sched.schedule_into(&*self, &mut ids);
159        }
160
161        if self.agent_step_ctx.is_none() {
162            self.schedule_buf = ids;
163            return Ok(());
164        }
165
166        let mut deferred = std::mem::take(&mut self.deferred_buf);
167        deferred.clear();
168
169        for &id in &ids {
170            let Some(mut agent_ref) = self.agents.get_mut(id) else {
171                continue;
172            };
173
174            {
175                let mut rng = self.rng.borrow_mut();
176                let mut sched = self.scheduler.borrow_mut();
177
178                let mut ctx = crate::step_context::StepContext {
179                    space: &mut self.space,
180                    properties: &mut self.properties,
181                    rng: &mut *rng,
182                    scheduler: &mut *sched,
183                    deferred: &mut deferred,
184                    _agent: PhantomData,
185                };
186
187                if let Some(step_fn) = self.agent_step_ctx.as_mut() {
188                    step_fn(&mut *agent_ref, &mut ctx);
189                }
190            }
191
192            drop(agent_ref);
193            self.apply_deferred_actions_spatial(&mut deferred)?;
194        }
195
196        self.deferred_buf = deferred;
197        self.schedule_buf = ids;
198        Ok(())
199    }
200}
201
202/// Type alias for the boxed agent step function.
203///
204/// The step function receives a mutable reference to the current agent and
205/// a [`StepContext`] providing safe access to space, properties, and RNG.
206///
207/// [`StepContext`]: crate::step_context::StepContext
208pub type AgentStepFn<S, A, Props, R, Sch> =
209    Box<dyn for<'a> FnMut(&mut A, &mut crate::step_context::StepContext<'a, S, A, Props, R, Sch>)>;
210
211/// Discrete-time agent-based model.
212///
213/// This is the main model type for tick-based simulations. It stores agents
214/// in an [`AgentStore`], uses a [`Scheduler`] to determine activation order,
215/// and calls user-provided step functions each tick.
216///
217/// # Type Parameters
218///
219/// - `S` - space type (e.g. `NothingSpace`, `Grid2D`)
220/// - `A` - agent type implementing [`Agent`]
221/// - `Store` - agent container implementing [`AgentStore<A>`]
222/// - `Props` - user-defined model properties (use `()` if unused)
223/// - `R` - RNG type (e.g. `StdRng`)
224/// - `Sch` - scheduler type (e.g. [`Fastest`], [`Randomly`])
225///
226/// # Example
227///
228/// ```ignore
229/// type MyModel = StandardModel<NothingSpace, Particle, HashMapStore<Particle>, (), StdRng, Fastest>;
230///
231/// let mut model = MyModel::new(store, space, Fastest::new(), (), rng, Some(Box::new(step_fn)), None, true);
232/// model.step_n(100);
233/// ```
234///
235/// [`Agent`]: crate::agent::Agent
236/// [`AgentStore`]: crate::store::AgentStore
237/// [`Fastest`]: crate::scheduler::Fastest
238/// [`Randomly`]: crate::scheduler::Randomly
239pub struct StandardModel<S, A, Store, Props, R, Sch>
240where
241    A: Agent,
242    S: Space,
243    Store: AgentStore<A>,
244    R: RngCore,
245{
246    pub(crate) agents: Store,
247    pub(crate) space: S,
248    pub(crate) scheduler: RefCell<Sch>,
249    pub(crate) properties: Props,
250    pub(crate) rng: RefCell<R>,
251    pub(crate) time: Time,
252    pub(crate) max_id: AgentId,
253    pub(crate) agents_first: bool,
254    pub(crate) agent_step_ctx: Option<AgentStepFn<S, A, Props, R, Sch>>,
255    pub(crate) model_step: Option<fn(&mut Self)>,
256    pub(crate) deferred_buf: Vec<crate::step_context::DeferredAction<A>>,
257    pub(crate) schedule_buf: Vec<AgentId>,
258    pub(crate) agent_marker: PhantomData<A>,
259}
260
261impl<S, A, Store, Props, R, Sch> StandardModel<S, A, Store, Props, R, Sch>
262where
263    A: Agent,
264    S: Space,
265    Store: AgentStore<A>,
266    R: RngCore,
267    Sch: Scheduler<Self>,
268{
269    #[allow(clippy::too_many_arguments)]
270    /// Create a new `StandardModel`.
271    ///
272    /// # Arguments
273    ///
274    /// - `agents` - pre-populated agent store.
275    /// - `space` - the simulation space.
276    /// - `scheduler` - controls agent activation order.
277    /// - `properties` - user-defined model properties.
278    /// - `rng` - seeded random number generator.
279    /// - `agent_step_ctx` - optional per-agent step function.
280    /// - `model_step` - optional per-tick model step function.
281    /// - `agents_first` - if `true`, agent steps run before the model step.
282    pub fn new(
283        agents: Store,
284        space: S,
285        scheduler: Sch,
286        properties: Props,
287        rng: R,
288        agent_step_ctx: Option<AgentStepFn<S, A, Props, R, Sch>>,
289        model_step: Option<fn(&mut Self)>,
290        agents_first: bool,
291    ) -> Self {
292        let max_id = agents.iter_ids().into_iter().max().unwrap_or(0);
293        Self {
294            agents,
295            space,
296            scheduler: RefCell::new(scheduler),
297            properties,
298            rng: RefCell::new(rng),
299            time: Time::Discrete(0),
300            max_id,
301            agents_first,
302            agent_step_ctx,
303            model_step,
304            deferred_buf: Vec::new(),
305            schedule_buf: Vec::new(),
306            agent_marker: PhantomData,
307        }
308    }
309
310    /// Create a model with no step functions configured yet.
311    ///
312    /// This is the most ergonomic starting point for external consumers who
313    /// want to configure the model via builder-style methods.
314    ///
315    /// Defaults:
316    /// - no `agent_step_ctx`
317    /// - no `model_step`
318    /// - `agents_first = true`
319    pub fn new_base(agents: Store, space: S, scheduler: Sch, properties: Props, rng: R) -> Self {
320        Self::new(agents, space, scheduler, properties, rng, None, None, true)
321    }
322
323    /// Create a model with an agent step configured and no model step.
324    ///
325    /// This avoids `Some(Box::new(...))` boilerplate in the common case of a
326    /// purely agent-driven simulation.
327    pub fn new_with_agent_step(
328        agents: Store,
329        space: S,
330        scheduler: Sch,
331        properties: Props,
332        rng: R,
333        agent_step_ctx: impl for<'a> FnMut(&mut A, &mut crate::step_context::StepContext<'a, S, A, Props, R, Sch>)
334            + 'static,
335        agents_first: bool,
336    ) -> Self {
337        Self::new(
338            agents,
339            space,
340            scheduler,
341            properties,
342            rng,
343            Some(Box::new(agent_step_ctx)),
344            None,
345            agents_first,
346        )
347    }
348
349    /// Create a model with a model step configured and no agent step.
350    pub fn new_with_model_step(
351        agents: Store,
352        space: S,
353        scheduler: Sch,
354        properties: Props,
355        rng: R,
356        model_step: fn(&mut Self),
357        agents_first: bool,
358    ) -> Self {
359        Self::new(
360            agents,
361            space,
362            scheduler,
363            properties,
364            rng,
365            None,
366            Some(model_step),
367            agents_first,
368        )
369    }
370
371    /// Builder-style method to set or replace the agent step function.
372    pub fn with_agent_step_ctx(
373        mut self,
374        agent_step_ctx: impl for<'a> FnMut(&mut A, &mut crate::step_context::StepContext<'a, S, A, Props, R, Sch>)
375            + 'static,
376    ) -> Self {
377        self.agent_step_ctx = Some(Box::new(agent_step_ctx));
378        self
379    }
380
381    /// Builder-style method to set or replace the model step function.
382    pub fn with_model_step(mut self, model_step: fn(&mut Self)) -> Self {
383        self.model_step = Some(model_step);
384        self
385    }
386
387    /// Builder-style method to configure whether agent steps run before model steps.
388    pub fn with_agents_first(mut self, agents_first: bool) -> Self {
389        self.agents_first = agents_first;
390        self
391    }
392
393    /// Current simulation time.
394    pub fn time(&self) -> Time {
395        self.time
396    }
397
398    /// Mutable access to the model's RNG (via `RefCell`).
399    pub fn rng_mut(&self) -> RefMut<'_, R> {
400        self.rng.borrow_mut()
401    }
402
403    /// Immutable reference to the simulation space.
404    pub fn space(&self) -> &S {
405        &self.space
406    }
407
408    /// Mutable reference to the simulation space.
409    pub fn space_mut(&mut self) -> &mut S {
410        &mut self.space
411    }
412
413    /// Immutable reference to user-defined properties.
414    pub fn properties(&self) -> &Props {
415        &self.properties
416    }
417
418    /// Mutable reference to user-defined properties.
419    pub fn properties_mut(&mut self) -> &mut Props {
420        &mut self.properties
421    }
422
423    /// Borrow an agent immutably by ID.
424    pub fn agent(&self, id: AgentId) -> Option<Ref<'_, A>> {
425        self.agents.get(id)
426    }
427
428    /// Borrow an agent mutably by ID.
429    pub fn agent_mut(&self, id: AgentId) -> Option<RefMut<'_, A>> {
430        self.agents.get_mut(id)
431    }
432
433    /// Insert an agent into the store.
434    ///
435    /// Returns `Err(agent)` if an agent with the same ID already exists
436    /// (the agent is returned back to the caller).
437    pub fn insert_agent(&mut self, agent: A) -> Result<(), A> {
438        let id = agent.id();
439        if self.agents.contains(id) {
440            return Err(agent);
441        }
442        self.agents.insert(agent);
443        if id > self.max_id {
444            self.max_id = id;
445        }
446        Ok(())
447    }
448
449    /// Remove an agent by ID, returning it if found.
450    pub fn remove_agent(&mut self, id: AgentId) -> Option<A> {
451        self.agents.remove(id)
452    }
453
454    /// Generate the next unused agent ID (monotonically increasing).
455    pub fn next_id(&mut self) -> AgentId {
456        self.max_id += 1;
457        self.max_id
458    }
459
460    /// Advance the simulation by one time step.
461    ///
462    /// Runs agent steps and model step in the order determined by `agents_first`,
463    /// then increments time by 1.
464    pub fn step(&mut self) {
465        let has_agent_step = self.agent_step_ctx.is_some();
466        let has_model_step = self.model_step.is_some();
467
468        if !(has_agent_step || has_model_step) {
469            trace!("step skipped: no agent_step or model_step defined");
470            return;
471        }
472
473        if self.agents_first {
474            self.step_agents();
475            self.step_model();
476        } else {
477            self.step_model();
478            self.step_agents();
479        }
480
481        self.time = match self.time {
482            Time::Discrete(t) => Time::Discrete(t.saturating_add(1)),
483            Time::Continuous(t) => Time::Continuous(t + 1.0),
484        };
485
486        debug!(time = %self.time, agents = self.agents.len(), "step completed");
487    }
488
489    /// Advance the simulation by `n` time steps.
490    pub fn step_n(&mut self, n: usize) {
491        for _ in 0..n {
492            self.step();
493        }
494    }
495
496    /// Alias for [`step_n`](Self::step_n).
497    pub fn run(&mut self, steps: usize) {
498        self.step_n(steps);
499    }
500
501    fn apply_deferred_actions(
502        &mut self,
503        deferred: &mut Vec<crate::step_context::DeferredAction<A>>,
504    ) {
505        use crate::step_context::DeferredAction;
506
507        if deferred.is_empty() {
508            return;
509        }
510
511        for action in deferred.drain(..) {
512            match action {
513                DeferredAction::RemoveAgent(id) => {
514                    self.remove_agent(id);
515                }
516                DeferredAction::InsertAgent(agent) => {
517                    let _ = self.insert_agent(agent);
518                }
519            }
520        }
521    }
522
523    fn step_agents(&mut self) {
524        let mut ids = std::mem::take(&mut self.schedule_buf);
525        ids.clear();
526        {
527            let mut sched = self.scheduler.borrow_mut();
528            sched.schedule_into(&*self, &mut ids);
529        }
530
531        if self.agent_step_ctx.is_none() {
532            self.schedule_buf = ids;
533            return;
534        }
535
536        let mut deferred = std::mem::take(&mut self.deferred_buf);
537        deferred.clear();
538
539        for &id in &ids {
540            let Some(mut agent_ref) = self.agents.get_mut(id) else {
541                continue;
542            };
543
544            {
545                let mut rng = self.rng.borrow_mut();
546                let mut sched = self.scheduler.borrow_mut();
547
548                let mut ctx = crate::step_context::StepContext {
549                    space: &mut self.space,
550                    properties: &mut self.properties,
551                    rng: &mut *rng,
552                    scheduler: &mut *sched,
553                    deferred: &mut deferred,
554                    _agent: PhantomData,
555                };
556
557                if let Some(step_fn) = self.agent_step_ctx.as_mut() {
558                    step_fn(&mut *agent_ref, &mut ctx);
559                }
560            }
561
562            drop(agent_ref);
563
564            self.apply_deferred_actions(&mut deferred);
565        }
566
567        self.deferred_buf = deferred;
568        self.schedule_buf = ids;
569    }
570
571    fn step_model(&mut self) {
572        if let Some(step_fn) = self.model_step {
573            step_fn(self);
574        }
575    }
576
577    /// Iterator over all agents in the store.
578    ///
579    /// Returns borrowed references. The order depends on the store implementation
580    /// and must not be treated as a stable replay contract unless the store
581    /// explicitly guarantees it.
582    pub fn agents(&self) -> impl Iterator<Item = Ref<'_, A>> {
583        self.agents
584            .iter_ids()
585            .into_iter()
586            .filter_map(|id| self.agents.get(id))
587    }
588
589    /// Number of agents currently in the store.
590    pub fn agents_len(&self) -> usize {
591        self.agents.len()
592    }
593}
594
595impl<S, A, Store, Props, R, Sch> HasAgentIds for StandardModel<S, A, Store, Props, R, Sch>
596where
597    A: Agent,
598    S: Space,
599    Store: AgentStore<A>,
600    R: RngCore,
601{
602    fn agent_ids(&self) -> Vec<AgentId> {
603        self.agents.iter_ids()
604    }
605
606    fn agent_ids_into(&self, buf: &mut Vec<AgentId>) {
607        self.agents.iter_ids_into(buf);
608    }
609}
610
611impl<S, A, Store, Props, R, Sch> Model for StandardModel<S, A, Store, Props, R, Sch>
612where
613    A: Agent,
614    S: Space,
615    Store: AgentStore<A>,
616    R: rand::RngCore,
617{
618    type Agent = A;
619    type Space = S;
620    type Properties = Props;
621    type Rng = R;
622
623    // Implementing GATs
624    type AgentRef<'a>
625        = Ref<'a, A>
626    where
627        Self: 'a;
628    type AgentRefMut<'a>
629        = RefMut<'a, A>
630    where
631        Self: 'a;
632
633    fn time(&self) -> Time {
634        self.time
635    }
636
637    fn rng_mut(&self) -> impl std::ops::DerefMut<Target = Self::Rng> + '_ {
638        self.rng.borrow_mut()
639    }
640
641    fn space(&self) -> &Self::Space {
642        &self.space
643    }
644
645    fn properties(&self) -> &Self::Properties {
646        &self.properties
647    }
648
649    fn properties_mut(&mut self) -> &mut Self::Properties {
650        &mut self.properties
651    }
652
653    fn agent(&self, id: AgentId) -> Option<Self::AgentRef<'_>> {
654        self.agents.get(id)
655    }
656
657    fn agent_mut(&self, id: AgentId) -> Option<Self::AgentRefMut<'_>> {
658        self.agents.get_mut(id)
659    }
660}