diff --git a/src/hierarchy.rs b/src/hierarchy.rs index 87c5ead..5fba4db 100644 --- a/src/hierarchy.rs +++ b/src/hierarchy.rs @@ -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; @@ -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 { @@ -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, +} + +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 { + // 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) { + (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)] @@ -668,6 +733,10 @@ mod test { hierarchy.children(root).collect::>(), vec![child0, child1, child2] ); + assert_eq!( + hierarchy.descendants(root).collect::>(), + 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)); diff --git a/src/view.rs b/src/view.rs index 7e9ffae..eab7085 100644 --- a/src/view.rs +++ b/src/view.rs @@ -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; @@ -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. diff --git a/src/view/flat_region.rs b/src/view/flat_region.rs new file mode 100644 index 0000000..28b3e34 --- /dev/null +++ b/src/view/flat_region.rs @@ -0,0 +1,359 @@ +//! 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, + } + } + + /// Get the root node of the region. + pub fn region_root(&self) -> NodeIndex { + self.region_root + } +} + +impl<'g, G> FlatRegion<'g, G> +where + G: LinkView + Clone, +{ + /// Utility function to filter out links that are not in the region. + #[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 region. + #[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 + } + + #[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 + Clone { + std::iter::once(self.region_root).chain(self.hierarchy.children(self.region_root)) + } + + #[inline] + fn ports_iter(&self) -> impl Iterator + 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) -> Option; + fn port_node(&self, port: impl Into) -> Option; + fn port_offset(&self, port: impl Into) -> Option; + fn port_index(&self, node: NodeIndex, offset: crate::PortOffset) -> Option; + fn ports(&self, node: NodeIndex, direction: Direction) -> impl Iterator + Clone; + fn all_ports(&self, node: NodeIndex) -> impl Iterator + Clone; + fn input(&self, node: NodeIndex, offset: usize) -> Option; + fn output(&self, node: NodeIndex, offset: usize) -> Option; + fn num_ports(&self, node: NodeIndex, direction: Direction) -> usize; + fn port_offsets(&self, node: NodeIndex, direction: Direction) -> impl Iterator + Clone; + fn all_port_offsets(&self, node: NodeIndex) -> impl Iterator + 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 + 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 + Clone { + self.graph + .port_links(port) + .filter(|&lnk| self.contains_link(lnk)) + } + + fn links( + &self, + node: NodeIndex, + direction: Direction, + ) -> impl Iterator + Clone { + self.graph + .links(node, direction) + .filter(|&lnk| self.contains_link(lnk)) + } + + fn all_links( + &self, + node: NodeIndex, + ) -> impl Iterator + Clone { + self.graph + .all_links(node) + .filter(|&lnk| self.contains_link(lnk)) + } + + fn neighbours( + &self, + node: NodeIndex, + direction: Direction, + ) -> impl Iterator + Clone { + self.graph + .neighbours(node, direction) + .filter(|&n| self.contains_node(n)) + } + + fn all_neighbours(&self, node: NodeIndex) -> impl Iterator + 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 + Clone { + self.graph + .subports(node, direction) + .filter(|&p| self.contains_endpoint(p)) + } + + fn all_subports(&self, node: NodeIndex) -> impl Iterator + Clone { + self.graph + .all_subports(node) + .filter(|&p| self.contains_endpoint(p)) + } + + fn subport_link(&self, subport: Self::LinkEndpoint) -> Option { + self.graph + .subport_link(subport) + .filter(|&p| self.contains_endpoint(p)) + } +} + +#[cfg(test)] +mod test { + use std::error::Error; + + use itertools::Itertools; + + use crate::multiportgraph::SubportIndex; + use crate::{Hierarchy, LinkMut, MultiPortGraph, 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> { + 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)?; + graph.link_nodes(a, 1, root, 0)?; + + let mut hierarchy = Hierarchy::new(); + hierarchy.push_child(root, other)?; + hierarchy.push_child(a, root)?; + hierarchy.push_child(b, root)?; + hierarchy.push_child(c, b)?; + + let region = FlatRegion::new(&graph, &hierarchy, root); + + assert!(!region.is_empty()); + assert_eq!(region.region_root(), root); + assert_eq!(region.node_count(), 3); + assert_eq!(region.port_count(), 4); + assert_eq!(region.node_capacity(), graph.node_capacity() - 2); + assert_eq!(region.port_capacity(), graph.port_capacity() - 42); + assert_eq!(region.node_port_capacity(a), graph.node_port_capacity(a)); + assert_eq!( + region.port_offsets(a, Direction::Outgoing).collect_vec(), + graph.port_offsets(a, Direction::Outgoing).collect_vec() + ); + + assert!(!region.contains_node(other)); + assert!(!region.contains_node(c)); + assert!(region.contains_node(root)); + assert!(!region.contains_port(graph.input(other, 10).unwrap())); + assert!(region.contains_port(graph.output(a, 0).unwrap())); + + assert_eq!(region.inputs(a).count(), 1); + assert_eq!(region.outputs(a).count(), 2); + assert_eq!(region.num_ports(a, Direction::Incoming), 1); + assert_eq!(region.num_ports(a, Direction::Outgoing), 2); + assert_eq!(region.all_ports(a).count(), 3); + assert_eq!(region.all_port_offsets(a).count(), 3); + + let inputs = region + .inputs(a) + .enumerate() + .map(|(i, port)| (i, port, Direction::Incoming)); + let outputs = region + .outputs(a) + .enumerate() + .map(|(i, port)| (i, port, Direction::Outgoing)); + for (i, port, dir) in inputs.chain(outputs) { + let offset = PortOffset::new(dir, i); + assert_eq!(region.port_direction(port), Some(dir)); + assert_eq!(region.port_offset(port), Some(offset)); + assert_eq!(region.port_node(port), Some(a)); + assert_eq!(region.port_index(a, offset), Some(port)); + } + + // Global iterators + let nodes = region.nodes_iter().collect_vec(); + assert_eq!(nodes.as_slice(), [root, a, b]); + + let ports = region.ports_iter().collect_vec(); + assert_eq!(ports.len(), region.port_count()); + + assert_eq!(region, region.clone()); + + // Links + let a_o1 = region.output(a, 1).unwrap(); + let root_i0 = region.input(root, 0).unwrap(); + assert!(region.connected(a, root)); + assert_eq!(region.link_count(), 1); + assert_eq!(region.output_neighbours(a).collect_vec().as_slice(), [root]); + assert_eq!( + region.output_links(a).collect_vec().as_slice(), + [(a_o1, root_i0)] + ); + assert_eq!( + region.port_links(a_o1).collect_vec().as_slice(), + [(a_o1, root_i0)] + ); + assert_eq!( + region.all_links(root).collect_vec().as_slice(), + [(root_i0, a_o1)] + ); + assert_eq!( + region.get_connections(a, root).collect_vec().as_slice(), + [(a_o1, root_i0)] + ); + assert_eq!(region.get_connections(b, a).count(), 0); + assert_eq!(region.all_neighbours(root).collect_vec().as_slice(), [a]); + + // Multiports + let multigraph = MultiPortGraph::from(graph); + let region = FlatRegion::new(&multigraph, &hierarchy, root); + let a_o1 = SubportIndex::new_unique(a_o1); + assert_eq!( + region.all_subports(a).collect_vec(), + multigraph.all_subports(a).collect_vec() + ); + assert_eq!( + region.subports(a, Direction::Incoming).collect_vec(), + multigraph.subports(a, Direction::Incoming).collect_vec() + ); + assert_eq!(region.subport_link(a_o1), multigraph.subport_link(a_o1)); + + Ok(()) + } +} diff --git a/src/view/region.rs b/src/view/region.rs index 7dbedf8..b6b4ea6 100644 --- a/src/view/region.rs +++ b/src/view/region.rs @@ -1,53 +1,45 @@ -//! Views of a portgraph containing only the descendants of a node in a [`Hierarchy`]. +//! View of a portgraph containing only the descendants of a node in a [`Hierarchy`]. -use std::{cell::RefCell, collections::HashMap, iter}; +use delegate::delegate; +use itertools::Either; +use std::sync::RwLock; +use std::{collections::HashMap, iter}; -use super::filter::NodeFiltered; -use crate::{Hierarchy, NodeIndex}; - -type RegionCallback<'g> = fn(NodeIndex, &RegionContext<'g>) -> bool; +use super::{LinkView, MultiView, PortView}; +use crate::{Direction, Hierarchy, NodeIndex, PortIndex, PortOffset}; /// View of a portgraph containing only a root node and its descendants in a /// [`Hierarchy`]. /// /// For a view of a portgraph containing only the root and its direct children, -/// see [`FlatRegion`]. Prefer using the flat variant when possible, as it is +/// see [`crate::view::FlatRegion`]. Prefer using the flat variant when possible, as it is /// more efficient. -/// -/// [`Region`] does not implement `Sync` as it uses a [`RefCell`] to cache the -/// node filtering. -pub type Region<'g, G> = NodeFiltered, RegionContext<'g>>; - -impl<'a, G> Region<'a, G> -where - G: Clone, -{ - /// Create a new region view including all the descendants of the root node. - pub fn new_region(graph: G, hierarchy: &'a Hierarchy, root: NodeIndex) -> Self { - let region_filter: RegionCallback<'a> = - |node, context| node == context.root() || context.is_descendant(node); - Self::new_node_filtered(graph, region_filter, RegionContext::new(hierarchy, root)) - } -} - -/// Internal context used in the [`Region`] adaptor. -#[derive(Debug, Clone)] -pub struct RegionContext<'g> { +#[derive(Debug)] +pub struct Region<'g, G> { + /// The base graph + graph: G, + /// The root node of the region + region_root: NodeIndex, + /// The graph's hierarchy hierarchy: &'g Hierarchy, - root: NodeIndex, /// Cache of the result of the [`is_descendant`] check. - is_descendant: RefCell>, + is_descendant: RwLock>, } -impl<'g> RegionContext<'g> { - /// Create a new [`RegionContext`]. - pub fn new(hierarchy: &'g Hierarchy, root: NodeIndex) -> Self { +impl<'g, G> Region<'g, G> +where + G: Clone, +{ + /// Create a new [`Region`] looking at a `root` node and its descendants in + /// a [`Hierarchy`]. + pub fn new(graph: G, hierarchy: &'g Hierarchy, root: NodeIndex) -> Self { let mut is_descendant = HashMap::new(); is_descendant.insert(root, false); Self { + graph, + region_root: root, hierarchy, - root, - is_descendant: RefCell::new(is_descendant), + is_descendant: RwLock::new(is_descendant), } } @@ -55,28 +47,47 @@ impl<'g> RegionContext<'g> { /// /// Caches the result of the check. pub fn is_descendant(&self, node: NodeIndex) -> bool { - if let Some(is_descendant) = self.is_descendant.borrow().get(&node) { - // We have already checked this node. - return *is_descendant; + if node == self.region_root { + return true; } - // Traverse the ancestors until we see a node we have already checked. - let cache = self.is_descendant.borrow(); - let ancestors: Vec<_> = iter::successors(Some(node), |node| { - let parent = self.hierarchy.parent(*node)?; - match cache.contains_key(&parent) { - true => None, - false => Some(parent), + + // First, access the cache read-only to check if the node has already been visited. + // And compute whether the node is a descendant otherwise. + let (ancestors, is_descendant) = { + let cache = self + .is_descendant + .read() + .expect("The Region cache is poisoned."); + + if let Some(is_descendant) = cache.get(&node) { + // We have already checked this node. + return *is_descendant; } - }) - .collect(); - let first_visited_ancestor = self.hierarchy.parent(*ancestors.last().unwrap()); - let is_descendant = first_visited_ancestor == Some(self.root) - || first_visited_ancestor - .map_or(false, |ancestor| cache.get(&ancestor).copied().unwrap()); - drop(cache); - - // Update the cache for all the unvisited ancestors. - let mut cache_mut = self.is_descendant.borrow_mut(); + // Traverse the ancestors until we see a node we have already checked, or the root. + let ancestors: Vec<_> = iter::successors(Some(node), |node| { + let parent = self.hierarchy.parent(*node)?; + match cache.contains_key(&parent) { + true => None, + false => Some(parent), + } + }) + .collect(); + let first_visited_ancestor = self.hierarchy.parent(*ancestors.last().unwrap()); + let is_descendant = first_visited_ancestor == Some(self.region_root) + || first_visited_ancestor + .map_or(false, |ancestor| cache.get(&ancestor).copied().unwrap()); + + // The read lock is dropped here, before we reacquire it for writing + // the computed values. + drop(cache); + (ancestors, is_descendant) + }; + + // Now lock the cache for writing and update it with the new information. + let mut cache_mut = self + .is_descendant + .write() + .expect("The Region cache is poisoned."); for node in ancestors { cache_mut.insert(node, is_descendant); } @@ -84,30 +95,216 @@ impl<'g> RegionContext<'g> { } /// Get the root node of the region. - pub fn root(&self) -> NodeIndex { - self.root + pub fn region_root(&self) -> NodeIndex { + self.region_root } } -type FlatRegionContext<'g> = (&'g Hierarchy, NodeIndex); -type FlatRegionCallback<'g> = fn(NodeIndex, &FlatRegionContext<'g>) -> bool; +impl<'g, G> Region<'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) + } -/// View of a portgraph containing only a root node and its direct children in a [`Hierarchy`]. -/// -/// For a view of all descendants, see [`Region`]. -pub type FlatRegion<'g, G> = NodeFiltered, FlatRegionContext<'g>>; + /// 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: PartialEq> PartialEq for Region<'g, G> { + fn eq(&self, other: &Self) -> bool { + self.region_root == other.region_root + && self.graph == other.graph + && self.hierarchy == other.hierarchy + } +} + +impl<'g, G: Clone> Clone for Region<'g, G> { + fn clone(&self) -> Self { + // Clone the cache if it is not currently locked. + // Otherwise, create a new empty cache. + let is_descendant = match self.is_descendant.try_read() { + Ok(cache) => cache.clone(), + Err(_) => HashMap::new(), + }; + Self { + graph: self.graph.clone(), + region_root: self.region_root, + hierarchy: self.hierarchy, + is_descendant: RwLock::new(is_descendant), + } + } +} -impl<'a, G> FlatRegion<'a, G> +impl<'g, G> PortView for Region<'g, G> where - G: Clone, + G: PortView + Clone, { - /// Create a new region view including all the descendants of the root node. - pub fn new_flat_region(graph: G, hierarchy: &'a Hierarchy, root: NodeIndex) -> Self { - let region_filter: FlatRegionCallback<'a> = |node, context| { - let (hierarchy, root) = context; - node == *root || hierarchy.parent(node) == Some(*root) + #[inline(always)] + fn contains_node(&'_ self, node: NodeIndex) -> bool { + self.is_descendant(node) + } + + #[inline(always)] + fn contains_port(&self, port: PortIndex) -> bool { + let Some(node) = self.graph.port_node(port) else { + return false; }; - Self::new_node_filtered(graph, region_filter, (hierarchy, root)) + self.contains_node(node) + } + + #[inline] + fn is_empty(&self) -> bool { + // The region root is always present + false + } + + #[inline] + fn node_count(&self) -> usize { + self.nodes_iter().count() + } + + #[inline] + fn port_count(&self) -> usize { + self.ports_iter().count() + } + + #[inline] + fn nodes_iter(&self) -> impl Iterator + Clone { + self.hierarchy.descendants(self.region_root) + } + + #[inline] + fn ports_iter(&self) -> impl Iterator + 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) -> Option; + fn port_node(&self, port: impl Into) -> Option; + fn port_offset(&self, port: impl Into) -> Option; + fn port_index(&self, node: NodeIndex, offset: crate::PortOffset) -> Option; + fn ports(&self, node: NodeIndex, direction: Direction) -> impl Iterator + Clone; + fn all_ports(&self, node: NodeIndex) -> impl Iterator + Clone; + fn input(&self, node: NodeIndex, offset: usize) -> Option; + fn output(&self, node: NodeIndex, offset: usize) -> Option; + fn num_ports(&self, node: NodeIndex, direction: Direction) -> usize; + fn port_offsets(&self, node: NodeIndex, direction: Direction) -> impl Iterator + Clone; + fn all_port_offsets(&self, node: NodeIndex) -> impl Iterator + Clone; + fn node_port_capacity(&self, node: NodeIndex) -> usize; + } + } +} + +impl<'g, G> LinkView for Region<'g, G> +where + G: LinkView + Clone, +{ + type LinkEndpoint = G::LinkEndpoint; + + fn get_connections( + &self, + from: NodeIndex, + to: NodeIndex, + ) -> impl Iterator + Clone { + if self.is_descendant(from) && self.is_descendant(to) { + Either::Left(self.graph.get_connections(from, to)) + } else { + Either::Right(std::iter::empty()) + } + } + + fn port_links( + &self, + port: PortIndex, + ) -> impl Iterator + Clone { + self.graph + .port_links(port) + .filter(|&lnk| self.contains_link(lnk)) + } + + fn links( + &self, + node: NodeIndex, + direction: Direction, + ) -> impl Iterator + Clone { + self.graph + .links(node, direction) + .filter(|&lnk| self.contains_link(lnk)) + } + + fn all_links( + &self, + node: NodeIndex, + ) -> impl Iterator + Clone { + self.graph + .all_links(node) + .filter(|&lnk| self.contains_link(lnk)) + } + + fn neighbours( + &self, + node: NodeIndex, + direction: Direction, + ) -> impl Iterator + Clone { + self.graph + .neighbours(node, direction) + .filter(|&n| self.contains_node(n)) + } + + fn all_neighbours(&self, node: NodeIndex) -> impl Iterator + 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 Region<'g, G> +where + G: MultiView + Clone, +{ + fn subports( + &self, + node: NodeIndex, + direction: Direction, + ) -> impl Iterator + Clone { + self.graph + .subports(node, direction) + .filter(|&p| self.contains_endpoint(p)) + } + + fn all_subports(&self, node: NodeIndex) -> impl Iterator + Clone { + self.graph + .all_subports(node) + .filter(|&p| self.contains_endpoint(p)) + } + + fn subport_link(&self, subport: Self::LinkEndpoint) -> Option { + self.graph + .subport_link(subport) + .filter(|&p| self.contains_endpoint(p)) } } @@ -115,7 +312,10 @@ where mod test { use std::error::Error; - use crate::{Hierarchy, LinkMut, LinkView, PortGraph, PortMut, PortView}; + use itertools::Itertools; + + use crate::multiportgraph::SubportIndex; + use crate::{Hierarchy, LinkMut, MultiPortGraph, PortGraph, PortMut}; use super::*; @@ -126,17 +326,13 @@ mod test { let hierarchy = Hierarchy::new(); - let region = FlatRegion::new_flat_region(&graph, &hierarchy, root); - assert_eq!(region.node_count(), 1); - assert_eq!(region.port_count(), 0); - - let region = Region::new_region(&graph, &hierarchy, root); + let region = Region::new(&graph, &hierarchy, root); assert_eq!(region.node_count(), 1); assert_eq!(region.port_count(), 0); } #[test] - fn simple_flat_region() -> Result<(), Box> { + fn simple_region() -> Result<(), Box> { let mut graph = PortGraph::new(); let other = graph.add_node(42, 0); let root = graph.add_node(1, 0); @@ -144,53 +340,104 @@ mod test { let b = graph.add_node(0, 0); let c = graph.add_node(0, 0); graph.link_nodes(a, 0, other, 0)?; + graph.link_nodes(a, 1, root, 0)?; let mut hierarchy = Hierarchy::new(); + hierarchy.push_child(root, other)?; hierarchy.push_child(a, root)?; hierarchy.push_child(b, root)?; hierarchy.push_child(c, b)?; - let region = FlatRegion::new_flat_region(&graph, &hierarchy, root); + let region = Region::new(&graph, &hierarchy, root); - assert!(region.nodes_iter().eq([root, a, b])); - assert_eq!(region.node_count(), 3); + assert!(!region.is_empty()); + assert_eq!(region.region_root(), root); + assert_eq!(region.node_count(), 4); assert_eq!(region.port_count(), 4); - assert_eq!(region.link_count(), 0); + assert_eq!(region.node_capacity(), graph.node_capacity() - 1); + assert_eq!(region.port_capacity(), graph.port_capacity() - 42); + assert_eq!(region.node_port_capacity(a), graph.node_port_capacity(a)); + assert_eq!( + region.port_offsets(a, Direction::Outgoing).collect_vec(), + graph.port_offsets(a, Direction::Outgoing).collect_vec() + ); - assert!(region.all_links(a).eq([])); - assert!(region.all_neighbours(a).eq([])); + assert!(!region.contains_node(other)); + assert!(region.contains_node(c)); + assert!(region.contains_node(root)); + assert!(!region.contains_port(graph.input(other, 10).unwrap())); + assert!(region.contains_port(graph.output(a, 0).unwrap())); + + assert_eq!(region.inputs(a).count(), 1); + assert_eq!(region.outputs(a).count(), 2); + assert_eq!(region.num_ports(a, Direction::Incoming), 1); + assert_eq!(region.num_ports(a, Direction::Outgoing), 2); + assert_eq!(region.all_ports(a).count(), 3); + assert_eq!(region.all_port_offsets(a).count(), 3); + + let inputs = region + .inputs(a) + .enumerate() + .map(|(i, port)| (i, port, Direction::Incoming)); + let outputs = region + .outputs(a) + .enumerate() + .map(|(i, port)| (i, port, Direction::Outgoing)); + for (i, port, dir) in inputs.chain(outputs) { + let offset = PortOffset::new(dir, i); + assert_eq!(region.port_direction(port), Some(dir)); + assert_eq!(region.port_offset(port), Some(offset)); + assert_eq!(region.port_node(port), Some(a)); + assert_eq!(region.port_index(a, offset), Some(port)); + } - Ok(()) - } + // Global iterators + let nodes = region.nodes_iter().collect_vec(); + assert_eq!(nodes.as_slice(), [root, a, b, c]); - #[test] - fn simple_region() -> Result<(), Box> { - 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 ports = region.ports_iter().collect_vec(); + assert_eq!(ports.len(), region.port_count()); - let mut hierarchy = Hierarchy::new(); - hierarchy.push_child(a, root)?; - hierarchy.push_child(b, root)?; - hierarchy.push_child(c, b)?; + assert_eq!(region, region.clone()); - let region = Region::new_region(&graph, &hierarchy, root); - - assert!( - region.nodes_iter().eq([root, a, b, c]), - "{:?} != [root, a,b,c]", - region.nodes_iter().collect::>() + // Links + let a_o1 = region.output(a, 1).unwrap(); + let root_i0 = region.input(root, 0).unwrap(); + assert!(region.connected(a, root)); + assert_eq!(region.link_count(), 1); + assert_eq!(region.output_neighbours(a).collect_vec().as_slice(), [root]); + assert_eq!( + region.output_links(a).collect_vec().as_slice(), + [(a_o1, root_i0)] ); - assert_eq!(region.node_count(), 4); - assert_eq!(region.port_count(), 4); - assert_eq!(region.link_count(), 0); - - assert!(region.all_links(a).eq([])); - assert!(region.all_neighbours(a).eq([])); + assert_eq!( + region.port_links(a_o1).collect_vec().as_slice(), + [(a_o1, root_i0)] + ); + assert_eq!( + region.all_links(root).collect_vec().as_slice(), + [(root_i0, a_o1)] + ); + assert_eq!( + region.get_connections(a, root).collect_vec().as_slice(), + [(a_o1, root_i0)] + ); + assert_eq!(region.get_connections(b, a).count(), 0); + assert_eq!(region.all_neighbours(root).collect_vec().as_slice(), [a]); + + // Multiports + let multigraph = MultiPortGraph::from(graph); + let region = Region::new(&multigraph, &hierarchy, root); + let a_o1 = SubportIndex::new_unique(a_o1); + assert_eq!( + region.all_subports(a).collect_vec(), + multigraph.all_subports(a).collect_vec() + ); + assert_eq!( + region.subports(a, Direction::Incoming).collect_vec(), + multigraph.subports(a, Direction::Incoming).collect_vec() + ); + assert_eq!(region.subport_link(a_o1), multigraph.subport_link(a_o1)); Ok(()) }