use crate::law::{IsTrue, Require};
use core::marker::PhantomData;
use serde::{Deserialize, Serialize};
#[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 ChoiceGraphMarker;
#[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, Serialize, Deserialize)]
#[serde(transparent)]
pub struct PowlNodeId(pub usize);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PowlNodeKind {
Start,
End,
Atom(String),
Silent,
PartialOrder(Vec<PowlNodeId>),
ChoiceGraph {
nodes: Vec<PowlNodeId>,
edges: Vec<ChoiceGraphEdge>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(bound = "")]
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, Serialize, Deserialize)]
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,
})
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
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;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
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, Serialize, Deserialize)]
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, Serialize, Deserialize)]
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::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(Serialize, Deserialize)]
#[serde(tag = "type")]
enum TaggedPowlJson {
Activity {
#[serde(default = "default_one")]
min_freq: u32,
#[serde(default = "default_some_one")]
max_freq: Option<u32>,
#[serde(default)]
label: Option<String>,
#[serde(default)]
organization: Option<String>,
#[serde(default)]
role: Option<String>,
},
PartialOrder {
#[serde(default = "default_one")]
min_freq: u32,
#[serde(default = "default_some_one")]
max_freq: Option<u32>,
nodes: Vec<TaggedPowlJson>,
#[serde(default)]
edges: Vec<(usize, usize)>,
},
ChoiceGraph {
#[serde(default = "default_one")]
min_freq: u32,
#[serde(default = "default_some_one")]
max_freq: Option<u32>,
nodes: Vec<TaggedPowlJson>,
#[serde(default)]
edges: Vec<(usize, usize)>,
#[serde(default)]
start_nodes: Vec<usize>,
#[serde(default)]
end_nodes: Vec<usize>,
},
}
fn default_one() -> u32 {
1
}
fn default_some_one() -> Option<u32> {
Some(1)
}
fn to_tagged_json(node_id: PowlNodeId, powl: &Powl) -> TaggedPowlJson {
let node = powl.nodes.iter().find(|n| n.id == node_id).unwrap();
match &node.kind {
PowlNodeKind::Start => TaggedPowlJson::Activity {
min_freq: 1,
max_freq: Some(1),
label: Some("START".to_string()),
organization: None,
role: None,
},
PowlNodeKind::End => TaggedPowlJson::Activity {
min_freq: 1,
max_freq: Some(1),
label: Some("END".to_string()),
organization: None,
role: None,
},
PowlNodeKind::Atom(label) => TaggedPowlJson::Activity {
min_freq: 1,
max_freq: Some(1),
label: Some(label.clone()),
organization: None,
role: None,
},
PowlNodeKind::Silent => TaggedPowlJson::Activity {
min_freq: 1,
max_freq: Some(1),
label: None,
organization: None,
role: None,
},
PowlNodeKind::PartialOrder(children) => {
let json_nodes: Vec<TaggedPowlJson> =
children.iter().map(|&c| to_tagged_json(c, powl)).collect();
let mut json_edges = Vec::new();
for edge in &powl.edges {
if let (Some(u_idx), Some(v_idx)) = (
children.iter().position(|&c| c == edge.from),
children.iter().position(|&c| c == edge.to),
) {
json_edges.push((u_idx, v_idx));
}
}
TaggedPowlJson::PartialOrder {
min_freq: 1,
max_freq: Some(1),
nodes: json_nodes,
edges: json_edges,
}
}
PowlNodeKind::ChoiceGraph {
nodes: cg_nodes,
edges: cg_edges,
} => {
if cg_nodes.len() < 2 {
return TaggedPowlJson::ChoiceGraph {
min_freq: 1,
max_freq: Some(1),
nodes: Vec::new(),
edges: Vec::new(),
start_nodes: Vec::new(),
end_nodes: Vec::new(),
};
}
let user_nodes = &cg_nodes[1..cg_nodes.len() - 1];
let json_nodes: Vec<TaggedPowlJson> = user_nodes
.iter()
.map(|&n| to_tagged_json(n, powl))
.collect();
let mut json_edges = Vec::new();
for edge in cg_edges {
if let (Some(u_idx), Some(v_idx)) = (
user_nodes.iter().position(|&n| n == edge.from),
user_nodes.iter().position(|&n| n == edge.to),
) {
json_edges.push((u_idx, v_idx));
}
}
let mut start_nodes = Vec::new();
let mut end_nodes = Vec::new();
for edge in cg_edges {
if edge.from == cg_nodes[0] {
if let Some(idx) = user_nodes.iter().position(|&n| n == edge.to) {
if !start_nodes.contains(&idx) {
start_nodes.push(idx);
}
}
}
if edge.to == cg_nodes[cg_nodes.len() - 1] {
if let Some(idx) = user_nodes.iter().position(|&n| n == edge.from) {
if !end_nodes.contains(&idx) {
end_nodes.push(idx);
}
}
}
}
let has_empty_path = cg_edges
.iter()
.any(|e| e.from == cg_nodes[0] && e.to == cg_nodes[cg_nodes.len() - 1]);
let min_freq = if has_empty_path { 0 } else { 1 };
TaggedPowlJson::ChoiceGraph {
min_freq,
max_freq: Some(1),
nodes: json_nodes,
edges: json_edges,
start_nodes,
end_nodes,
}
}
}
}
fn flatten_json_node(
json: &TaggedPowlJson,
powl: &mut Powl,
id_counter: &mut usize,
) -> Result<PowlNodeId, String> {
match json {
TaggedPowlJson::Activity { label, .. } => {
let id = PowlNodeId(*id_counter);
*id_counter += 1;
let kind = match label {
Some(l) => {
if l == "START" {
PowlNodeKind::Start
} else if l == "END" {
PowlNodeKind::End
} else {
PowlNodeKind::Atom(l.clone())
}
}
None => PowlNodeKind::Silent,
};
let node = PowlNode::new(id, kind);
powl.nodes.push(node);
Ok(id)
}
TaggedPowlJson::PartialOrder { nodes, edges, .. } => {
let mut children = Vec::new();
for child_json in nodes {
let child_id = flatten_json_node(child_json, powl, id_counter)?;
children.push(child_id);
}
for &(u_idx, v_idx) in edges {
if u_idx < children.len() && v_idx < children.len() {
powl.edges
.push(OrderEdge::new(children[u_idx], children[v_idx]));
}
}
let id = PowlNodeId(*id_counter);
*id_counter += 1;
let kind = PowlNodeKind::PartialOrder(children);
let node = PowlNode::new(id, kind);
powl.nodes.push(node);
Ok(id)
}
TaggedPowlJson::ChoiceGraph {
nodes,
edges,
start_nodes,
end_nodes,
min_freq,
..
} => {
let mut user_nodes = Vec::new();
for child_json in nodes {
let child_id = flatten_json_node(child_json, powl, id_counter)?;
user_nodes.push(child_id);
}
let start_id = PowlNodeId(*id_counter);
*id_counter += 1;
powl.nodes
.push(PowlNode::new(start_id, PowlNodeKind::Start));
let end_id = PowlNodeId(*id_counter);
*id_counter += 1;
powl.nodes.push(PowlNode::new(end_id, PowlNodeKind::End));
let mut cg_nodes = vec![start_id];
cg_nodes.extend(user_nodes.iter().copied());
cg_nodes.push(end_id);
let mut cg_edges = Vec::new();
for &(u_idx, v_idx) in edges {
if u_idx < user_nodes.len() && v_idx < user_nodes.len() {
cg_edges.push(ChoiceGraphEdge::new(user_nodes[u_idx], user_nodes[v_idx]));
}
}
for &to_idx in start_nodes {
if to_idx < user_nodes.len() {
cg_edges.push(ChoiceGraphEdge::new(start_id, user_nodes[to_idx]));
}
}
for &from_idx in end_nodes {
if from_idx < user_nodes.len() {
cg_edges.push(ChoiceGraphEdge::new(user_nodes[from_idx], end_id));
}
}
if *min_freq == 0 {
cg_edges.push(ChoiceGraphEdge::new(start_id, end_id));
}
let id = PowlNodeId(*id_counter);
*id_counter += 1;
let kind = PowlNodeKind::ChoiceGraph {
nodes: cg_nodes,
edges: cg_edges,
};
let node = PowlNode::new(id, kind);
powl.nodes.push(node);
Ok(id)
}
}
}
impl Serialize for Powl {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if let Some(root_id) = self.root {
let json = to_tagged_json(root_id, self);
json.serialize(serializer)
} else {
serializer.serialize_none()
}
}
}
impl<'de> Deserialize<'de> for Powl {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let opt_json = Option::<TaggedPowlJson>::deserialize(deserializer)?;
let mut powl = Powl::new();
if let Some(json) = opt_json {
let mut id_counter = 0;
let root_id = flatten_json_node(&json, &mut powl, &mut id_counter)
.map_err(serde::de::Error::custom)?;
powl.root = Some(root_id);
}
Ok(powl)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[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, Serialize, Deserialize)]
pub enum StandaloneChoiceGraphNode {
Start,
End,
Activity(String),
SubModel(u32),
}
pub type ChoiceGraphNode = StandaloneChoiceGraphNode;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChoiceGraph {
nodes: Vec<StandaloneChoiceGraphNode>,
edges: Vec<(usize, usize)>,
start_idx: usize,
end_idx: usize,
}
impl ChoiceGraph {
pub fn new(
nodes: Vec<StandaloneChoiceGraphNode>,
edges: Vec<(usize, usize)>,
) -> Result<Self, crate::choice_graph::ChoiceGraphError> {
let mut start_idx: Option<usize> = None;
let mut end_idx: Option<usize> = None;
for (i, n) in nodes.iter().enumerate() {
match n {
StandaloneChoiceGraphNode::Start => {
if start_idx.is_some() {
return Err(crate::choice_graph::ChoiceGraphError::MultipleStarts);
}
start_idx = Some(i);
}
StandaloneChoiceGraphNode::End => {
if end_idx.is_some() {
return Err(crate::choice_graph::ChoiceGraphError::MultipleEnds);
}
end_idx = Some(i);
}
_ => {}
}
}
let start_idx = start_idx.ok_or(crate::choice_graph::ChoiceGraphError::NoStart)?;
let end_idx = end_idx.ok_or(crate::choice_graph::ChoiceGraphError::NoEnd)?;
let n = nodes.len();
for &(a, b) in &edges {
if a >= n || b >= n {
return Err(crate::choice_graph::ChoiceGraphError::EdgeOutOfBounds);
}
}
for &(a, b) in &edges {
if b == start_idx {
return Err(crate::choice_graph::ChoiceGraphError::StartHasIncoming);
}
if a == end_idx {
return Err(crate::choice_graph::ChoiceGraphError::EndHasOutgoing);
}
}
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for &(a, b) in &edges {
adj[a].push(b);
}
let mut radj: Vec<Vec<usize>> = vec![Vec::new(); n];
for &(a, b) in &edges {
radj[b].push(a);
}
let reach_from_start = bfs_reach_local(&adj, start_idx, n);
let reach_to_end = bfs_reach_local(&radj, end_idx, n);
for i in 0..n {
if !(reach_from_start[i] && reach_to_end[i]) {
return Err(crate::choice_graph::ChoiceGraphError::NodeNotOnStartEndPath);
}
}
Ok(ChoiceGraph {
nodes,
edges,
start_idx,
end_idx,
})
}
pub fn new_raw(
nodes: Vec<StandaloneChoiceGraphNode>,
edges: Vec<(usize, usize)>,
start_idx: usize,
end_idx: usize,
) -> Result<Self, crate::choice_graph::ChoiceGraphError> {
let n = nodes.len();
if start_idx >= n || end_idx >= n {
return Err(crate::choice_graph::ChoiceGraphError::EdgeOutOfBounds);
}
for &(a, b) in &edges {
if a >= n || b >= n {
return Err(crate::choice_graph::ChoiceGraphError::EdgeOutOfBounds);
}
}
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for &(a, b) in &edges {
adj[a].push(b);
}
let mut radj: Vec<Vec<usize>> = vec![Vec::new(); n];
for &(a, b) in &edges {
radj[b].push(a);
}
let reach_from_start = bfs_reach_local(&adj, start_idx, n);
let reach_to_end = bfs_reach_local(&radj, end_idx, n);
for i in 0..n {
if !(reach_from_start[i] && reach_to_end[i]) {
return Err(crate::choice_graph::ChoiceGraphError::NodeNotOnStartEndPath);
}
}
Ok(ChoiceGraph {
nodes,
edges,
start_idx,
end_idx,
})
}
pub fn nodes(&self) -> &[StandaloneChoiceGraphNode] {
&self.nodes
}
pub fn edges(&self) -> &[(usize, usize)] {
&self.edges
}
pub fn start_idx(&self) -> usize {
self.start_idx
}
pub fn end_idx(&self) -> usize {
self.end_idx
}
pub fn set_nodes(
&mut self,
nodes: Vec<StandaloneChoiceGraphNode>,
) -> Result<(), crate::choice_graph::ChoiceGraphError> {
let old = std::mem::replace(&mut self.nodes, nodes);
if let Err(e) = self.validate_connected_path() {
self.nodes = old;
return Err(e);
}
Ok(())
}
pub fn set_edges(
&mut self,
edges: Vec<(usize, usize)>,
) -> Result<(), crate::choice_graph::ChoiceGraphError> {
let old = std::mem::replace(&mut self.edges, edges);
if let Err(e) = self.validate_connected_path() {
self.edges = old;
return Err(e);
}
Ok(())
}
pub fn set_start_idx(
&mut self,
start_idx: usize,
) -> Result<(), crate::choice_graph::ChoiceGraphError> {
let old = self.start_idx;
self.start_idx = start_idx;
if let Err(e) = self.validate_connected_path() {
self.start_idx = old;
return Err(e);
}
Ok(())
}
pub fn set_end_idx(
&mut self,
end_idx: usize,
) -> Result<(), crate::choice_graph::ChoiceGraphError> {
let old = self.end_idx;
self.end_idx = end_idx;
if let Err(e) = self.validate_connected_path() {
self.end_idx = old;
return Err(e);
}
Ok(())
}
fn validate_connected_path(&self) -> Result<(), crate::choice_graph::ChoiceGraphError> {
let n = self.nodes.len();
if self.start_idx >= n || self.end_idx >= n {
return Err(crate::choice_graph::ChoiceGraphError::EdgeOutOfBounds);
}
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for &(a, b) in &self.edges {
if a >= n || b >= n {
return Err(crate::choice_graph::ChoiceGraphError::EdgeOutOfBounds);
}
adj[a].push(b);
}
let mut radj: Vec<Vec<usize>> = vec![Vec::new(); n];
for &(a, b) in &self.edges {
radj[b].push(a);
}
let reach_from_start = bfs_reach_local(&adj, self.start_idx, n);
let reach_to_end = bfs_reach_local(&radj, self.end_idx, n);
for i in 0..n {
if !(reach_from_start[i] && reach_to_end[i]) {
return Err(crate::choice_graph::ChoiceGraphError::NodeNotOnStartEndPath);
}
}
Ok(())
}
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)
}
}
fn bfs_reach_local(adj: &[Vec<usize>], src: usize, n: usize) -> Vec<bool> {
let mut seen = vec![false; n];
if src >= n {
return seen;
}
let mut q: Vec<usize> = vec![src];
seen[src] = true;
while let Some(v) = q.pop() {
for &w in &adj[v] {
if !seen[w] {
seen[w] = true;
q.push(w);
}
}
}
seen
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
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, Serialize, Deserialize)]
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_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));
}
#[test]
fn test_powl_validate_choice_graph_with_unreachable_node() {
let mut p = Powl::new();
let start = PowlNodeId(0);
let x1 = PowlNodeId(1);
let x2 = PowlNodeId(2); let end = PowlNodeId(3);
p.nodes.push(PowlNode::new(
PowlNodeId(10),
PowlNodeKind::ChoiceGraph {
nodes: vec![start, x1, x2, end],
edges: vec![
ChoiceGraphEdge::new(start, x1),
ChoiceGraphEdge::new(x1, 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 start_node(mut self, label: &str) -> Self {
let id = PowlNodeId(self.powl.nodes.len());
self.powl.nodes.push(PowlNode::new(id, PowlNodeKind::Start));
self.label_map.insert(label.to_string(), id);
self
}
pub fn end_node(mut self, label: &str) -> Self {
let id = PowlNodeId(self.powl.nodes.len());
self.powl.nodes.push(PowlNode::new(id, PowlNodeKind::End));
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_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_kourani_figure_2_non_separable_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));
}
#[test]
fn builder_kourani_figure_7a_long_term_dependency_refused() {
let result = PowlBuilder::new()
.atom("START")
.atom("END")
.atom("a")
.atom("b")
.atom("d")
.atom("e")
.choice_graph(
"top_level",
&["START", "a", "b", "d", "e", "END"],
&[
("START", "a"),
("START", "b"),
("a", "d"),
("b", "e"),
],
)
.root("top_level")
.build();
assert_eq!(result, Err(PowlRefusal::ChoiceGraphDisconnected));
}
#[test]
fn builder_kourani_figure_1b_powl_2_0() {
let powl = PowlBuilder::new()
.atom("START") .atom("END") .atom("task_a")
.atom("task_b")
.partial_order("concurrent_production", &["task_a", "task_b"], &[])
.atom("review")
.atom("finalize")
.choice_graph(
"top_level",
&[
"START",
"concurrent_production",
"review",
"finalize",
"END",
],
&[
("START", "concurrent_production"),
("concurrent_production", "review"),
("review", "finalize"),
("review", "concurrent_production"),
("finalize", "END"),
],
)
.root("top_level")
.build()
.unwrap();
assert_eq!(powl.node_count(), 8);
assert!(powl.validate().is_ok());
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct LeafNode {
pub id: String,
pub label: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ComplexModel {
pub nodes: Vec<String>,
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct oc_powl {
pub model: ComplexModel,
}
#[cfg(test)]
mod serde_tests {
use super::*;
#[test]
fn test_serde_powl_node_kind() {
let kind = PowlNodeKind::ChoiceGraph {
nodes: vec![PowlNodeId(1), PowlNodeId(2)],
edges: vec![ChoiceGraphEdge::new(PowlNodeId(1), PowlNodeId(2))],
};
let serialized = serde_json::to_string(&kind).unwrap();
let deserialized: PowlNodeKind = serde_json::from_str(&serialized).unwrap();
assert_eq!(kind, deserialized);
}
#[test]
fn test_serde_powl_loop_node() {
let node: TypedPowlLoopNode<[PowlNodeId; 2], 2> =
TypedPowlLoopNode::new([PowlNodeId(1), PowlNodeId(2)]);
let serialized = serde_json::to_string(&node).unwrap();
let deserialized: TypedPowlLoopNode<[PowlNodeId; 2], 2> =
serde_json::from_str(&serialized).unwrap();
assert_eq!(node.children, deserialized.children);
}
#[test]
fn test_serde_powl_composition() {
let comp: PowlComposition<PowlNodeId, 8> = PowlComposition::new(PowlNodeId(42));
let serialized = serde_json::to_string(&comp).unwrap();
let deserialized: PowlComposition<PowlNodeId, 8> =
serde_json::from_str(&serialized).unwrap();
assert_eq!(comp.inner, deserialized.inner);
}
}