use crate::law::{IsTrue, Require};
use core::marker::PhantomData;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Atom;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct PartialOrder;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Choice;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Loop;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Silent;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Irreducible;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct AcyclicPartialOrder;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ProcessTreeProjectable;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ExceedsProcessTree;
mod acyclic_witness_seal {
pub trait Sealed {}
impl Sealed for super::AcyclicPartialOrder {}
}
pub trait AcyclicWitness: acyclic_witness_seal::Sealed {}
impl AcyclicWitness for AcyclicPartialOrder {}
pub fn assert_acyclic<W: AcyclicWitness>(_marker: W) -> bool {
true
}
mod tree_projectable_seal {
pub trait Sealed {}
impl Sealed for super::ProcessTreeProjectable {}
}
pub trait TreeProjectable: tree_projectable_seal::Sealed {}
impl TreeProjectable for ProcessTreeProjectable {}
pub fn assert_tree_projectable<P: TreeProjectable>(_marker: P) -> bool {
true
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PowlNodeId(pub usize);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PowlNodeKind {
Atom(String),
Silent,
Choice(Vec<PowlNodeId>),
Loop {
body: PowlNodeId,
redo: Option<PowlNodeId>,
},
PartialOrder(Vec<PowlNodeId>),
ChoiceGraph {
nodes: Vec<PowlNodeId>,
edges: Vec<ChoiceGraphEdge>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PowlNode<W = ()> {
pub id: PowlNodeId,
pub kind: PowlNodeKind,
pub witness: PhantomData<W>,
}
impl<W> PowlNode<W> {
pub fn new(id: PowlNodeId, kind: PowlNodeKind) -> Self {
Self {
id,
kind,
witness: PhantomData,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PowlChoiceNode {
pub branches: Vec<PowlNodeId>,
}
impl PowlChoiceNode {
pub fn new(branches: Vec<PowlNodeId>) -> Self {
Self { branches }
}
#[inline]
pub fn branch_count(&self) -> usize {
self.branches.len()
}
#[inline]
pub fn is_well_formed(&self) -> bool {
self.branches.len() >= 2
}
#[must_use = "check the shape-check result"]
pub fn validate(&self) -> Result<&[PowlNodeId], PowlRefusal> {
if self.is_well_formed() {
Ok(&self.branches)
} else {
Err(PowlRefusal::InvalidChoiceArity {
declared: self.branches.len(),
required_min: 2,
})
}
}
}
pub struct TypedPowlLoopNode<Children, const ARITY: usize>
where
Require<{ ARITY == 2 }>: IsTrue,
{
pub children: Children,
}
impl<Children, const ARITY: usize> TypedPowlLoopNode<Children, ARITY>
where
Require<{ ARITY == 2 }>: IsTrue,
{
pub fn new(children: Children) -> Self {
TypedPowlLoopNode { children }
}
}
pub const MAX_POWL_DEPTH: usize = 8;
pub struct PowlComposition<Inner, const DEPTH: usize>
where
Require<{ DEPTH <= MAX_POWL_DEPTH }>: IsTrue,
{
pub inner: Inner,
}
impl<Inner, const DEPTH: usize> PowlComposition<Inner, DEPTH>
where
Require<{ DEPTH <= MAX_POWL_DEPTH }>: IsTrue,
{
pub fn new(inner: Inner) -> Self {
PowlComposition { inner }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct OrderEdge {
pub from: PowlNodeId,
pub to: PowlNodeId,
}
impl OrderEdge {
pub fn new(from: PowlNodeId, to: PowlNodeId) -> Self {
Self { from, to }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ChoiceGraphEdge {
pub from: PowlNodeId,
pub to: PowlNodeId,
}
impl ChoiceGraphEdge {
pub fn new(from: PowlNodeId, to: PowlNodeId) -> Self {
Self { from, to }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Powl {
pub nodes: Vec<PowlNode>,
pub edges: Vec<OrderEdge>,
pub root: Option<PowlNodeId>,
}
impl Powl {
pub fn new() -> Self {
Self::default()
}
pub fn node_count(&self) -> usize {
self.nodes.len()
}
pub fn validate(&self) -> Result<(), PowlRefusal> {
for node in &self.nodes {
match &node.kind {
PowlNodeKind::Choice(branches) => {
if branches.len() < 2 {
return Err(PowlRefusal::InvalidChoiceArity {
declared: branches.len(),
required_min: 2,
});
}
}
PowlNodeKind::Loop { body, redo } => {
let node_ids: std::collections::HashSet<usize> =
self.nodes.iter().map(|n| n.id.0).collect();
if !node_ids.contains(&body.0) {
return Err(PowlRefusal::InvalidLoop);
}
if let Some(r) = redo {
if !node_ids.contains(&r.0) {
return Err(PowlRefusal::InvalidLoop);
}
}
}
PowlNodeKind::PartialOrder(children) => {
let child_set: std::collections::HashSet<PowlNodeId> =
children.iter().cloned().collect();
let mut adj: std::collections::HashMap<PowlNodeId, Vec<PowlNodeId>> =
std::collections::HashMap::new();
let mut in_degree: std::collections::HashMap<PowlNodeId, usize> =
std::collections::HashMap::new();
for &c in children {
adj.entry(c).or_default();
in_degree.entry(c).or_insert(0);
}
for edge in &self.edges {
if child_set.contains(&edge.from) && child_set.contains(&edge.to) {
adj.entry(edge.from).or_default().push(edge.to);
*in_degree.entry(edge.to).or_insert(0) += 1;
}
}
let mut queue: std::collections::VecDeque<PowlNodeId> = children
.iter()
.copied()
.filter(|c| in_degree.get(c).copied().unwrap_or(0) == 0)
.collect();
let mut visited = 0;
while let Some(u) = queue.pop_front() {
visited += 1;
if let Some(neighbors) = adj.get(&u) {
for &v in neighbors {
if let Some(deg) = in_degree.get_mut(&v) {
*deg -= 1;
if *deg == 0 {
queue.push_back(v);
}
}
}
}
}
if visited != children.len() {
return Err(PowlRefusal::CyclicPartialOrder);
}
}
PowlNodeKind::ChoiceGraph {
nodes: cg_nodes,
edges: cg_edges,
} => {
if cg_nodes.len() < 2 {
return Err(PowlRefusal::ChoiceGraphDisconnected);
}
let start = cg_nodes[0];
let end = cg_nodes[cg_nodes.len() - 1];
let node_set: std::collections::HashSet<PowlNodeId> =
cg_nodes.iter().cloned().collect();
for edge in cg_edges {
if !node_set.contains(&edge.from) || !node_set.contains(&edge.to) {
return Err(PowlRefusal::ChoiceGraphDisconnected);
}
}
let mut forward_adj: std::collections::HashMap<PowlNodeId, Vec<PowlNodeId>> =
std::collections::HashMap::new();
let mut backward_adj: std::collections::HashMap<PowlNodeId, Vec<PowlNodeId>> =
std::collections::HashMap::new();
for edge in cg_edges {
forward_adj.entry(edge.from).or_default().push(edge.to);
backward_adj.entry(edge.to).or_default().push(edge.from);
}
let mut forward_visited = std::collections::HashSet::new();
let mut queue = std::collections::VecDeque::new();
queue.push_back(start);
forward_visited.insert(start);
while let Some(u) = queue.pop_front() {
if let Some(neighbors) = forward_adj.get(&u) {
for &v in neighbors {
if forward_visited.insert(v) {
queue.push_back(v);
}
}
}
}
let mut backward_visited = std::collections::HashSet::new();
queue.clear();
queue.push_back(end);
backward_visited.insert(end);
while let Some(u) = queue.pop_front() {
if let Some(parents) = backward_adj.get(&u) {
for &v in parents {
if backward_visited.insert(v) {
queue.push_back(v);
}
}
}
}
for &node in cg_nodes {
if !forward_visited.contains(&node) || !backward_visited.contains(&node) {
return Err(PowlRefusal::ChoiceGraphDisconnected);
}
}
}
_ => {}
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum PowlRefusal {
CyclicPartialOrder,
InvalidChoice,
InvalidChoiceArity {
declared: usize,
required_min: usize,
},
InvalidLoop,
LoopMissingDoBody,
IrreducibleProjection,
LanguageMismatch,
ChoiceGraphDisconnected,
}
impl core::fmt::Display for PowlRefusal {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
PowlRefusal::CyclicPartialOrder => write!(f, "POWL refused: CyclicPartialOrder"),
PowlRefusal::InvalidChoice => write!(f, "POWL refused: InvalidChoice"),
PowlRefusal::InvalidChoiceArity { declared, required_min } => write!(
f,
"POWL refused: InvalidChoiceArity (declared={declared}, required_min={required_min})"
),
PowlRefusal::InvalidLoop => write!(f, "POWL refused: InvalidLoop"),
PowlRefusal::LoopMissingDoBody => write!(f, "POWL refused: LoopMissingDoBody"),
PowlRefusal::IrreducibleProjection => {
write!(f, "POWL refused: IrreducibleProjection")
}
PowlRefusal::LanguageMismatch => write!(f, "POWL refused: LanguageMismatch"),
PowlRefusal::ChoiceGraphDisconnected => {
write!(f, "POWL refused: ChoiceGraphDisconnected")
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StandaloneChoiceGraphNode {
Start,
End,
Activity(String),
SubModel(u32),
}
pub type ChoiceGraphNode = StandaloneChoiceGraphNode;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ChoiceGraph {
pub nodes: Vec<StandaloneChoiceGraphNode>,
pub edges: Vec<(usize, usize)>,
pub start_idx: usize,
pub end_idx: usize,
}
impl ChoiceGraph {
pub fn new(nodes: Vec<StandaloneChoiceGraphNode>, edges: Vec<(usize, usize)>) -> Self {
let end_idx = nodes.len().saturating_sub(1);
ChoiceGraph {
nodes,
edges,
start_idx: 0,
end_idx,
}
}
pub fn successors(&self, node_idx: usize) -> Vec<usize> {
self.edges
.iter()
.filter_map(|&(a, b)| if a == node_idx { Some(b) } else { None })
.collect()
}
pub fn predecessors(&self, node_idx: usize) -> Vec<usize> {
self.edges
.iter()
.filter_map(|&(a, b)| if b == node_idx { Some(a) } else { None })
.collect()
}
pub fn has_empty_path(&self) -> bool {
self.edges
.iter()
.any(|&(a, b)| a == self.start_idx && b == self.end_idx)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RefusedProjection {
reason: PowlRefusal,
}
impl RefusedProjection {
#[inline]
pub fn new(reason: PowlRefusal) -> Self {
Self { reason }
}
#[inline]
pub fn reason(&self) -> &PowlRefusal {
&self.reason
}
#[inline]
pub fn into_reason(self) -> PowlRefusal {
self.reason
}
}
impl core::fmt::Display for RefusedProjection {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(&self.reason, f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Powl8Op {
Sequence,
XorChoice,
Parallel,
Loop,
StrictPartialOrder,
ChoiceGraph,
Silent,
Activity,
}
mod wfnet2powl_seal {
pub(super) struct WfNet2PowlSeal;
}
pub struct WfNet2PowlWitness {
pub context: String,
_seal: wfnet2powl_seal::WfNet2PowlSeal,
}
impl WfNet2PowlWitness {
pub fn new_internal(context: impl Into<String>) -> Self {
WfNet2PowlWitness {
context: context.into(),
_seal: wfnet2powl_seal::WfNet2PowlSeal,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_powl_validate_empty() {
let p = Powl::new();
assert!(p.validate().is_ok());
}
#[test]
fn test_powl_validate_invalid_choice() {
let mut p = Powl::new();
p.nodes.push(PowlNode::new(
PowlNodeId(0),
PowlNodeKind::Choice(vec![PowlNodeId(1)]),
));
assert_eq!(
p.validate(),
Err(PowlRefusal::InvalidChoiceArity {
declared: 1,
required_min: 2
})
);
}
#[test]
fn test_powl_validate_cyclic_partial_order() {
let mut p = Powl::new();
p.nodes.push(PowlNode::new(
PowlNodeId(0),
PowlNodeKind::PartialOrder(vec![PowlNodeId(1), PowlNodeId(2)]),
));
p.edges.push(OrderEdge::new(PowlNodeId(1), PowlNodeId(2)));
p.edges.push(OrderEdge::new(PowlNodeId(2), PowlNodeId(1)));
assert_eq!(p.validate(), Err(PowlRefusal::CyclicPartialOrder));
}
#[test]
fn test_powl_validate_choice_graph_disconnected() {
let mut p = Powl::new();
let start = PowlNodeId(0);
let x1 = PowlNodeId(1);
let end = PowlNodeId(2);
p.nodes.push(PowlNode::new(
PowlNodeId(10),
PowlNodeKind::ChoiceGraph {
nodes: vec![start, x1, end],
edges: vec![ChoiceGraphEdge::new(start, end)], },
));
assert_eq!(p.validate(), Err(PowlRefusal::ChoiceGraphDisconnected));
}
}
#[derive(Debug, Default)]
pub struct PowlBuilder {
powl: Powl,
label_map: std::collections::HashMap<String, PowlNodeId>,
}
impl PowlBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn atom(mut self, label: &str) -> Self {
let id = PowlNodeId(self.powl.nodes.len());
self.powl
.nodes
.push(PowlNode::new(id, PowlNodeKind::Atom(label.to_string())));
self.label_map.insert(label.to_string(), id);
self
}
pub fn silent(mut self, label: &str) -> Self {
let id = PowlNodeId(self.powl.nodes.len());
self.powl
.nodes
.push(PowlNode::new(id, PowlNodeKind::Silent));
self.label_map.insert(label.to_string(), id);
self
}
pub fn partial_order(mut self, label: &str, children: &[&str], edges: &[(&str, &str)]) -> Self {
for &c in children {
if !self.label_map.contains_key(c) {
self = self.atom(c);
}
}
let child_ids: Vec<PowlNodeId> = children.iter().map(|c| self.label_map[*c]).collect();
for &(from_lbl, to_lbl) in edges {
if let (Some(&from), Some(&to)) =
(self.label_map.get(from_lbl), self.label_map.get(to_lbl))
{
self.powl.edges.push(OrderEdge::new(from, to));
}
}
let id = PowlNodeId(self.powl.nodes.len());
self.powl
.nodes
.push(PowlNode::new(id, PowlNodeKind::PartialOrder(child_ids)));
self.label_map.insert(label.to_string(), id);
self
}
pub fn choice(mut self, label: &str, branches: &[&str]) -> Self {
for &b in branches {
if !self.label_map.contains_key(b) {
self = self.atom(b);
}
}
let branch_ids: Vec<PowlNodeId> = branches.iter().map(|b| self.label_map[*b]).collect();
let id = PowlNodeId(self.powl.nodes.len());
self.powl
.nodes
.push(PowlNode::new(id, PowlNodeKind::Choice(branch_ids)));
self.label_map.insert(label.to_string(), id);
self
}
pub fn loop_node(mut self, label: &str, do_label: &str, redo_label: Option<&str>) -> Self {
if !self.label_map.contains_key(do_label) {
self = self.atom(do_label);
}
if let Some(r) = redo_label {
if !self.label_map.contains_key(r) {
self = self.atom(r);
}
}
let body = self.label_map[do_label];
let redo = redo_label.map(|r| self.label_map[r]);
let id = PowlNodeId(self.powl.nodes.len());
self.powl
.nodes
.push(PowlNode::new(id, PowlNodeKind::Loop { body, redo }));
self.label_map.insert(label.to_string(), id);
self
}
pub fn choice_graph(mut self, label: &str, nodes: &[&str], edges: &[(&str, &str)]) -> Self {
for &n in nodes {
if !self.label_map.contains_key(n) {
self = self.atom(n);
}
}
let node_ids: Vec<PowlNodeId> = nodes.iter().map(|n| self.label_map[*n]).collect();
let edge_objs: Vec<ChoiceGraphEdge> = edges
.iter()
.filter_map(|&(f, t)| {
Some(ChoiceGraphEdge::new(
*self.label_map.get(f)?,
*self.label_map.get(t)?,
))
})
.collect();
let id = PowlNodeId(self.powl.nodes.len());
self.powl.nodes.push(PowlNode::new(
id,
PowlNodeKind::ChoiceGraph {
nodes: node_ids,
edges: edge_objs,
},
));
self.label_map.insert(label.to_string(), id);
self
}
pub fn root(mut self, label: &str) -> Self {
if let Some(&id) = self.label_map.get(label) {
self.powl.root = Some(id);
}
self
}
pub fn build(self) -> Result<Powl, PowlRefusal> {
self.powl.validate()?;
Ok(self.powl)
}
pub fn build_unchecked(self) -> Powl {
self.powl
}
}
#[cfg(test)]
mod builder_tests {
use super::*;
#[test]
fn builder_atom_sequence() {
let powl = PowlBuilder::new()
.atom("a")
.atom("b")
.partial_order("po", &["a", "b"], &[("a", "b")])
.build()
.unwrap();
assert_eq!(powl.node_count(), 3);
assert!(powl.root.is_none()); }
#[test]
fn builder_choice() {
let powl = PowlBuilder::new()
.atom("x")
.atom("y")
.choice("c", &["x", "y"])
.root("c")
.build()
.unwrap();
assert_eq!(powl.node_count(), 3);
assert_eq!(powl.root, Some(PowlNodeId(2)));
}
#[test]
fn builder_loop() {
let powl = PowlBuilder::new()
.atom("do")
.atom("redo")
.loop_node("lp", "do", Some("redo"))
.build()
.unwrap();
assert_eq!(powl.node_count(), 3);
}
#[test]
fn builder_cyclic_partial_order_refused() {
let result = PowlBuilder::new()
.atom("a")
.atom("b")
.partial_order("po", &["a", "b"], &[("a", "b"), ("b", "a")])
.build();
assert_eq!(result, Err(PowlRefusal::CyclicPartialOrder));
}
}