use crate::error::GraphError;
use crate::types::{DenseNodeId, Endpoint, MarkedEdge, MiddleMark};
use crate::workspace::GraphWorkspace;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub(crate) struct AdjEntry {
pub(crate) neighbor: DenseNodeId,
pub(crate) at_self: Endpoint,
pub(crate) at_neighbor: Endpoint,
pub(crate) middle: MiddleMark,
}
impl AdjEntry {
#[inline]
pub(crate) const fn new(
neighbor: DenseNodeId,
at_self: Endpoint,
at_neighbor: Endpoint,
middle: MiddleMark,
) -> Self {
Self { neighbor, at_self, at_neighbor, middle }
}
#[inline]
pub(crate) const fn is_directed_out(self) -> bool {
matches!((self.at_self, self.at_neighbor), (Endpoint::Tail, Endpoint::Arrow))
}
}
pub(crate) fn push_marked_pair(adj: &mut [Vec<AdjEntry>], edge: MarkedEdge) {
adj[edge.a.as_usize()].push(AdjEntry::new(edge.b, edge.at_a, edge.at_b, edge.middle));
adj[edge.b.as_usize()].push(AdjEntry::new(edge.a, edge.at_b, edge.at_a, edge.middle));
}
#[must_use]
pub(crate) fn edge_between(
adj: &[Vec<AdjEntry>],
a: DenseNodeId,
b: DenseNodeId,
) -> Option<MarkedEdge> {
if a.as_usize() >= adj.len() || b.as_usize() >= adj.len() {
return None;
}
for e in &adj[a.as_usize()] {
if e.neighbor == b {
return Some(MarkedEdge {
a,
b,
at_a: e.at_self,
at_b: e.at_neighbor,
middle: e.middle,
});
}
}
None
}
pub(crate) fn directed_children(
adj: &[Vec<AdjEntry>],
id: DenseNodeId,
) -> impl Iterator<Item = DenseNodeId> + '_ {
adj.get(id.as_usize()).into_iter().flatten().filter(|e| e.is_directed_out()).map(|e| e.neighbor)
}
pub(crate) fn directed_parents(
adj: &[Vec<AdjEntry>],
id: DenseNodeId,
) -> impl Iterator<Item = DenseNodeId> + '_ {
adj.get(id.as_usize())
.into_iter()
.flatten()
.filter(|e| matches!((e.at_self, e.at_neighbor), (Endpoint::Arrow, Endpoint::Tail)))
.map(|e| e.neighbor)
}
pub(crate) fn undirected_neighbors(
adj: &[Vec<AdjEntry>],
id: DenseNodeId,
) -> impl Iterator<Item = DenseNodeId> + '_ {
adj.get(id.as_usize())
.into_iter()
.flatten()
.filter(|e| matches!((e.at_self, e.at_neighbor), (Endpoint::Tail, Endpoint::Tail)))
.map(|e| e.neighbor)
}
pub(crate) fn all_marked_edges(adj: &[Vec<AdjEntry>]) -> Vec<MarkedEdge> {
let mut out = Vec::new();
for (i, nbrs) in adj.iter().enumerate() {
let a = DenseNodeId::from_raw(u32::try_from(i).expect("fit"));
for e in nbrs {
if a.raw() < e.neighbor.raw()
|| (a.raw() == e.neighbor.raw()
&& matches!((e.at_self, e.at_neighbor), (Endpoint::Tail, Endpoint::Arrow)))
{
out.push(MarkedEdge {
a,
b: e.neighbor,
at_a: e.at_self,
at_b: e.at_neighbor,
middle: e.middle,
});
} else if a.raw() > e.neighbor.raw() {
} else if matches!((e.at_self, e.at_neighbor), (Endpoint::Arrow, Endpoint::Tail)) {
out.push(MarkedEdge::directed(e.neighbor, a));
}
}
}
out.sort_by_key(|e| (e.a.raw(), e.b.raw(), e.at_a as u8, e.at_b as u8));
out.dedup();
out
}
pub(crate) fn neighbors(
adj: &[Vec<AdjEntry>],
id: DenseNodeId,
) -> impl Iterator<Item = (DenseNodeId, Endpoint, Endpoint)> + '_ {
adj[id.as_usize()].iter().map(|e| (e.neighbor, e.at_self, e.at_neighbor))
}
pub(crate) fn insert_marked_finish(
adj: &mut [Vec<AdjEntry>],
edge: MarkedEdge,
) -> Result<(), GraphError> {
if edge_between(adj, edge.a, edge.b).is_some() {
return Err(GraphError::DuplicateEdge { from: edge.a.raw(), to: edge.b.raw() });
}
if let Some((from, to)) = edge.parent_child() {
let mut ws = GraphWorkspace::default();
if reaches_directed(adj, &mut ws, to, from) {
return Err(GraphError::Cycle { from: from.raw(), to: to.raw() });
}
}
push_marked_pair(adj, edge);
Ok(())
}
pub(crate) fn orient_undirected_finish(
adj: &mut [Vec<AdjEntry>],
from: DenseNodeId,
to: DenseNodeId,
) -> Result<(), GraphError> {
let Some(edge) = edge_between(adj, from, to) else {
return Err(GraphError::UnknownNode { id: from.raw() });
};
if !edge.is_undirected() {
return Err(GraphError::InvalidEndpoints {
message: "orient_undirected requires an undirected Tail–Tail edge",
});
}
let mut ws = GraphWorkspace::default();
if reaches_directed(adj, &mut ws, to, from) {
return Err(GraphError::Cycle { from: from.raw(), to: to.raw() });
}
set_marks(adj, from, to, Endpoint::Tail, Endpoint::Arrow)
}
pub(crate) fn mark_conflict_finish(
adj: &mut [Vec<AdjEntry>],
a: DenseNodeId,
b: DenseNodeId,
) -> Result<(), GraphError> {
if edge_between(adj, a, b).is_none() {
return Err(GraphError::UnknownNode { id: a.raw() });
}
set_marks(adj, a, b, Endpoint::Conflict, Endpoint::Conflict)
}
pub(crate) fn set_marks_finish(
adj: &mut [Vec<AdjEntry>],
a: DenseNodeId,
b: DenseNodeId,
at_a: Endpoint,
at_b: Endpoint,
previous: MarkedEdge,
) -> Result<(), GraphError> {
let edge = MarkedEdge { a, b, at_a, at_b, middle: previous.middle };
if let Some((from, to)) = edge.parent_child() {
remove_edge(adj, a, b);
let mut ws = GraphWorkspace::default();
let cycle = reaches_directed(adj, &mut ws, to, from);
if cycle {
push_marked_pair(adj, previous);
return Err(GraphError::Cycle { from: from.raw(), to: to.raw() });
}
push_marked_pair(adj, edge);
return Ok(());
}
set_marks(adj, a, b, at_a, at_b)
}
#[must_use]
pub(crate) fn reaches_directed(
adj: &[Vec<AdjEntry>],
ws: &mut GraphWorkspace,
from: DenseNodeId,
to: DenseNodeId,
) -> bool {
if from == to {
return true;
}
if from.as_usize() >= adj.len() || to.as_usize() >= adj.len() {
return false;
}
ws.prepare(adj.len());
ws.frontier.push(from);
ws.visited.insert(from);
while let Some(u) = ws.frontier.pop() {
for c in directed_children(adj, u) {
if c == to {
return true;
}
if !ws.visited.contains(c) {
ws.visited.insert(c);
ws.frontier.push(c);
}
}
}
false
}
pub(crate) fn set_marks(
adj: &mut [Vec<AdjEntry>],
a: DenseNodeId,
b: DenseNodeId,
at_a: Endpoint,
at_b: Endpoint,
) -> Result<(), GraphError> {
let mut found = false;
for e in &mut adj[a.as_usize()] {
if e.neighbor == b {
e.at_self = at_a;
e.at_neighbor = at_b;
found = true;
break;
}
}
if !found {
return Err(GraphError::UnknownNode { id: a.raw() });
}
found = false;
for e in &mut adj[b.as_usize()] {
if e.neighbor == a {
e.at_self = at_b;
e.at_neighbor = at_a;
found = true;
break;
}
}
if !found {
return Err(GraphError::UnknownNode { id: b.raw() });
}
Ok(())
}
pub(crate) fn set_middle(
adj: &mut [Vec<AdjEntry>],
a: DenseNodeId,
b: DenseNodeId,
middle: MiddleMark,
) -> Result<(), GraphError> {
let mut found = false;
for e in &mut adj[a.as_usize()] {
if e.neighbor == b {
e.middle = middle;
found = true;
break;
}
}
if !found {
return Err(GraphError::UnknownNode { id: a.raw() });
}
found = false;
for e in &mut adj[b.as_usize()] {
if e.neighbor == a {
e.middle = middle;
found = true;
break;
}
}
if !found {
return Err(GraphError::UnknownNode { id: b.raw() });
}
Ok(())
}
pub(crate) fn remove_edge(adj: &mut [Vec<AdjEntry>], a: DenseNodeId, b: DenseNodeId) {
adj[a.as_usize()].retain(|e| e.neighbor != b);
adj[b.as_usize()].retain(|e| e.neighbor != a);
}