use crate::model::action::{Action, Rates, Touched};
use crate::model::{Schedule, SimRng, Spawn, Time, World};
use super::heap::{IndexedHeap, Priority};
#[derive(Clone)]
pub(crate) struct Engine {
clock: Time,
pub(crate) rates: Rates,
heap: IndexedHeap,
}
impl Engine {
pub(crate) fn new(rates: Rates, start_time: Time) -> Self {
Engine {
clock: start_time,
rates,
heap: IndexedHeap::new(),
}
}
pub(crate) fn initialize(&mut self, world: &World, rng: &mut SimRng) {
let total = 2 * world.legs.len() + world.lefs.len();
self.heap = IndexedHeap::with_capacity(total);
for k in world.legs.keys().collect::<Vec<_>>() {
for action in [Action::Step { leg: k }, Action::Evict { leg: k }] {
let priority = match action.schedule(world, &self.rates, self.clock, rng) {
Schedule::At(t) => Priority(t),
Schedule::Never => Priority::NEVER,
};
self.heap.insert(action, priority);
}
}
for k in world.lefs.keys().collect::<Vec<_>>() {
let action = Action::Respawn { lef: k };
let priority = match action.schedule(world, &self.rates, self.clock, rng) {
Schedule::At(t) => Priority(t),
Schedule::Never => Priority::NEVER,
};
self.heap.insert(action, priority);
}
}
pub(crate) fn clock(&self) -> Time {
self.clock
}
pub(crate) fn next_event_time(&self) -> Option<Time> {
self.heap
.peek()
.filter(|(p, _)| p.is_finite())
.map(|(p, _)| p.0)
}
pub(crate) fn advance_clock_to(&mut self, t: Time) {
debug_assert!(t >= self.clock, "clock must be monotone");
debug_assert!(
self.next_event_time().is_none_or(|next| t <= next),
"cannot advance past a pending event"
);
self.clock = t;
}
pub(crate) fn reschedule(&mut self, action: Action, world: &World, rng: &mut SimRng) {
let priority = match action.schedule(world, &self.rates, self.clock, rng) {
Schedule::At(t) => Priority(t),
Schedule::Never => Priority::NEVER,
};
self.heap.update(action, priority);
}
pub(crate) fn step(
&mut self,
world: &mut World,
spawn: &dyn Spawn,
rng: &mut SimRng,
touched: &mut Touched,
) -> Option<(Time, Action)> {
let (priority, action) = self.heap.peek()?;
if !priority.is_finite() {
return None;
}
let time = priority.0;
self.clock = time;
touched.clear();
action.fire(world, spawn, time, rng, touched);
for dep in touched.dependent_actions(world, action) {
self.reschedule(dep, world, rng);
}
Some((time, action))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{Direction, Lattice, Site, SpawnRule};
use rand::SeedableRng;
#[test]
fn next_event_time_predicts_next_step() {
let mut sites = vec![Site::plain(); 100];
sites[30] = Site::ctcf(Direction::Right, 0.5);
sites[70] = Site::ctcf(Direction::Left, 0.5);
let mut world = World::new(Lattice::new(sites).unwrap());
let mut rng = SimRng::seed_from_u64(3);
world.spawn_lef(40, &mut rng);
world.spawn_lef(60, &mut rng);
let mut engine = Engine::new(Rates::default(), 0.0);
engine.initialize(&world, &mut rng);
let mut touched = Touched::default();
for _ in 0..2000 {
let predicted = engine.next_event_time();
let fired = engine
.step(
&mut world,
&SpawnRule::UniformNonCtcf,
&mut rng,
&mut touched,
)
.map(|(t, _)| t);
assert_eq!(predicted, fired);
assert_eq!(engine.clock(), fired.unwrap());
}
}
#[test]
fn advance_clock_to_leaves_next_event_untouched() {
let mut world = World::new(Lattice::plain(50).unwrap());
let mut rng = SimRng::seed_from_u64(5);
world.spawn_lef(25, &mut rng);
let mut engine = Engine::new(Rates::default(), 0.0);
engine.initialize(&world, &mut rng);
let next = engine.next_event_time().unwrap();
engine.advance_clock_to(next / 2.0);
assert_eq!(engine.clock(), next / 2.0);
assert_eq!(engine.next_event_time(), Some(next));
}
}