use crate::edge::link::EdgeDescriptor;
use crate::errors::GraphError;
use crate::node::link::NodeDescriptor;
use crate::node::source::EXTERNAL_INGRESS_NODE;
use crate::node::NodeKind;
#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::vec;
pub trait GraphValidator {
fn validate(&self) -> Result<(), GraphError>;
}
#[derive(Debug, Clone)]
pub struct GraphDescBuf<const N: usize, const E: usize> {
nodes: [NodeDescriptor; N],
edges: [EdgeDescriptor; E],
}
impl<const N: usize, const E: usize> GraphDescBuf<N, E> {
#[inline]
pub fn new(nodes: [NodeDescriptor; N], edges: [EdgeDescriptor; E]) -> Self {
Self { nodes, edges }
}
#[inline]
pub fn nodes(&self) -> &[NodeDescriptor; N] {
&self.nodes
}
#[inline]
pub fn edges(&self) -> &[EdgeDescriptor; E] {
&self.edges
}
}
impl<const N: usize, const E: usize> GraphValidator for GraphDescBuf<N, E> {
#[inline]
fn validate(&self) -> Result<(), GraphError> {
validate_ports(&self.nodes, &self.edges)?;
#[cfg(not(feature = "alloc"))]
{
validate_acyclic_buf::<N>(&self.nodes, &self.edges)?;
}
#[cfg(feature = "alloc")]
{
validate_acyclic_alloc(&self.nodes, &self.edges)?;
}
Ok(())
}
}
pub fn validate_ports(
nodes: &[NodeDescriptor],
edges: &[EdgeDescriptor],
) -> Result<(), GraphError> {
let n = nodes.len();
for (i, nd) in nodes.iter().enumerate() {
if nd.id().as_usize() != &i {
return Err(GraphError::IncompatiblePorts);
}
match nd.kind() {
NodeKind::Source => {
if nd.in_ports() != &0 || nd.out_ports() < &1 {
return Err(GraphError::IncompatiblePorts);
}
}
NodeKind::Sink => {
if nd.in_ports() < &1 || nd.out_ports() != &0 {
return Err(GraphError::IncompatiblePorts);
}
}
NodeKind::Split => {
if nd.in_ports() < &1 || nd.out_ports() < &2 {
return Err(GraphError::IncompatiblePorts);
}
}
NodeKind::Join => {
if nd.in_ports() < &2 || nd.out_ports() < &1 {
return Err(GraphError::IncompatiblePorts);
}
}
NodeKind::Process | NodeKind::Model => {
if nd.in_ports() < &1 || nd.out_ports() < &1 {
return Err(GraphError::IncompatiblePorts);
}
}
NodeKind::External => {
}
}
}
for (i, ed) in edges.iter().enumerate() {
if ed.id().as_usize() != &i {
return Err(GraphError::IncompatiblePorts);
}
let is_monitor = ed.upstream().node() == &EXTERNAL_INGRESS_NODE;
let t = ed.downstream().node().as_usize();
if is_monitor {
if t >= &n {
return Err(GraphError::IncompatiblePorts);
}
if nodes[*t].kind() != &NodeKind::Source {
return Err(GraphError::IncompatiblePorts);
}
for (j, other) in edges.iter().enumerate() {
if j == i {
continue;
}
if other.upstream().node() == &EXTERNAL_INGRESS_NODE
&& other.downstream().node().as_usize() == t
{
return Err(GraphError::IncompatiblePorts);
}
}
continue;
}
let f = ed.upstream().node().as_usize();
if f >= &n || t >= &n {
return Err(GraphError::IncompatiblePorts);
}
let nf = &nodes[*f];
let nt = &nodes[*t];
if *ed.upstream().port().as_usize() >= *nf.out_ports() as usize {
return Err(GraphError::IncompatiblePorts);
}
if *ed.downstream().port().as_usize() >= *nt.in_ports() as usize {
return Err(GraphError::IncompatiblePorts);
}
}
for (i, ei) in edges.iter().enumerate() {
if ei.upstream().node() == &EXTERNAL_INGRESS_NODE {
continue; }
for ej in edges.iter().skip(i + 1) {
if ej.upstream().node() == &EXTERNAL_INGRESS_NODE {
continue; }
if ei.downstream().node().as_usize() == ej.downstream().node().as_usize()
&& ei.downstream().port().as_usize() == ej.downstream().port().as_usize()
{
return Err(GraphError::IncompatiblePorts);
}
}
}
Ok(())
}
pub fn validate_acyclic_buf<const N: usize>(
_nodes: &[NodeDescriptor; N],
edges: &[EdgeDescriptor],
) -> Result<(), GraphError> {
let mut indeg = [0usize; N];
for e in edges {
if e.upstream().node() == &EXTERNAL_INGRESS_NODE {
continue; }
let u = e.upstream().node().as_usize();
let v = e.downstream().node().as_usize();
if u >= &N || v >= &N {
return Err(GraphError::IncompatiblePorts);
}
indeg[*v] += 1;
}
let mut stack = [0usize; N];
let mut top = 0usize;
for (i, °) in indeg.iter().enumerate() {
if deg == 0 {
stack[top] = i;
top += 1;
}
}
let mut visited = 0usize;
while top > 0 {
top -= 1;
let u = stack[top];
visited += 1;
for e in edges.iter().filter(|e| {
e.upstream().node() != &EXTERNAL_INGRESS_NODE && e.upstream().node().as_usize() == &u
}) {
let v = e.downstream().node().as_usize();
debug_assert!(v < &N, "downstream index validated earlier");
indeg[*v] -= 1;
if indeg[*v] == 0 {
stack[top] = *v;
top += 1;
}
}
}
if visited != N {
return Err(GraphError::Cyclic);
}
Ok(())
}
#[cfg(feature = "alloc")]
pub fn validate_acyclic_alloc(
nodes: &[NodeDescriptor],
edges: &[EdgeDescriptor],
) -> Result<(), GraphError> {
use alloc::vec::Vec;
let n = nodes.len();
let mut indeg = vec![0usize; n];
for e in edges {
if e.upstream().node() == &EXTERNAL_INGRESS_NODE {
continue;
}
let u = e.upstream().node().as_usize();
let v = e.downstream().node().as_usize();
if u >= &n || v >= &n {
return Err(GraphError::IncompatiblePorts);
}
indeg[*v] += 1;
}
let mut stack = Vec::with_capacity(n);
for (i, °) in indeg.iter().take(n).enumerate() {
if deg == 0 {
stack.push(i);
}
}
let mut visited = 0usize;
while let Some(u) = stack.pop() {
visited += 1;
for e in edges.iter().filter(|e| {
e.upstream().node() != &EXTERNAL_INGRESS_NODE && e.upstream().node().as_usize() == &u
}) {
let v = e.downstream().node().as_usize();
indeg[*v] -= 1;
if indeg[*v] == 0 {
stack.push(*v);
}
}
}
if visited != n {
return Err(GraphError::Cyclic);
}
Ok(())
}