pub use antecedent_core::NodeRef;
use crate::error::GraphError;
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct DenseNodeId(u32);
impl DenseNodeId {
#[must_use]
pub const fn from_raw(raw: u32) -> Self {
Self(raw)
}
pub fn try_from_usize(i: usize) -> Result<Self, GraphError> {
let raw = u32::try_from(i).map_err(|_| GraphError::TooManyNodes)?;
Ok(Self::from_raw(raw))
}
#[must_use]
pub const fn raw(self) -> u32 {
self.0
}
#[must_use]
pub const fn as_usize(self) -> usize {
self.0 as usize
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum Endpoint {
Tail,
Arrow,
Circle,
Conflict,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
pub enum MiddleMark {
Unknown,
Left,
Right,
Both,
#[default]
Empty,
}
impl MiddleMark {
#[must_use]
pub const fn is_definite(self) -> bool {
matches!(self, Self::Empty)
}
#[must_use]
pub const fn apply(self, update: Self) -> Self {
use MiddleMark::{Both, Empty, Left, Right, Unknown};
match (self, update) {
(Empty, _) | (_, Empty) => Empty,
(Both, _) | (_, Both) | (Left, Right) | (Right, Left) => Both,
(Unknown, other) | (other, Unknown) => other,
(Left, Left) => Left,
(Right, Right) => Right,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct MarkedEdge {
pub a: DenseNodeId,
pub b: DenseNodeId,
pub at_a: Endpoint,
pub at_b: Endpoint,
pub middle: MiddleMark,
}
impl MarkedEdge {
#[must_use]
pub const fn directed(from: DenseNodeId, to: DenseNodeId) -> Self {
Self {
a: from,
b: to,
at_a: Endpoint::Tail,
at_b: Endpoint::Arrow,
middle: MiddleMark::Empty,
}
}
#[must_use]
pub fn undirected(a: DenseNodeId, b: DenseNodeId) -> Self {
if a.raw() <= b.raw() {
Self { a, b, at_a: Endpoint::Tail, at_b: Endpoint::Tail, middle: MiddleMark::Empty }
} else {
Self {
a: b,
b: a,
at_a: Endpoint::Tail,
at_b: Endpoint::Tail,
middle: MiddleMark::Empty,
}
}
}
#[must_use]
pub const fn is_dag_directed(self) -> bool {
matches!(
(self.at_a, self.at_b),
(Endpoint::Tail, Endpoint::Arrow) | (Endpoint::Arrow, Endpoint::Tail)
)
}
#[must_use]
pub const fn is_undirected(self) -> bool {
matches!((self.at_a, self.at_b), (Endpoint::Tail, Endpoint::Tail))
}
#[must_use]
pub const fn is_bidirected(self) -> bool {
matches!((self.at_a, self.at_b), (Endpoint::Arrow, Endpoint::Arrow))
}
#[must_use]
pub const fn is_conflict(self) -> bool {
matches!((self.at_a, self.at_b), (Endpoint::Conflict, Endpoint::Conflict))
}
#[must_use]
pub fn bidirected(a: DenseNodeId, b: DenseNodeId) -> Self {
if a.raw() <= b.raw() {
Self { a, b, at_a: Endpoint::Arrow, at_b: Endpoint::Arrow, middle: MiddleMark::Empty }
} else {
Self {
a: b,
b: a,
at_a: Endpoint::Arrow,
at_b: Endpoint::Arrow,
middle: MiddleMark::Empty,
}
}
}
#[must_use]
pub fn conflict(a: DenseNodeId, b: DenseNodeId) -> Self {
if a.raw() <= b.raw() {
Self {
a,
b,
at_a: Endpoint::Conflict,
at_b: Endpoint::Conflict,
middle: MiddleMark::Empty,
}
} else {
Self {
a: b,
b: a,
at_a: Endpoint::Conflict,
at_b: Endpoint::Conflict,
middle: MiddleMark::Empty,
}
}
}
#[must_use]
pub const fn with_middle(mut self, middle: MiddleMark) -> Self {
self.middle = middle;
self
}
#[must_use]
pub const fn is_cpdag_legal(self) -> bool {
matches!(
(self.at_a, self.at_b),
(Endpoint::Tail, Endpoint::Arrow | Endpoint::Tail)
| (Endpoint::Arrow, Endpoint::Tail)
| (Endpoint::Conflict, Endpoint::Conflict)
)
}
#[must_use]
pub const fn is_admg_legal(self) -> bool {
matches!(
(self.at_a, self.at_b),
(Endpoint::Tail | Endpoint::Arrow, Endpoint::Arrow) | (Endpoint::Arrow, Endpoint::Tail)
)
}
#[must_use]
pub fn parent_child(self) -> Option<(DenseNodeId, DenseNodeId)> {
match (self.at_a, self.at_b) {
(Endpoint::Tail, Endpoint::Arrow) => Some((self.a, self.b)),
(Endpoint::Arrow, Endpoint::Tail) => Some((self.b, self.a)),
_ => None,
}
}
#[must_use]
pub fn causal_arrow_direction(self) -> Option<(DenseNodeId, DenseNodeId)> {
match (self.at_a, self.at_b) {
(Endpoint::Tail | Endpoint::Circle, Endpoint::Arrow) => Some((self.a, self.b)),
(Endpoint::Arrow, Endpoint::Tail | Endpoint::Circle) => Some((self.b, self.a)),
_ => None,
}
}
}
pub(crate) fn reject_future_to_past(
nodes: &[NodeRef],
from: DenseNodeId,
to: DenseNodeId,
) -> Result<(), GraphError> {
if let (NodeRef::Lagged { lag: from_lag, .. }, NodeRef::Lagged { lag: to_lag, .. }) =
(nodes[from.as_usize()], nodes[to.as_usize()])
{
if from_lag < to_lag {
return Err(GraphError::FutureToPast {
from: from.raw(),
to: to.raw(),
from_lag,
to_lag,
});
}
}
Ok(())
}