use slotmap::SlotMap;
use smallvec::SmallVec;
use super::SimRng;
use super::action::land_lef;
use super::entity::{Lef, LefKey, Leg, LegKey, Mobility};
use super::lattice::{Direction, Lattice, Position, SiteKind};
pub type LegState = (Position, Mobility);
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(
into = "Vec<super::serial::LefRow>",
from = "Vec<super::serial::LefRow>"
)
)]
pub struct LefStates {
pub left_pos: Vec<Position>,
pub right_pos: Vec<Position>,
pub left_mobility: Vec<Mobility>,
pub right_mobility: Vec<Mobility>,
}
impl LefStates {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(n: usize) -> Self {
LefStates {
left_pos: Vec::with_capacity(n),
right_pos: Vec::with_capacity(n),
left_mobility: Vec::with_capacity(n),
right_mobility: Vec::with_capacity(n),
}
}
pub fn reserve(&mut self, n: usize) {
self.left_pos.reserve(n);
self.right_pos.reserve(n);
self.left_mobility.reserve(n);
self.right_mobility.reserve(n);
}
pub fn extend_from(&mut self, other: &LefStates) {
self.left_pos.extend_from_slice(&other.left_pos);
self.right_pos.extend_from_slice(&other.right_pos);
self.left_mobility.extend_from_slice(&other.left_mobility);
self.right_mobility.extend_from_slice(&other.right_mobility);
}
pub fn clear(&mut self) {
self.left_pos.clear();
self.right_pos.clear();
self.left_mobility.clear();
self.right_mobility.clear();
}
pub fn len(&self) -> usize {
self.left_pos.len()
}
pub fn is_empty(&self) -> bool {
self.left_pos.is_empty()
}
pub fn push(&mut self, left: LegState, right: LegState) {
self.left_pos.push(left.0);
self.left_mobility.push(left.1);
self.right_pos.push(right.0);
self.right_mobility.push(right.1);
}
pub fn get(&self, i: usize) -> Option<(LegState, LegState)> {
Some((
(*self.left_pos.get(i)?, *self.left_mobility.get(i)?),
(*self.right_pos.get(i)?, *self.right_mobility.get(i)?),
))
}
pub(crate) fn columns_agree(&self) -> bool {
let n = self.left_pos.len();
self.right_pos.len() == n && self.left_mobility.len() == n && self.right_mobility.len() == n
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum WorldError {
ColumnLengths,
PositionOutOfRange {
lef: usize,
pos: Position,
n_sites: usize,
},
LegsCrossed {
lef: usize,
left: Position,
right: Position,
},
SiteOccupied { lef: usize, pos: Position },
CapturedOffCtcf { lef: usize, pos: Position },
CapturedWrongSide {
lef: usize,
pos: Position,
facing: Direction,
},
}
impl std::fmt::Display for WorldError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WorldError::ColumnLengths => write!(f, "LEF state columns have different lengths"),
WorldError::PositionOutOfRange { lef, pos, n_sites } => {
write!(
f,
"LEF {lef}: position {pos} is outside a lattice of {n_sites} sites"
)
}
WorldError::LegsCrossed { lef, left, right } => {
write!(
f,
"LEF {lef}: left leg at {left} is to the right of the right leg at {right}"
)
}
WorldError::SiteOccupied { lef, pos } => {
write!(
f,
"LEF {lef}: site {pos} is already occupied by another LEF"
)
}
WorldError::CapturedOffCtcf { lef, pos } => {
write!(
f,
"LEF {lef}: leg at {pos} is Captured but the site is not a CTCF"
)
}
WorldError::CapturedWrongSide { lef, pos, facing } => write!(
f,
"LEF {lef}: a {}-facing leg can't be captured by the CTCF at {pos}, \
which captures from the {}",
Direction::NAMES[facing.code() as usize].to_lowercase(),
Direction::NAMES[facing.code() as usize].to_lowercase(),
),
}
}
}
impl std::error::Error for WorldError {}
pub type OccupancyBucket = SmallVec<[LegKey; 2]>;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(
try_from = "super::serial::WorldData",
into = "super::serial::WorldData"
)
)]
pub struct World {
pub(crate) lattice: Lattice,
pub(crate) legs: SlotMap<LegKey, Leg>,
pub(crate) lefs: SlotMap<LefKey, Lef>,
pub(crate) lef_order: Vec<LefKey>,
pub(crate) occupancy: Vec<OccupancyBucket>,
}
impl PartialEq for World {
fn eq(&self, other: &Self) -> bool {
self.lattice == other.lattice && self.lef_states() == other.lef_states()
}
}
impl World {
pub fn new(lattice: Lattice) -> Self {
let n = lattice.len();
World {
lattice,
legs: SlotMap::with_key(),
lefs: SlotMap::with_key(),
lef_order: Vec::new(),
occupancy: vec![OccupancyBucket::new(); n],
}
}
pub fn from_lef_states(lattice: Lattice, lefs: &LefStates) -> Result<Self, WorldError> {
if !lefs.columns_agree() {
return Err(WorldError::ColumnLengths);
}
let mut world = World::new(lattice);
for i in 0..lefs.len() {
let (left, right) = lefs.get(i).expect("columns agree");
world.add_lef(left, right)?;
}
Ok(world)
}
pub fn add_lef(&mut self, left: LegState, right: LegState) -> Result<LefKey, WorldError> {
self.check_lef(left, right)?;
Ok(self.insert_lef(left, right).0)
}
pub fn n_lefs(&self) -> usize {
self.lef_order.len()
}
pub fn lef_state(&self, lef_id: usize) -> Option<(LegState, LegState)> {
let [l, r] = self.lefs[*self.lef_order.get(lef_id)?].legs;
let (l, r) = (&self.legs[l], &self.legs[r]);
Some(((l.pos, l.mobility), (r.pos, r.mobility)))
}
pub fn lef_states(&self) -> LefStates {
let mut states = LefStates::with_capacity(self.n_lefs());
self.push_lef_states(&mut states);
states
}
pub fn push_lef_states(&self, out: &mut LefStates) {
for &lef in &self.lef_order {
let [l, r] = self.lefs[lef].legs;
let (l, r) = (&self.legs[l], &self.legs[r]);
out.push((l.pos, l.mobility), (r.pos, r.mobility));
}
}
fn check_lef(&self, left: LegState, right: LegState) -> Result<(), WorldError> {
let lef = self.n_lefs();
let n_sites = self.lattice.len();
for (pos, _) in [left, right] {
if pos as usize >= n_sites {
return Err(WorldError::PositionOutOfRange { lef, pos, n_sites });
}
}
if left.0 > right.0 {
return Err(WorldError::LegsCrossed {
lef,
left: left.0,
right: right.0,
});
}
for pos in [left.0, right.0] {
if self.occupied(pos) {
return Err(WorldError::SiteOccupied { lef, pos });
}
}
for ((pos, mobility), facing) in [(left, Direction::Left), (right, Direction::Right)] {
match mobility {
Mobility::Moving => {}
Mobility::Captured => match self.lattice.sites[pos as usize].kind {
SiteKind::Plain => return Err(WorldError::CapturedOffCtcf { lef, pos }),
SiteKind::Ctcf { captures_from, .. } if captures_from != facing.opposite() => {
return Err(WorldError::CapturedWrongSide { lef, pos, facing });
}
SiteKind::Ctcf { .. } => {}
},
}
}
Ok(())
}
pub(crate) fn insert_lef(&mut self, left: LegState, right: LegState) -> (LefKey, [LegKey; 2]) {
let l1 = self.legs.insert(Leg {
pos: left.0,
lef: LefKey::default(),
partner: LegKey::default(),
facing: Direction::Left,
mobility: left.1,
});
let l2 = self.legs.insert(Leg {
pos: right.0,
lef: LefKey::default(),
partner: l1,
facing: Direction::Right,
mobility: right.1,
});
let lef_k = self.lefs.insert(Lef { legs: [l1, l2] });
let leg = &mut self.legs[l1];
leg.lef = lef_k;
leg.partner = l2;
self.legs[l2].lef = lef_k;
self.insert_leg(l1, left.0);
self.insert_leg(l2, right.0);
self.lef_order.push(lef_k);
(lef_k, [l1, l2])
}
pub(crate) fn insert_leg(&mut self, k: LegKey, p: Position) {
self.occupancy[p as usize].push(k);
}
pub(crate) fn remove_leg(&mut self, k: LegKey, p: Position) {
self.occupancy[p as usize].retain(|x| *x != k);
}
pub fn lattice(&self) -> &Lattice {
&self.lattice
}
pub fn legs(&self) -> &SlotMap<LegKey, Leg> {
&self.legs
}
pub fn lefs(&self) -> &SlotMap<LefKey, Lef> {
&self.lefs
}
pub fn occupied(&self, p: Position) -> bool {
!self.occupancy[p as usize].is_empty()
}
pub fn legs_at(&self, p: Position) -> &[LegKey] {
&self.occupancy[p as usize]
}
pub(crate) fn spawn_lef(&mut self, pos: Position, rng: &mut SimRng) -> (LefKey, [LegKey; 2]) {
let [left, right] = land_lef(self.lattice.sites[pos as usize].kind, rng);
self.insert_lef((pos, left), (pos, right))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::Ctcf;
use Mobility::{Captured, Moving};
fn lattice() -> Lattice {
Lattice::with_ctcfs(
20,
&[
Ctcf {
site: 5,
captures_from: Direction::Left,
strength: 1.0,
},
Ctcf {
site: 15,
captures_from: Direction::Right,
strength: 1.0,
},
],
)
.unwrap()
}
#[test]
fn add_lef_builds_a_world_that_reads_back() {
let mut world = World::new(lattice());
world.add_lef((2, Moving), (5, Captured)).unwrap();
world.add_lef((15, Captured), (18, Moving)).unwrap();
world.add_lef((9, Moving), (9, Moving)).unwrap();
assert_eq!(world.n_lefs(), 3);
assert_eq!(world.lef_state(1), Some(((15, Captured), (18, Moving))));
assert_eq!(world.lef_state(3), None);
assert!(world.occupied(9) && world.occupied(5) && !world.occupied(10));
let states = world.lef_states();
assert_eq!(states.left_pos, [2, 15, 9]);
assert_eq!(states.right_mobility, [Captured, Moving, Moving]);
let rebuilt = World::from_lef_states(lattice(), &states).unwrap();
assert_eq!(rebuilt, world);
}
#[test]
fn equality_compares_state_not_identity() {
let mut a = World::new(lattice());
a.add_lef((1, Moving), (3, Moving)).unwrap();
let mut b = a.clone();
assert_eq!(a, b);
b.add_lef((10, Moving), (11, Moving)).unwrap();
assert_ne!(a, b);
assert_eq!(a.n_lefs(), 1); assert_ne!(
World::new(lattice()),
World::new(Lattice::plain(20).unwrap())
);
}
#[test]
fn rejects_invalid_lefs() {
let mut world = World::new(lattice());
world.add_lef((8, Moving), (9, Moving)).unwrap();
let cases = [
(
(20, Moving),
(20, Moving),
WorldError::PositionOutOfRange {
lef: 1,
pos: 20,
n_sites: 20,
},
),
(
(4, Moving),
(3, Moving),
WorldError::LegsCrossed {
lef: 1,
left: 4,
right: 3,
},
),
(
(9, Moving),
(12, Moving),
WorldError::SiteOccupied { lef: 1, pos: 9 },
),
(
(1, Captured),
(2, Moving),
WorldError::CapturedOffCtcf { lef: 1, pos: 1 },
),
(
(5, Captured),
(6, Moving),
WorldError::CapturedWrongSide {
lef: 1,
pos: 5,
facing: Direction::Left,
},
),
(
(12, Moving),
(15, Captured),
WorldError::CapturedWrongSide {
lef: 1,
pos: 15,
facing: Direction::Right,
},
),
];
for (left, right, expected) in cases {
assert_eq!(
world.add_lef(left, right),
Err(expected.clone()),
"{expected}"
);
}
assert_eq!(world.n_lefs(), 1, "rejected LEFs leave the world unchanged");
}
#[test]
fn from_lef_states_checks_column_lengths_and_each_row() {
let mut states = LefStates::new();
states.push((1, Moving), (2, Moving));
states.right_pos.push(3);
assert_eq!(
World::from_lef_states(lattice(), &states),
Err(WorldError::ColumnLengths)
);
let mut clash = LefStates::new();
clash.push((1, Moving), (4, Moving));
clash.push((4, Moving), (6, Moving));
assert_eq!(
World::from_lef_states(lattice(), &clash),
Err(WorldError::SiteOccupied { lef: 1, pos: 4 })
);
}
}