pub mod action;
pub mod distance;
pub mod entity;
pub mod lattice;
#[cfg(feature = "serde")]
mod serial;
pub mod spawn;
pub mod world;
pub use action::{Action, Rates};
pub use distance::{DistanceError, LoopGraph};
pub use entity::{Lef, LefKey, Leg, LegKey, Mobility};
pub use lattice::{Ctcf, Direction, Lattice, LatticeError, Position, Site, SiteKind};
pub use spawn::{Spawn, SpawnError, SpawnRule, populate};
pub use world::{LefStates, LegState, World, WorldError};
use rand_pcg::Pcg64;
pub type Time = f64;
#[derive(Copy, Clone, Debug)]
pub enum Schedule {
At(Time),
Never,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(try_from = "serial::ModelData", into = "serial::ModelData")
)]
pub struct Model {
pub(crate) world: World,
pub(crate) rates: Rates,
pub(crate) spawn: SpawnRule,
}
impl Model {
pub fn new(world: World, rates: Rates, spawn: SpawnRule) -> Result<Self, ModelError> {
check(&world, &rates, &spawn)?;
Ok(Model {
world,
rates,
spawn,
})
}
pub fn world(&self) -> &World {
&self.world
}
pub fn rates(&self) -> Rates {
self.rates
}
pub fn spawn(&self) -> SpawnRule {
self.spawn
}
pub fn into_parts(self) -> (World, Rates, SpawnRule) {
(self.world, self.rates, self.spawn)
}
}
pub(crate) fn check(world: &World, rates: &Rates, spawn: &dyn Spawn) -> Result<(), ModelError> {
for (name, value) in [
("step", rates.step),
("respawn", rates.respawn),
("respawn_blocked", rates.respawn_blocked),
("respawn_captured", rates.respawn_captured),
] {
if !(value.is_finite() && value > 0.0) {
return Err(ModelError::BadRate { name, value });
}
}
spawn
.check(world.lattice(), world.n_lefs())
.map_err(ModelError::Spawn)
}
#[derive(Clone, Debug, PartialEq)]
pub enum ModelError {
BadRate { name: &'static str, value: f64 },
Spawn(SpawnError),
}
impl std::fmt::Display for ModelError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ModelError::BadRate { name, value } => {
write!(
f,
"the {name} rate must be positive and finite, got {value}"
)
}
ModelError::Spawn(e) => e.fmt(f),
}
}
}
impl std::error::Error for ModelError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ModelError::Spawn(e) => Some(e),
ModelError::BadRate { .. } => None,
}
}
}
pub type SimRng = Pcg64;
#[cfg(test)]
mod tests {
use super::*;
fn world(n_sites: usize, n_lefs: usize) -> World {
populate(
Lattice::plain(n_sites).unwrap(),
n_lefs,
&SpawnRule::UniformNonCtcf,
1,
)
.unwrap()
}
#[test]
fn model_checks_rates_and_spawn_rule() {
let rule = SpawnRule::UniformNonCtcf;
assert!(Model::new(world(10, 3), Rates::default(), rule).is_ok());
let bad = Rates {
respawn: f64::NAN,
..Rates::default()
};
let err = Model::new(world(10, 3), bad, rule).unwrap_err();
assert!(
matches!(
err,
ModelError::BadRate {
name: "respawn",
..
}
),
"{err}"
);
let bad = Rates {
step: 0.0,
..Rates::default()
};
assert!(Model::new(world(10, 3), bad, rule).is_err());
let mut w = World::new(Lattice::plain(4).unwrap());
for p in [0, 1, 2] {
w.add_lef((p, Mobility::Moving), (p, Mobility::Moving))
.unwrap();
}
let err = Model::new(w, Rates::default(), rule).unwrap_err();
assert!(
matches!(err, ModelError::Spawn(SpawnError::NotEnoughSites { .. })),
"{err}"
);
}
#[test]
fn model_is_send_sync_and_clone() {
fn assert_traits<T: Send + Sync + Clone>() {}
assert_traits::<Model>();
}
}