Skip to content

Commit

Permalink
Add "anonymous" places and transitions for runtime configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
nxsaken committed Jan 16, 2024
1 parent 496e7b9 commit b37e904
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 121 deletions.
5 changes: 4 additions & 1 deletion examples/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ fn mark<Net: NetId, P: Place<Net>>(net: Res<PetriNet<Net>>, mut tokens: Query<&m
for mut token in &mut tokens {
net.mark::<P>(&mut token, 1);
// TODO: better place/trans names
let (_, name) = type_name::<P>()
let (_, name) = net
.place::<P>()
.1
.name()
.rsplit_once(':')
.unwrap_or(("", type_name::<P>()));
info!("{} marked!", name);
Expand Down
7 changes: 3 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
#![allow(clippy::module_name_repetitions)]

pub use crate::net::place::{Place, PlaceId, PlaceMetadata, Pn};
pub use crate::net::trans::{Arcs, Tn, Trans, TransId, TransMetadata, W};
pub use crate::net::{NetId, Nn, PetriNet};
pub use crate::net::trans::{Tn, Trans, TransId, TransMetadata};
pub use crate::net::{Arcs, NetId, Nn, PetriNet, W};
pub use crate::plugin::PetriNetPlugin;
pub use crate::token::Token;
pub use net::token::Token;

mod net;
mod plugin;
mod token;

#[cfg(test)]
mod tests {}
194 changes: 178 additions & 16 deletions src/net.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
//! Petri net.
use bevy_ecs::system::Resource;
use bevy_utils::thiserror::Error;
use bevy_utils::{all_tuples, thiserror::Error};
use educe::Educe;
use std::borrow::Cow;

use crate::net::place::{Place, Places};
use crate::net::trans::{Arcs, Flows, Inflow, Outflow, Trans, TransId, Transitions};
use crate::token::Token;
use crate::PlaceId;
use place::{Place, PlaceId, PlaceMetadata, Places};
use token::Token;
use trans::{Flows, Inflow, Outflow, Trans, TransId, TransMetadata, Transitions};

pub mod place;
pub mod token;
pub mod trans;

/// Label for a Petri net.
Expand Down Expand Up @@ -62,10 +63,24 @@ impl<Net: NetId> PetriNet<Net> {
Token::new(self.places.len())
}

/// Returns a reference to the places of this net.
#[must_use]
pub fn place<P: Place<Net>>(&self) -> (PlaceId<Net>, &PlaceMetadata<Net>) {
let id = self.places.id::<P>();
(id, self.places.metadata(id))
}

/// Returns a reference to the transitions of this net.
#[must_use]
pub fn trans<T: Trans<Net>>(&self) -> (TransId<Net>, &TransMetadata<Net>) {
let id = self.transitions.id::<T>();
(id, self.transitions.metadata(id))
}

/// Returns the number of times a place has been marked by a token.
#[must_use]
pub fn marks<P: Place<Net>>(&self, token: &Token<Net>) -> usize {
token.marks_by_id(self.places.id::<P>())
self.marks_by_id(self.places.id::<P>(), token)
}

/// Returns whether a transition is enabled.
Expand All @@ -88,7 +103,7 @@ impl<Net: NetId> PetriNet<Net> {
/// Marks a place with this token `n` times.
pub fn mark<P: Place<Net>>(&self, token: &mut Token<Net>, n: usize) {
let place = self.places.id::<P>();
token.mark_by_id(place, n);
self.mark_by_id(place, token, n);
}

/// Undoes `n` markings of a place by this token.
Expand All @@ -102,19 +117,49 @@ impl<Net: NetId> PetriNet<Net> {
n: usize,
) -> Result<(), NotEnoughMarks<Net>> {
let place = self.places.id::<P>();
self.unmark_by_id(place, token, n)
}

/// Returns the number of times a place has been marked by a token.
#[must_use]
pub fn marks_by_id(&self, place: PlaceId<Net>, token: &Token<Net>) -> usize {
token.marks_by_id(place)
}

/// Marks a place with this token `n` times.
pub fn mark_by_id(&self, place: PlaceId<Net>, token: &mut Token<Net>, n: usize) {
token.mark_by_id(place, n);
}

/// Undoes `n` markings of a place by this token.
///
/// ## Errors
///
/// Returns [`NotEnoughMarks`] if the place does not have enough tokens to be unmarked.
pub fn unmark_by_id(
&self,
place: PlaceId<Net>,
token: &mut Token<Net>,
n: usize,
) -> Result<(), NotEnoughMarks<Net>> {
token.unmark_by_id(place, n)
}

/// Tries to return an enabled transition.
fn enabled_by_id(&self, trans: TransId<Net>, token: &Token<Net>) -> bool {
#[must_use]
pub fn enabled_by_id(&self, trans: TransId<Net>, token: &Token<Net>) -> bool {
self.flows
.inflows(trans)
.iter()
.all(|&Inflow { source, weight }| token.marks_by_id(source) >= weight)
}

/// Fires transition.
fn fire_by_id(
///
/// ## Errors
///
/// Returns [`NotEnabled`] if the transition is not enabled.
pub fn fire_by_id(
&self,
trans: TransId<Net>,
token: &mut Token<Net>,
Expand All @@ -125,8 +170,11 @@ impl<Net: NetId> PetriNet<Net> {
self.flows
.inflows(trans)
.iter()
.map(|&Inflow { source, weight }| token.unmark_by_id(source, weight))
.for_each(|um| um.expect("place unmarked"));
.for_each(|&Inflow { source, weight }| {
token
.unmark_by_id(source, weight)
.unwrap_or_else(|_| unreachable!());
});
self.flows
.outflows(trans)
.iter()
Expand All @@ -143,16 +191,27 @@ impl<Net: NetId> PetriNet<Net> {
self
}

/// Adds an "anonymous" place to the net (not a Rust type).
///
/// Returns the identifier to the place.
/// The user is responsible for storing the generated [`PlaceId`].
#[must_use]
pub fn add_place_anon<N: Into<Cow<'static, str>>>(&mut self, name: N) -> PlaceId<Net> {
self.places
.register_with_meta(PlaceMetadata::new_anon(name))
}

/// Adds a [`Trans`] and its input and output [`Arcs`] to the net.
///
/// ## Panics
///
///
/// Panics if the transition has already been registered with this net,
/// or if any input or output place is not registered with the net.
#[must_use]
pub fn add_trans<T: Trans<Net>, I: Arcs<Net>, O: Arcs<Net>>(mut self) -> Self {
pub fn add_trans<T: Trans<Net>, Inflows: Arcs<Net>, Outflows: Arcs<Net>>(mut self) -> Self {
self.transitions.register::<T>();
self.flows.add_inflows(
I::erased()
Inflows::erased()
.into_iter()
.map(|(source, weight)| Inflow {
source: self.places.id_from_erased(source.type_id()),
Expand All @@ -161,7 +220,7 @@ impl<Net: NetId> PetriNet<Net> {
.collect(),
);
self.flows.add_outflows(
O::erased()
Outflows::erased()
.into_iter()
.map(|(target, weight)| Outflow {
target: self.places.id_from_erased(target.type_id()),
Expand All @@ -172,22 +231,89 @@ impl<Net: NetId> PetriNet<Net> {
self
}

/// Adds an "anonymous" transition to the net (not a Rust type).
///
/// Returns the identifier to the transition.
/// The user is responsible for storing the generated [`TransId`].
#[must_use]
pub fn add_trans_anon<N: Into<Cow<'static, str>>>(
&mut self,
name: N,
inflows: &[(PlaceId<Net>, usize)],
outflows: &[(PlaceId<Net>, usize)],
) -> TransId<Net> {
let trans = self
.transitions
.register_with_meta(TransMetadata::new_anon(name));
self.flows.add_inflows(
inflows
.iter()
.map(|&(source, weight)| Inflow { source, weight })
.collect(),
);
self.flows.add_outflows(
outflows
.iter()
.map(|&(target, weight)| Outflow { target, weight })
.collect(),
);
trans
}

/// Allows composing Petri net configuration.
#[must_use]
pub fn compose(self, f: impl FnOnce(Self) -> Self) -> Self {
f(self)
}
}

/// Arc weight.
pub enum W<const N: usize> {}

/// Weighted place-transition arcs.
pub trait Arcs<Net: NetId> {
/// Returns a vector of erased arcs.
fn erased() -> Vec<(PlaceMetadata<Net>, usize)>;
}

// single place case
impl<Net, P0, const W0: usize> Arcs<Net> for (P0, W<W0>)
where
Net: NetId,
P0: Place<Net>,
{
fn erased() -> Vec<(PlaceMetadata<Net>, usize)> {
vec![(PlaceMetadata::new::<P0>(), W0)]
}
}

macro_rules! impl_arcs {
($(($place:ident, $weight:ident)),*) => {
#[allow(unused_parens)]
impl<Net, $($place, const $weight: usize),*> Arcs<Net> for ($(($place, W<$weight>),)*)
where
Net: NetId,
$($place: Place<Net>),*
{
fn erased() -> Vec<(PlaceMetadata<Net>, usize)> {
vec![$((PlaceMetadata::new::<$place>(), $weight)),*]
}
}
};
}

all_tuples!(impl_arcs, 0, 15, P, W);

#[cfg(test)]
mod tests {
use crate::{NetId, PetriNet, Place, Trans, W};
use crate::{NetId, PetriNet, Place, Pn, Tn, Trans, W};

enum Minimal {}
enum ProdCons {}
enum Star {}
enum Ring {}
enum Choice {}
enum Anon<const MIXED: bool> {}

enum P0 {}
enum P1 {}
Expand All @@ -203,6 +329,7 @@ mod tests {
impl NetId for Star {}
impl NetId for Ring {}
impl NetId for Choice {}
impl<const MIXED: bool> NetId for Anon<MIXED> {}

impl<Net: NetId> Place<Net> for P0 {}
impl<Net: NetId> Place<Net> for P1 {}
Expand Down Expand Up @@ -330,4 +457,39 @@ mod tests {
assert!(net.fire::<T0>(&mut token).is_ok());
assert!(!net.enabled::<T1>(&token));
}

#[test]
fn test_pure_anon_net() {
let mut net = PetriNet::<Anon<false>>::new();
let p = ["p0", "p1", "p2"].map(|pn| net.add_place_anon(pn));
let t0 = net.add_trans_anon("t0", &[(p[0], 1), (p[1], 1)], &[(p[2], 1)]);
let mut token = net.spawn_token();
net.mark_by_id(p[0], &mut token, 1);
net.mark_by_id(p[1], &mut token, 1);
assert!(net.fire_by_id(t0, &mut token).is_ok());
assert_eq!(net.marks_by_id(p[2], &token), 1);
}

#[test]
fn test_mixed_net() {
let mut net = PetriNet::<Anon<true>>::new();
net = net
.add_place::<Pn<0>>()
.add_place::<Pn<1>>()
.add_place::<Pn<3>>();
let p2 = net.add_place_anon("p2");
let (p1, _) = net.place::<Pn<1>>();
let (p3, _) = net.place::<Pn<3>>();
net = net.add_trans::<Tn<0>, ((Pn<0>, W<1>), (Pn<1>, W<1>)), (Pn<3>, W<1>)>();
let t1 = net.add_trans_anon("t1", &[(p1, 1), (p2, 1)], &[(p3, 1)]);
let mut token_a = net.spawn_token();
net.mark::<Pn<0>>(&mut token_a, 1);
net.mark::<Pn<1>>(&mut token_a, 1);
net.mark_by_id(p2, &mut token_a, 1);
let mut token_b = token_a.clone();
assert!(net.fire::<Tn<0>>(&mut token_a).is_ok());
assert_eq!(net.marks::<Pn<3>>(&token_a), 1);
assert!(net.fire_by_id(t1, &mut token_b).is_ok());
assert_eq!(net.marks::<Pn<3>>(&token_b), 1);
}
}
Loading

0 comments on commit b37e904

Please sign in to comment.