Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf!: Manual impl of Region/FlatRegion for improved perf #162

Merged
merged 4 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/hierarchy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
//! hierarchy.shrink_to(graph.node_count());
//! ```

use std::collections::VecDeque;
use std::iter::FusedIterator;
use std::mem::{replace, take};
use thiserror::Error;
Expand Down Expand Up @@ -390,6 +391,18 @@ impl Hierarchy {
}
}

/// Iterates over the node's descendants.
///
/// Traverses the hierarchy in breadth-first order.
///
/// The iterator will yield the node itself first, followed by its children.
pub fn descendants(&self, node: NodeIndex) -> Descendants<'_> {
Descendants {
layout: self,
child_queue: VecDeque::from(vec![node]),
}
}

/// Returns the number of the node's children.
#[inline]
pub fn child_count(&self, node: NodeIndex) -> usize {
Expand Down Expand Up @@ -610,6 +623,58 @@ impl<'a> ExactSizeIterator for Children<'a> {

impl<'a> FusedIterator for Children<'a> {}

/// Iterator created by [`Hierarchy::descendants`].
///
/// Traverses the descendants of a node in breadth-first order.
#[derive(Clone, Debug)]
pub struct Descendants<'a> {
/// The hierarchy this iterator is iterating over.
layout: &'a Hierarchy,
/// A queue of regions to visit.
///
/// For each region, we point to a child node that has not been visited yet.
/// When a region is visited, we move to the next child node and queue its children region at the end of the queue.
child_queue: VecDeque<NodeIndex>,
}

impl Default for Descendants<'static> {
fn default() -> Self {
static HIERARCHY: Hierarchy = Hierarchy::new();
Self {
layout: &HIERARCHY,
child_queue: VecDeque::new(),
}
}
}

impl<'a> Iterator for Descendants<'a> {
type Item = NodeIndex;

fn next(&mut self) -> Option<Self::Item> {
// The next element is always the first node in the queue.
let next = self.child_queue.pop_front()?;

// Check if the node had a next sibling, and add it to the front of queue.
if let Some(next_sibling) = self.layout.next(next) {
self.child_queue.push_front(next_sibling);
}

// Now add the children region of `next` to the end of the queue.
if let Some(child) = self.layout.first(next) {
self.child_queue.push_back(child);
}

Some(next)
}

#[inline(always)]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.child_queue.len(), None)
}
}

impl<'a> FusedIterator for Descendants<'a> {}

/// Error produced when trying to attach nodes in the Hierarchy.
#[derive(Debug, Clone, Error, PartialEq, Eq)]
#[allow(missing_docs)]
Expand Down Expand Up @@ -668,6 +733,10 @@ mod test {
hierarchy.children(root).collect::<Vec<_>>(),
vec![child0, child1, child2]
);
assert_eq!(
hierarchy.descendants(root).collect::<Vec<_>>(),
vec![root, child0, child1, child2]
);
assert_eq!(hierarchy.parent(root), None);
assert_eq!(hierarchy.first(root), Some(child0));
assert_eq!(hierarchy.last(root), Some(child2));
Expand Down
9 changes: 6 additions & 3 deletions src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

pub mod filter;
pub mod refs;
pub mod region;
pub mod subgraph;

mod flat_region;
mod region;
mod subgraph;

#[cfg(feature = "petgraph")]
pub mod petgraph;
Expand All @@ -13,7 +15,8 @@ use std::collections::HashMap;
use crate::{portgraph::PortOperation, Direction, LinkError, NodeIndex, PortIndex, PortOffset};

pub use filter::{FilteredGraph, LinkFilter, NodeFilter, NodeFiltered};
pub use region::{FlatRegion, Region};
pub use flat_region::FlatRegion;
pub use region::Region;
pub use subgraph::Subgraph;

/// Core capabilities for querying a graph containing nodes and ports.
Expand Down
268 changes: 268 additions & 0 deletions src/view/flat_region.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
//! View of a portgraph containing only the children of a node in a [`Hierarchy`].

use super::{LinkView, MultiView, PortView};
use crate::{Direction, Hierarchy, NodeIndex, PortIndex, PortOffset};

