#[cfg(feature = "alloc")]
use alloc::{collections::VecDeque, vec, vec::Vec};
use core::{iter::FusedIterator, marker::PhantomData};
use oxgraph_topology::{
ContainsElement, ElementId, ElementIndex, ElementPredecessors, ElementSuccessors, TopologyBase,
};
use crate::bfs::BfsError;
pub(super) trait Sealed {}
pub(super) trait BfsStep<G>: Sealed
where
G: TopologyBase,
{
fn pop(&mut self) -> Option<ElementId<G>>;
fn try_visit_and_push(&mut self, graph: &G, target: ElementId<G>);
}
pub(super) trait ExpansionDirection<G>: Sealed
where
G: TopologyBase,
{
type Iter<'view>: Iterator<Item = ElementId<G>>
where
G: 'view;
fn expand(graph: &G, element: ElementId<G>) -> Self::Iter<'_>;
}
pub(super) struct Forward;
impl Sealed for Forward {}
impl<G> ExpansionDirection<G> for Forward
where
G: ElementSuccessors,
{
type Iter<'view>
= <G as ElementSuccessors>::Successors<'view>
where
G: 'view;
fn expand(graph: &G, element: ElementId<G>) -> Self::Iter<'_> {
<G as ElementSuccessors>::element_successors(graph, element)
}
}
pub(super) struct Reverse;
impl Sealed for Reverse {}
impl<G> ExpansionDirection<G> for Reverse
where
G: ElementPredecessors,
{
type Iter<'view>
= <G as ElementPredecessors>::Predecessors<'view>
where
G: 'view;
fn expand(graph: &G, element: ElementId<G>) -> Self::Iter<'_> {
<G as ElementPredecessors>::element_predecessors(graph, element)
}
}
pub(super) struct Bfs<'graph, G, S, D>
where
G: TopologyBase,
S: BfsStep<G>,
D: ExpansionDirection<G>,
{
graph: &'graph G,
state: S,
_direction: PhantomData<D>,
}
impl<'graph, G, S, D> Bfs<'graph, G, S, D>
where
G: TopologyBase,
S: BfsStep<G>,
D: ExpansionDirection<G>,
{
pub(super) const fn new(graph: &'graph G, state: S) -> Self {
Self {
graph,
state,
_direction: PhantomData,
}
}
}
impl<G, S, D> Iterator for Bfs<'_, G, S, D>
where
G: TopologyBase,
S: BfsStep<G>,
D: ExpansionDirection<G>,
{
type Item = ElementId<G>;
fn next(&mut self) -> Option<Self::Item> {
let element = self.state.pop()?;
for target in D::expand(self.graph, element) {
self.state.try_visit_and_push(self.graph, target);
}
Some(element)
}
}
impl<G, S, D> FusedIterator for Bfs<'_, G, S, D>
where
G: TopologyBase,
S: BfsStep<G>,
D: ExpansionDirection<G>,
{
}
#[cfg(feature = "alloc")]
#[must_use = "a validation witness encodes a runtime check; consume it via Seeded*Frontier::new"]
pub(super) struct ValidatedStart<'graph, G> {
start_index: usize,
bound: usize,
_graph: PhantomData<&'graph G>,
}
#[cfg(feature = "alloc")]
impl<'graph, G> ValidatedStart<'graph, G>
where
G: ContainsElement + ElementIndex,
{
pub(super) fn new(graph: &'graph G, start: ElementId<G>) -> Result<Self, BfsError> {
let bound = graph.element_bound();
if !graph.contains_element(start) {
return Err(BfsError::StartElementNotContained);
}
let start_index = graph.element_index(start);
if start_index >= bound {
return Err(BfsError::StartIndexOutOfBounds {
index: start_index,
bound,
});
}
Ok(Self {
start_index,
bound,
_graph: PhantomData,
})
}
pub(super) const fn bound(&self) -> usize {
self.bound
}
pub(super) const fn into_parts(self) -> (usize, usize) {
(self.start_index, self.bound)
}
}
#[must_use = "a validation witness encodes a runtime check; consume it via Seeded*Frontier::new"]
pub(super) struct ValidatedScratch<'graph, G> {
start_index: usize,
bound: usize,
_graph: PhantomData<&'graph G>,
}
impl<'graph, G> ValidatedScratch<'graph, G>
where
G: ContainsElement + ElementIndex,
{
pub(super) fn new(
graph: &'graph G,
start: ElementId<G>,
visited_len: usize,
queue_len: usize,
) -> Result<Self, BfsError> {
let bound = graph.element_bound();
if visited_len < bound {
return Err(BfsError::VisitedTooSmall {
needed: bound,
actual: visited_len,
});
}
if queue_len < bound {
return Err(BfsError::QueueTooSmall {
needed: bound,
actual: queue_len,
});
}
if !graph.contains_element(start) {
return Err(BfsError::StartElementNotContained);
}
let start_index = graph.element_index(start);
if start_index >= bound {
return Err(BfsError::StartIndexOutOfBounds {
index: start_index,
bound,
});
}
Ok(Self {
start_index,
bound,
_graph: PhantomData,
})
}
pub(super) const fn into_parts(self) -> (usize, usize) {
(self.start_index, self.bound)
}
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub(super) struct SeededByteFlagFrontier<'scratch, G>
where
G: ElementIndex,
{
visited: &'scratch mut [u8],
queue: &'scratch mut [ElementId<G>],
head: usize,
tail: usize,
}
impl<'scratch, G> SeededByteFlagFrontier<'scratch, G>
where
G: ContainsElement + ElementIndex,
{
pub(super) fn new(
visited: &'scratch mut [u8],
queue: &'scratch mut [ElementId<G>],
start: ElementId<G>,
witness: ValidatedScratch<'_, G>,
) -> Self {
let (start_index, bound) = witness.into_parts();
visited[..bound].fill(0);
visited[start_index] = 1;
queue[0] = start;
Self {
visited,
queue,
head: 0,
tail: 1,
}
}
}
impl<G> Sealed for SeededByteFlagFrontier<'_, G> where G: ElementIndex {}
impl<G> BfsStep<G> for SeededByteFlagFrontier<'_, G>
where
G: ContainsElement + ElementIndex,
{
fn pop(&mut self) -> Option<ElementId<G>> {
if self.head == self.tail {
return None;
}
let element = self.queue[self.head];
self.head += 1;
Some(element)
}
fn try_visit_and_push(&mut self, graph: &G, target: ElementId<G>) {
let target_index = graph.element_index(target);
let visited_len = self.visited.len();
let Some(slot) = self.visited.get_mut(target_index) else {
neighbor_oob(target_index, visited_len)
};
if *slot == 0 {
*slot = 1;
self.queue[self.tail] = target;
self.tail += 1;
}
}
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub(super) struct SeededEpochFrontier<'borrow, G>
where
G: ElementIndex,
{
marks: &'borrow mut [u32],
queue: &'borrow mut [ElementId<G>],
epoch: u32,
head: usize,
tail: usize,
}
impl<'borrow, G> SeededEpochFrontier<'borrow, G>
where
G: ContainsElement + ElementIndex,
{
pub(super) fn new(
marks: &'borrow mut [u32],
queue: &'borrow mut [ElementId<G>],
epoch: u32,
start: ElementId<G>,
witness: ValidatedScratch<'_, G>,
) -> Self {
let (start_index, _bound) = witness.into_parts();
marks[start_index] = epoch;
queue[0] = start;
Self {
marks,
queue,
epoch,
head: 0,
tail: 1,
}
}
}
impl<G> Sealed for SeededEpochFrontier<'_, G> where G: ElementIndex {}
impl<G> BfsStep<G> for SeededEpochFrontier<'_, G>
where
G: ContainsElement + ElementIndex,
{
fn pop(&mut self) -> Option<ElementId<G>> {
if self.head == self.tail {
return None;
}
let element = self.queue[self.head];
self.head += 1;
Some(element)
}
fn try_visit_and_push(&mut self, graph: &G, target: ElementId<G>) {
let target_index = graph.element_index(target);
let marks_len = self.marks.len();
let Some(mark) = self.marks.get_mut(target_index) else {
neighbor_oob(target_index, marks_len)
};
if *mark != self.epoch {
*mark = self.epoch;
self.queue[self.tail] = target;
self.tail += 1;
}
}
}
#[cfg(feature = "alloc")]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub(super) struct SeededWorkspaceFrontier<'workspace, G>
where
G: ElementIndex,
{
marks: &'workspace mut [u32],
queue: &'workspace mut Vec<ElementId<G>>,
epoch: u32,
head: usize,
}
#[cfg(feature = "alloc")]
impl<'workspace, G> SeededWorkspaceFrontier<'workspace, G>
where
G: ContainsElement + ElementIndex,
{
pub(super) fn new(
marks: &'workspace mut [u32],
queue: &'workspace mut Vec<ElementId<G>>,
epoch: u32,
start: ElementId<G>,
witness: ValidatedStart<'_, G>,
) -> Self {
let (start_index, _bound) = witness.into_parts();
queue.clear();
marks[start_index] = epoch;
queue.push(start);
Self {
marks,
queue,
epoch,
head: 0,
}
}
}
#[cfg(feature = "alloc")]
impl<G> Sealed for SeededWorkspaceFrontier<'_, G> where G: ElementIndex {}
#[cfg(feature = "alloc")]
impl<G> BfsStep<G> for SeededWorkspaceFrontier<'_, G>
where
G: ContainsElement + ElementIndex,
{
fn pop(&mut self) -> Option<ElementId<G>> {
let element = self.queue.get(self.head).copied()?;
self.head += 1;
Some(element)
}
fn try_visit_and_push(&mut self, graph: &G, target: ElementId<G>) {
let target_index = graph.element_index(target);
let marks_len = self.marks.len();
let Some(mark) = self.marks.get_mut(target_index) else {
neighbor_oob(target_index, marks_len)
};
if *mark != self.epoch {
*mark = self.epoch;
self.queue.push(target);
}
}
}
#[cfg(feature = "alloc")]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub(super) struct SeededOwnedFrontier<G>
where
G: ElementIndex,
{
visited: Vec<u8>,
queue: Vec<ElementId<G>>,
head: usize,
}
#[cfg(feature = "alloc")]
impl<G> SeededOwnedFrontier<G>
where
G: ContainsElement + ElementIndex,
{
pub(super) fn new(start: ElementId<G>, witness: ValidatedStart<'_, G>) -> Self {
let (start_index, bound) = witness.into_parts();
let mut queue = Vec::with_capacity(bound);
queue.push(start);
let mut visited = vec![0; bound];
visited[start_index] = 1;
Self {
visited,
queue,
head: 0,
}
}
}
#[cfg(feature = "alloc")]
impl<G> Sealed for SeededOwnedFrontier<G> where G: ElementIndex {}
#[cfg(feature = "alloc")]
impl<G> BfsStep<G> for SeededOwnedFrontier<G>
where
G: ContainsElement + ElementIndex,
{
fn pop(&mut self) -> Option<ElementId<G>> {
let element = self.queue.get(self.head).copied()?;
self.head += 1;
Some(element)
}
fn try_visit_and_push(&mut self, graph: &G, target: ElementId<G>) {
let target_index = graph.element_index(target);
let visited_len = self.visited.len();
let Some(slot) = self.visited.get_mut(target_index) else {
neighbor_oob(target_index, visited_len)
};
if *slot == 0 {
*slot = 1;
self.queue.push(target);
}
}
}
#[cfg(feature = "alloc")]
pub(super) trait VisitedSet<T>: Default {
fn insert(&mut self, value: T) -> bool;
}
#[cfg(feature = "alloc")]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub(super) struct GenericState<G, V>
where
G: TopologyBase,
V: VisitedSet<ElementId<G>>,
{
queue: VecDeque<ElementId<G>>,
visited: V,
}
#[cfg(feature = "alloc")]
impl<G, V> GenericState<G, V>
where
G: TopologyBase,
V: VisitedSet<ElementId<G>>,
{
pub(super) fn new(start: ElementId<G>) -> Self {
let mut queue = VecDeque::new();
queue.push_back(start);
let mut visited = V::default();
visited.insert(start);
Self { queue, visited }
}
}
#[cfg(feature = "alloc")]
impl<G, V> Sealed for GenericState<G, V>
where
G: TopologyBase,
V: VisitedSet<ElementId<G>>,
{
}
#[cfg(feature = "alloc")]
impl<G, V> BfsStep<G> for GenericState<G, V>
where
G: TopologyBase,
V: VisitedSet<ElementId<G>>,
{
fn pop(&mut self) -> Option<ElementId<G>> {
self.queue.pop_front()
}
fn try_visit_and_push(&mut self, _graph: &G, target: ElementId<G>) {
if self.visited.insert(target) {
self.queue.push_back(target);
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub(super) struct EpochCounter {
epoch: u32,
}
impl EpochCounter {
pub(super) const fn new() -> Self {
Self { epoch: 0 }
}
pub(super) fn advance(&mut self, marks: &mut [u32]) -> u32 {
if self.epoch == u32::MAX {
marks.fill(0);
self.epoch = 1;
} else {
self.epoch += 1;
}
self.epoch
}
}
#[cold]
#[track_caller]
pub(super) fn neighbor_oob(index: usize, bound: usize) -> ! {
panic!("{}", BfsError::NeighborIndexOutOfBounds { index, bound })
}