use std::collections::HashSet;
use std::sync::Arc;
use crate::action::BoxedAction;
use crate::place::PlaceRef;
use crate::transition::{Transition, rebuild_with_action};
#[derive(Debug, Clone)]
pub struct PetriNet {
name: Arc<str>,
places: HashSet<PlaceRef>,
transitions: Vec<Transition>,
}
impl PetriNet {
pub fn name(&self) -> &str {
&self.name
}
pub fn places(&self) -> &HashSet<PlaceRef> {
&self.places
}
pub fn transitions(&self) -> &[Transition] {
&self.transitions
}
pub fn bind_actions(
&self,
bindings: &std::collections::HashMap<String, BoxedAction>,
) -> PetriNet {
self.bind_actions_with_resolver(|name| bindings.get(name).cloned())
}
pub fn bind_actions_with_resolver(
&self,
resolver: impl Fn(&str) -> Option<BoxedAction>,
) -> PetriNet {
let transitions: Vec<Transition> = self
.transitions
.iter()
.map(|t: &Transition| {
if let Some(action) = resolver(t.name()) {
rebuild_with_action(t, action)
} else {
t.clone()
}
})
.collect();
PetriNet {
name: Arc::clone(&self.name),
places: self.places.clone(),
transitions,
}
}
pub fn builder(name: impl Into<Arc<str>>) -> PetriNetBuilder {
PetriNetBuilder::new(name)
}
}
pub struct PetriNetBuilder {
name: Arc<str>,
places: HashSet<PlaceRef>,
transitions: Vec<Transition>,
}
impl PetriNetBuilder {
pub fn new(name: impl Into<Arc<str>>) -> Self {
Self {
name: name.into(),
places: HashSet::new(),
transitions: Vec::new(),
}
}
pub fn place(mut self, place: PlaceRef) -> Self {
self.places.insert(place);
self
}
pub fn places(mut self, places: impl IntoIterator<Item = PlaceRef>) -> Self {
self.places.extend(places);
self
}
pub fn transition(mut self, transition: Transition) -> Self {
for spec in transition.input_specs() {
self.places.insert(spec.place().clone());
}
for p in transition.output_places() {
self.places.insert(p.clone());
}
for inh in transition.inhibitors() {
self.places.insert(inh.place.clone());
}
for r in transition.reads() {
self.places.insert(r.place.clone());
}
for r in transition.resets() {
self.places.insert(r.place.clone());
}
self.transitions.push(transition);
self
}
pub fn transitions(mut self, transitions: impl IntoIterator<Item = Transition>) -> Self {
for t in transitions {
self = self.transition(t);
}
self
}
pub fn build(self) -> PetriNet {
PetriNet {
name: self.name,
places: self.places,
transitions: self.transitions,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::input::one;
use crate::output::out_place;
use crate::place::Place;
#[test]
fn petri_net_builder_auto_collects_places() {
let p1 = Place::<i32>::new("p1");
let p2 = Place::<i32>::new("p2");
let t = Transition::builder("t1")
.input(one(&p1))
.output(out_place(&p2))
.build();
let net = PetriNet::builder("test").transition(t).build();
assert_eq!(net.places().len(), 2);
assert!(net.places().contains(&PlaceRef::new("p1")));
assert!(net.places().contains(&PlaceRef::new("p2")));
assert_eq!(net.transitions().len(), 1);
}
#[test]
fn petri_net_explicit_places() {
let net = PetriNet::builder("test")
.place(PlaceRef::new("orphan"))
.build();
assert_eq!(net.places().len(), 1);
assert!(net.places().contains(&PlaceRef::new("orphan")));
}
#[test]
fn petri_net_bind_actions() {
let p1 = Place::<i32>::new("p1");
let p2 = Place::<i32>::new("p2");
let t = Transition::builder("t1")
.input(one(&p1))
.output(out_place(&p2))
.build();
let net = PetriNet::builder("test").transition(t).build();
assert!(net.transitions()[0].action().is_sync());
let mut bindings = std::collections::HashMap::new();
bindings.insert("t1".to_string(), crate::action::passthrough());
let net2 = net.bind_actions(&bindings);
assert_eq!(net2.transitions().len(), 1);
assert_eq!(net2.transitions()[0].name(), "t1");
}
#[test]
fn petri_net_collects_inhibitor_read_reset_places() {
let p_in = Place::<i32>::new("in");
let p_out = Place::<i32>::new("out");
let p_inh = Place::<i32>::new("inh");
let p_read = Place::<i32>::new("read");
let p_reset = Place::<i32>::new("reset");
let t = Transition::builder("t1")
.input(one(&p_in))
.output(out_place(&p_out))
.inhibitor(crate::arc::inhibitor(&p_inh))
.read(crate::arc::read(&p_read))
.reset(crate::arc::reset(&p_reset))
.build();
let net = PetriNet::builder("test").transition(t).build();
assert_eq!(net.places().len(), 5);
}
}