use crate::law::SoundnessState;
pub use crate::models::{Arc, ArcDirection, PetriNet, PetriNetRefusal, Place, Transition};
use std::fmt;
use std::marker::PhantomData;
#[derive(Debug, Clone, Default)]
pub struct Marking {
tokens: Vec<(String, usize)>,
}
impl Marking {
pub fn new(tokens: impl IntoIterator<Item = (String, usize)>) -> Self {
Marking {
tokens: tokens.into_iter().collect(),
}
}
pub fn empty() -> Self {
Marking::default()
}
pub fn is_empty(&self) -> bool {
self.tokens.is_empty()
}
pub fn tokens(&self) -> &[(String, usize)] {
&self.tokens
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PetriRefusal {
MissingInitialMarking,
MissingFinalMarking,
DeadTransition,
UnsafeNet,
UnboundedNet,
ObjectTypeNotPreserved,
InvalidVariableArc,
SoundnessNotWitnessed,
InvalidCancellationRegion,
InvalidInstanceBounds,
}
impl fmt::Display for PetriRefusal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let law = match self {
PetriRefusal::MissingInitialMarking => "MissingInitialMarking",
PetriRefusal::MissingFinalMarking => "MissingFinalMarking",
PetriRefusal::DeadTransition => "DeadTransition",
PetriRefusal::UnsafeNet => "UnsafeNet",
PetriRefusal::UnboundedNet => "UnboundedNet",
PetriRefusal::ObjectTypeNotPreserved => "ObjectTypeNotPreserved",
PetriRefusal::InvalidVariableArc => "InvalidVariableArc",
PetriRefusal::SoundnessNotWitnessed => "SoundnessNotWitnessed",
PetriRefusal::InvalidCancellationRegion => "InvalidCancellationRegion",
PetriRefusal::InvalidInstanceBounds => "InvalidInstanceBounds",
};
write!(f, "Petri-net refused by law: {law}")
}
}
impl std::error::Error for PetriRefusal {}
pub struct SoundnessClaimed;
pub struct SoundnessWitnessed;
pub struct SoundnessUnknown;
pub type Unchecked = SoundnessUnknown;
pub struct WfNet<S = SoundnessUnknown> {
net: PetriNet,
final_marking: Marking,
_s: PhantomData<S>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WfNetSoundnessProofOf<Net> {
_seal: (),
_marker: std::marker::PhantomData<Net>,
}
impl<Net> WfNetSoundnessProofOf<Net> {
#[allow(dead_code)]
pub(crate) fn new() -> Self {
WfNetSoundnessProofOf {
_seal: (),
_marker: std::marker::PhantomData,
}
}
}
impl WfNet<SoundnessUnknown> {
pub fn new(net: PetriNet, final_marking: Marking) -> Self {
WfNet {
net,
final_marking,
_s: PhantomData,
}
}
#[must_use]
pub fn claim_sound(self) -> WfNet<SoundnessClaimed> {
WfNet {
net: self.net,
final_marking: self.final_marking,
_s: PhantomData,
}
}
}
impl<S> WfNet<S> {
pub fn validate(&self) -> Result<(), PetriRefusal> {
if self.final_marking.is_empty() {
return Err(PetriRefusal::MissingFinalMarking);
}
Ok(())
}
#[must_use]
pub fn final_marking(&self) -> Option<&Marking> {
if self.final_marking.is_empty() {
None
} else {
Some(&self.final_marking)
}
}
pub fn net(&self) -> &PetriNet {
&self.net
}
}
#[derive(Debug, Clone)]
pub struct ObjectCentricPetriNet {
net: PetriNet,
object_types: Vec<String>,
}
impl ObjectCentricPetriNet {
pub fn new(net: PetriNet, object_types: impl IntoIterator<Item = String>) -> Self {
ObjectCentricPetriNet {
net,
object_types: object_types.into_iter().collect(),
}
}
pub fn net(&self) -> &PetriNet {
&self.net
}
pub fn object_types(&self) -> &[String] {
&self.object_types
}
pub fn validate(&self) -> Result<(), PetriRefusal> {
let type_set: std::collections::HashSet<&str> =
self.object_types.iter().map(|s| s.as_str()).collect();
for arc in &self.net.arcs {
if let Some((ref ot, _)) = arc.object_type {
if !type_set.contains(ot.as_str()) {
return Err(PetriRefusal::ObjectTypeNotPreserved);
}
}
}
Ok(())
}
}
impl Place {
pub fn id(&self) -> &str {
&self.id
}
}
impl Transition {
pub fn id(&self) -> &str {
&self.id
}
pub fn label(&self) -> &str {
&self.label
}
pub fn is_silent(&self) -> bool {
self.is_invisible.unwrap_or(false)
}
pub fn silent(id: &str) -> Self {
Transition {
id: id.to_owned(),
label: String::new(),
is_invisible: Some(true),
}
}
}
impl PetriNet {
pub fn places(&self) -> &[Place] {
&self.places
}
pub fn transitions(&self) -> &[Transition] {
&self.transitions
}
pub fn arcs(&self) -> &[Arc] {
&self.arcs
}
pub fn initial_marking(&self) -> RuntimeMarking<'_> {
RuntimeMarking { net: self }
}
}
pub struct RuntimeMarking<'a> {
net: &'a PetriNet,
}
impl<'a> RuntimeMarking<'a> {
pub fn tokens_on(&self, place_id: &str) -> usize {
use crate::dense_kernel::fnv1a_64;
let hash = fnv1a_64(place_id.as_bytes());
*self.net.initial_marking.get(hash).unwrap_or(&0)
}
}
impl Marking {
pub fn tokens_on(&self, place_id: &str) -> usize {
self.tokens
.iter()
.find(|(id, _)| id == place_id)
.map(|(_, n)| *n)
.unwrap_or(0)
}
}
use crate::law::ArcDirectionConst;
pub struct BipartiteArcConst<const DIR: ArcDirectionConst, W> {
place_id: String,
transition_id: String,
weight: W,
}
impl<const DIR: ArcDirectionConst, W: Copy> BipartiteArcConst<DIR, W> {
pub fn new(place_id: &str, transition_id: &str, weight: W) -> Self {
BipartiteArcConst {
place_id: place_id.to_owned(),
transition_id: transition_id.to_owned(),
weight,
}
}
pub fn place_id(&self) -> &str {
&self.place_id
}
pub fn transition_id(&self) -> &str {
&self.transition_id
}
pub fn weight(&self) -> W {
self.weight
}
pub fn direction(&self) -> ArcDirectionConst {
DIR
}
}
pub struct InitialFinalMarkingPair {
pub initial: Marking,
pub final_marking: Marking,
}
impl InitialFinalMarkingPair {
pub fn new(initial: Marking, final_marking: Marking) -> Self {
InitialFinalMarkingPair {
initial,
final_marking,
}
}
pub fn validate(&self) -> Result<(), PetriRefusal> {
for (p_init, t_init) in self.initial.tokens() {
if *t_init > 0 && self.final_marking.tokens_on(p_init) > 0 {
return Err(PetriRefusal::UnsafeNet);
}
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct PlaceNodeMarker;
pub trait IsPlaceNode: Default + Clone + Copy {}
impl IsPlaceNode for PlaceNodeMarker {}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct TransitionNodeMarker;
pub trait IsTransitionNode: Default + Clone + Copy {}
impl IsTransitionNode for TransitionNodeMarker {}
pub trait IsValidArc {}
pub struct PlaceToTransitionArc<P, T, W> {
w: W,
_p: PhantomData<(P, T)>,
}
impl<P, T, W: Copy> PlaceToTransitionArc<P, T, W> {
pub fn new(weight: W) -> Self {
PlaceToTransitionArc {
w: weight,
_p: PhantomData,
}
}
pub fn weight(&self) -> W {
self.w
}
}
impl<P, T, W> IsValidArc for PlaceToTransitionArc<P, T, W> {}
pub struct TransitionToPlaceArc<T, P, W> {
w: W,
_p: PhantomData<(T, P)>,
}
impl<T, P, W: Copy> TransitionToPlaceArc<T, P, W> {
pub fn new(weight: W) -> Self {
TransitionToPlaceArc {
w: weight,
_p: PhantomData,
}
}
pub fn weight(&self) -> W {
self.w
}
}
impl<T, P, W> IsValidArc for TransitionToPlaceArc<T, P, W> {}
pub struct SeparableWfNetMarker;
#[allow(clippy::manual_non_exhaustive)]
pub struct SeparableWfNet<const S: SoundnessState> {
pub net: WfNetConst<S>,
_seal: (),
}
impl<const S: SoundnessState> SeparableWfNet<S> {
pub fn declare_separable(net: WfNetConst<S>) -> Self {
SeparableWfNet { net, _seal: () }
}
}
mod wfnet_seal {
pub(super) struct WfNetSeal;
}
pub struct SoundnessProof(wfnet_seal::WfNetSeal);
#[allow(dead_code)] impl SoundnessProof {
pub(crate) fn new() -> Self {
SoundnessProof(wfnet_seal::WfNetSeal)
}
}
pub struct WfNetConst<const SOUNDNESS: SoundnessState> {
_seal: (),
}
impl WfNetConst<{ SoundnessState::Unknown }> {
pub fn new() -> Self {
WfNetConst { _seal: () }
}
#[must_use]
pub fn claim_sound(self) -> WfNetConst<{ SoundnessState::Claimed }> {
WfNetConst { _seal: () }
}
}
impl WfNetConst<{ SoundnessState::Claimed }> {
#[must_use]
pub fn witness_soundness(
self,
_proof: SoundnessProof,
) -> WfNetConst<{ SoundnessState::Witnessed }> {
WfNetConst { _seal: () }
}
}
impl<const S: SoundnessState> WfNetConst<S> {
pub fn soundness_state(&self) -> SoundnessState {
S
}
}
impl Default for WfNetConst<{ SoundnessState::Unknown }> {
fn default() -> Self {
WfNetConst { _seal: () }
}
}
pub trait WfNetQuery {
fn soundness_state(&self) -> SoundnessState;
}
impl<const S: SoundnessState> WfNetQuery for WfNetConst<S> {
fn soundness_state(&self) -> SoundnessState {
S
}
}
#[repr(transparent)]
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct CancellationRegion {
pub members: Vec<String>,
}
impl CancellationRegion {
pub fn new<I, S>(members: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
CancellationRegion {
members: members.into_iter().map(Into::into).collect(),
}
}
pub fn members(&self) -> &[String] {
&self.members
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum InstanceCreationKind {
Static,
Dynamic,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MultipleInstanceSpec {
pub min: u32,
pub max: Option<u32>,
pub threshold: Option<u32>,
pub creation: InstanceCreationKind,
}
impl MultipleInstanceSpec {
pub fn new(
min: u32,
max: Option<u32>,
threshold: Option<u32>,
creation: InstanceCreationKind,
) -> Self {
MultipleInstanceSpec {
min,
max,
threshold,
creation,
}
}
#[must_use = "check the shape-check result"]
pub fn validate(&self) -> Result<(), PetriRefusal> {
if self.min == 0 {
return Err(PetriRefusal::InvalidInstanceBounds);
}
if let Some(max) = self.max {
if self.min > max {
return Err(PetriRefusal::InvalidInstanceBounds);
}
}
Ok(())
}
}
pub struct MultipleInstanceSpecConst<const MIN: u32, const MAX: u32>
where
crate::law::Require<{ MIN >= 1 }>: crate::law::IsTrue,
crate::law::Require<{ MIN <= MAX }>: crate::law::IsTrue,
{
_private: (),
}
impl<const MIN: u32, const MAX: u32> MultipleInstanceSpecConst<MIN, MAX>
where
crate::law::Require<{ MIN >= 1 }>: crate::law::IsTrue,
crate::law::Require<{ MIN <= MAX }>: crate::law::IsTrue,
{
pub const fn new() -> Self {
MultipleInstanceSpecConst { _private: () }
}
pub const fn min(&self) -> u32 {
MIN
}
pub const fn max(&self) -> u32 {
MAX
}
}
impl<const MIN: u32, const MAX: u32> Default for MultipleInstanceSpecConst<MIN, MAX>
where
crate::law::Require<{ MIN >= 1 }>: crate::law::IsTrue,
crate::law::Require<{ MIN <= MAX }>: crate::law::IsTrue,
{
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct MissingFinalMarkingError;
impl core::fmt::Display for MissingFinalMarkingError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "WF-net refused by law: MissingFinalMarking")
}
}
impl From<MissingFinalMarkingError> for PetriRefusal {
fn from(_: MissingFinalMarkingError) -> Self {
PetriRefusal::MissingFinalMarking
}
}
pub struct BoundedWfNet<const PLACES: usize, const TRANSITIONS: usize>
where
[(); PLACES + TRANSITIONS]:,
{
pub places: [String; PLACES],
pub transitions: [String; TRANSITIONS],
}
impl<const PLACES: usize, const TRANSITIONS: usize> BoundedWfNet<PLACES, TRANSITIONS>
where
[(); PLACES + TRANSITIONS]:,
crate::law::Require<{ PLACES + TRANSITIONS <= 4096 }>: crate::law::IsTrue,
{
pub fn new(places: [String; PLACES], transitions: [String; TRANSITIONS]) -> Self {
Self {
places,
transitions,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::law::SoundnessState;
#[test]
fn soundness_proof_mints_and_advances_to_witnessed() {
let proof = SoundnessProof::new();
let witnessed = WfNetConst::<{ SoundnessState::Unknown }>::new()
.claim_sound()
.witness_soundness(proof);
assert_eq!(witnessed.soundness_state(), SoundnessState::Witnessed);
}
}