use delegate::delegate;
use itertools::Either;

/// View of a portgraph containing only a root node and its direct children in a [`Hierarchy`].
///
/// For a view of all descendants, see [`crate::view::Region`].
#[derive(Debug, Clone, PartialEq)]
pub struct FlatRegion<'g, G> {
/// The base graph
graph: G,
/// The root node of the region
region_root: NodeIndex,
/// The graph's hierarchy
hierarchy: &'g Hierarchy,
}

impl<'a, G> FlatRegion<'a, G>
where
G: Clone,
{
/// Create a new region view including only a root node and its direct
/// children in a [`Hierarchy`].
pub fn new(graph: G, hierarchy: &'a Hierarchy, root: NodeIndex) -> Self {
Self {
graph,
region_root: root,
hierarchy,
}
}
}

impl<'g, G> FlatRegion<'g, G>
where
G: LinkView + Clone,
{
/// Utility function to filter out links that are not in the subgraph.
#[inline(always)]
fn contains_link(&self, (from, to): (G::LinkEndpoint, G::LinkEndpoint)) -> bool {
self.contains_endpoint(from) && self.contains_endpoint(to)
}

/// Utility function to filter out link endpoints that are not in the subgraph.
#[inline(always)]
fn contains_endpoint(&self, e: G::LinkEndpoint) -> bool {
self.contains_port(e.into())
}
}

impl<'g, G> PortView for FlatRegion<'g, G>
where
G: PortView + Clone,
{
#[inline(always)]
fn contains_node(&'_ self, node: NodeIndex) -> bool {
node == self.region_root || self.hierarchy.parent(node) == Some(self.region_root)
}

#[inline(always)]
fn contains_port(&self, port: PortIndex) -> bool {
let Some(node) = self.graph.port_node(port) else {
return false;
};
self.contains_node(node)
}

#[inline]
fn is_empty(&self) -> bool {
// The region root is always present
false
ss2165 marked this conversation as resolved.
Show resolved Hide resolved
}

#[inline]
fn node_count(&self) -> usize {
self.hierarchy.child_count(self.region_root) + 1
}

#[inline]
fn port_count(&self) -> usize {
self.ports_iter().count()
}

#[inline]
fn nodes_iter(&self) -> impl Iterator<Item = NodeIndex> + Clone {
std::iter::once(self.region_root).chain(self.hierarchy.children(self.region_root))
}

#[inline]
fn ports_iter(&self) -> impl Iterator<Item = PortIndex> + Clone {
self.nodes_iter().flat_map(|n| self.graph.all_ports(n))
}

#[inline]
fn node_capacity(&self) -> usize {
self.graph.node_capacity() - self.graph.node_count() + self.node_count()
}

#[inline]
fn port_capacity(&self) -> usize {
self.graph.port_capacity() - self.graph.port_count() + self.port_count()
}

delegate! {
to self.graph {
fn port_direction(&self, port: impl Into<PortIndex>) -> Option<Direction>;
fn port_node(&self, port: impl Into<PortIndex>) -> Option<NodeIndex>;
fn port_offset(&self, port: impl Into<PortIndex>) -> Option<crate::PortOffset>;
fn port_index(&self, node: NodeIndex, offset: crate::PortOffset) -> Option<PortIndex>;
fn ports(&self, node: NodeIndex, direction: Direction) -> impl Iterator<Item = PortIndex> + Clone;
fn all_ports(&self, node: NodeIndex) -> impl Iterator<Item = PortIndex> + Clone;
fn input(&self, node: NodeIndex, offset: usize) -> Option<PortIndex>;
fn output(&self, node: NodeIndex, offset: usize) -> Option<PortIndex>;
fn num_ports(&self, node: NodeIndex, direction: Direction) -> usize;
fn port_offsets(&self, node: NodeIndex, direction: Direction) -> impl Iterator<Item = PortOffset> + Clone;
fn all_port_offsets(&self, node: NodeIndex) -> impl Iterator<Item = PortOffset> + Clone;
fn node_port_capacity(&self, node: NodeIndex) -> usize;
}
}
}

impl<'g, G> LinkView for FlatRegion<'g, G>
where
G: LinkView + Clone,
{
type LinkEndpoint = G::LinkEndpoint;

fn get_connections(
&self,
from: NodeIndex,
to: NodeIndex,
) -> impl Iterator<Item = (Self::LinkEndpoint, Self::LinkEndpoint)> + Clone {
if self.contains_node(from) && self.contains_node(to) {
Either::Left(self.graph.get_connections(from, to))
} else {
Either::Right(std::iter::empty())
}
}

fn port_links(
&self,
port: PortIndex,
) -> impl Iterator<Item = (Self::LinkEndpoint, Self::LinkEndpoint)> + Clone {
self.graph
.port_links(port)
.filter(|&lnk| self.contains_link(lnk))
ss2165 marked this conversation as resolved.
Show resolved Hide resolved
}

fn links(
&self,
node: NodeIndex,
direction: Direction,
) -> impl Iterator<Item = (Self::LinkEndpoint, Self::LinkEndpoint)> + Clone {
self.graph
.links(node, direction)
.filter(|&lnk| self.contains_link(lnk))
}

fn all_links(
&self,
node: NodeIndex,
) -> impl Iterator<Item = (Self::LinkEndpoint, Self::LinkEndpoint)> + Clone {
self.graph
.all_links(node)
.filter(|&lnk| self.contains_link(lnk))
}

fn neighbours(
&self,
node: NodeIndex,
direction: Direction,
) -> impl Iterator<Item = NodeIndex> + Clone {
self.graph
.neighbours(node, direction)
.filter(|&n| self.contains_node(n))
}

fn all_neighbours(&self, node: NodeIndex) -> impl Iterator<Item = NodeIndex> + Clone {
self.graph
.all_neighbours(node)
.filter(|&n| self.contains_node(n))
}

fn link_count(&self) -> usize {
self.nodes_iter()
.flat_map(|node| self.links(node, Direction::Outgoing))
.count()
}
}

impl<'g, G> MultiView for FlatRegion<'g, G>
where
G: MultiView + Clone,
{
fn subports(
&self,
node: NodeIndex,
direction: Direction,
) -> impl Iterator<Item = Self::LinkEndpoint> + Clone {
self.graph
.subports(node, direction)
.filter(|&p| self.contains_endpoint(p))
}

fn all_subports(&self, node: NodeIndex) -> impl Iterator<Item = Self::LinkEndpoint> + Clone {
self.graph
.all_subports(node)
.filter(|&p| self.contains_endpoint(p))
}

fn subport_link(&self, subport: Self::LinkEndpoint) -> Option<Self::LinkEndpoint> {
self.graph
.subport_link(subport)
.filter(|&p| self.contains_endpoint(p))
}
}

#[cfg(test)]
mod test {
use std::error::Error;

use crate::{Hierarchy, LinkMut, PortGraph, PortMut};

use super::*;

#[test]
fn single_node_region() {
let mut graph = PortGraph::new();
let root = graph.add_node(0, 0);

let hierarchy = Hierarchy::new();

let region = FlatRegion::new(&graph, &hierarchy, root);
assert_eq!(region.node_count(), 1);
assert_eq!(region.port_count(), 0);
}

#[test]
fn simple_flat_region() -> Result<(), Box<dyn Error>> {
let mut graph = PortGraph::new();
let other = graph.add_node(42, 0);
let root = graph.add_node(1, 0);
let a = graph.add_node(1, 2);
let b = graph.add_node(0, 0);
let c = graph.add_node(0, 0);
graph.link_nodes(a, 0, other, 0)?;

let mut hierarchy = Hierarchy::new();
hierarchy.push_child(a, root)?;
hierarchy.push_child(b, root)?;
hierarchy.push_child(c, b)?;

let region = FlatRegion::new(&graph, &hierarchy, root);

assert!(region.nodes_iter().eq([root, a, b]));
assert_eq!(region.node_count(), 3);
assert_eq!(region.port_count(), 4);
assert_eq!(region.link_count(), 0);

assert!(region.all_links(a).eq([]));
assert!(region.all_neighbours(a).eq([]));

Ok(())
}
}
Loading
Loading