diff --git a/src/view.rs b/src/view.rs index eab7085..15a4a76 100644 --- a/src/view.rs +++ b/src/view.rs @@ -17,7 +17,7 @@ use crate::{portgraph::PortOperation, Direction, LinkError, NodeIndex, PortIndex pub use filter::{FilteredGraph, LinkFilter, NodeFilter, NodeFiltered}; pub use flat_region::FlatRegion; pub use region::Region; -pub use subgraph::Subgraph; +pub use subgraph::{CopySubgraphError, Subgraph}; /// Core capabilities for querying a graph containing nodes and ports. pub trait PortView { diff --git a/src/view/refs.rs b/src/view/refs.rs index 52dff1d..331cb02 100644 --- a/src/view/refs.rs +++ b/src/view/refs.rs @@ -3,11 +3,11 @@ //! Note: The `auto_impl` crate would do all of this for us, //! but it does not support GATs at the moment. -use crate::{Direction, NodeIndex, PortIndex, PortOffset}; +use crate::{Direction, LinkError, NodeIndex, PortIndex, PortOffset}; use delegate::delegate; -use super::{LinkView, MultiView, PortView}; +use super::{LinkMut, LinkView, MultiMut, MultiView, PortMut, PortView}; impl PortView for &G { delegate! { @@ -37,6 +37,34 @@ impl PortView for &G { } } +impl PortView for &mut G { + delegate! { + to (&**self) { + 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: 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 contains_node(&self, node: NodeIndex) -> bool; + fn contains_port(&self, port: PortIndex) -> bool; + fn is_empty(&self) -> bool; + fn node_count(&self) -> usize; + fn port_count(&self) -> usize; + fn nodes_iter(&self) -> impl Iterator + Clone; + fn ports_iter(&self) -> impl Iterator + Clone; + fn node_capacity(&self) -> usize; + fn port_capacity(&self) -> usize; + fn node_port_capacity(&self, node: NodeIndex) -> usize; + } + } +} + impl LinkView for &G { type LinkEndpoint = G::LinkEndpoint; @@ -53,6 +81,22 @@ impl LinkView for &G { } } +impl LinkView for &mut G { + type LinkEndpoint = G::LinkEndpoint; + + delegate! { + to (&**self) { + fn get_connections(&self, from: NodeIndex, to: NodeIndex) -> impl Iterator + Clone; + fn port_links(&self, port: PortIndex) -> impl Iterator + Clone; + fn links(&self, node: NodeIndex, direction: Direction) -> impl Iterator + Clone; + fn all_links(&self, node: NodeIndex) -> impl Iterator + Clone; + fn neighbours(&self, node: NodeIndex, direction: Direction) -> impl Iterator + Clone; + fn all_neighbours(&self, node: NodeIndex) -> impl Iterator + Clone; + fn link_count(&self) -> usize; + } + } +} + impl MultiView for &G { delegate! { to (*self) { @@ -63,4 +107,46 @@ impl MultiView for &G { } } -// TODO: PortMut, LinkMut, MultiMut +impl MultiView for &mut G { + delegate! { + to (&**self) { + fn subports(&self, node: NodeIndex, direction: Direction) -> impl Iterator + Clone; + fn all_subports(&self, node: NodeIndex) -> impl Iterator + Clone; + fn subport_link(&self, subport: Self::LinkEndpoint) -> Option; + } + } +} + +impl PortMut for &mut G { + delegate! { + to (*self) { + fn add_node(&mut self, incoming: usize, outgoing: usize) -> NodeIndex; + fn remove_node(&mut self, node: NodeIndex); + fn clear(&mut self); + fn reserve(&mut self, nodes: usize, ports: usize); + fn set_num_ports(&mut self, node: NodeIndex, incoming: usize, outgoing: usize, rekey: F); + fn swap_nodes(&mut self, a: NodeIndex, b: NodeIndex); + fn compact_nodes(&mut self, rekey: F); + fn compact_ports(&mut self, rekey: F); + fn shrink_to_fit(&mut self); + } + } +} + +impl LinkMut for &mut G { + delegate! { + to (*self) { + fn link_ports(&mut self, port_a: PortIndex, port_b: PortIndex) -> Result<(Self::LinkEndpoint, Self::LinkEndpoint), LinkError>; + fn unlink_port(&mut self, port: PortIndex) -> Option; + } + } +} + +impl MultiMut for &mut G { + delegate! { + to (*self) { + fn link_subports(&mut self, subport_from: Self::LinkEndpoint, subport_to: Self::LinkEndpoint) -> Result<(), LinkError>; + fn unlink_subport(&mut self, subport: Self::LinkEndpoint) -> Option; + } + } +} diff --git a/src/view/subgraph.rs b/src/view/subgraph.rs index f486ee4..291ac62 100644 --- a/src/view/subgraph.rs +++ b/src/view/subgraph.rs @@ -1,20 +1,18 @@ //! Views into non-hierarchical parts of `PortView`s. use std::borrow::Cow; -use std::collections::BTreeSet; +use std::collections::{BTreeSet, HashMap}; use delegate::delegate; use itertools::{Either, Itertools}; +use thiserror::Error; +use crate::algorithms::{ConvexChecker, TopoConvexChecker}; use crate::boundary::{Boundary, HasBoundary}; -use crate::PortOffset; use crate::{ - algorithms::{ConvexChecker, TopoConvexChecker}, - Direction, LinkView, NodeIndex, PortIndex, + Direction, LinkError, LinkMut, LinkView, MultiView, NodeIndex, PortIndex, PortOffset, PortView, }; -use super::{MultiView, PortView}; - /// View into a subgraph of a PortView. /// /// The subgraph is given by boundary edges that define where one "enters" the @@ -59,10 +57,7 @@ pub struct Subgraph { boundary: Boundary, } -impl Subgraph -where - G: Clone, -{ +impl Subgraph { /// Create a new subgraph view of `graph`. /// /// ### Arguments @@ -164,10 +159,7 @@ fn collect_boundary_ports( Boundary::new(inputs, outputs) } -impl PortView for Subgraph -where - G: PortView + Clone, -{ +impl PortView for Subgraph { #[inline(always)] fn contains_node(&'_ self, node: NodeIndex) -> bool { self.nodes.contains(&node) @@ -234,10 +226,7 @@ where } } -impl LinkView for Subgraph -where - G: LinkView + Clone, -{ +impl LinkView for Subgraph { type LinkEndpoint = G::LinkEndpoint; fn get_connections( @@ -303,10 +292,7 @@ where } } -impl MultiView for Subgraph -where - G: MultiView + Clone, -{ +impl MultiView for Subgraph { fn subport_link(&self, subport: Self::LinkEndpoint) -> Option { self.graph .subport_link(subport) @@ -321,6 +307,89 @@ where } } +/// An error from [Subgraph::copy_in_parent] +#[derive(Clone, Debug, PartialEq, Eq, Error)] +#[non_exhaustive] +pub enum CopySubgraphError { + /// Tried to copy an edge crossing a subgraph boundary + /// when the containing graph is not a [`MultiMut`](crate::MultiMut) + #[error("Cannot copy edge between external port {external:?} and (copy of) internal port {internal:?}")] + #[allow(missing_docs)] + CantCopyBoundary { + internal: PortIndex, + external: PortIndex, + }, +} + +impl Subgraph { + /// Copies all the nodes and edges in this subgraph into the parent graph. + /// + /// If there are any boundary edges, these will also be copied but keeping + /// the same *external* end port - this will fail unless the underlying graph + /// is a [`MultiMut`] + /// + /// # Returns + /// + /// A map from node indices within this subgraph, to the indices of the + /// newly-created nodes in the parent graph (they are not in this subgraph). + /// + /// # Errors + /// + /// - [`CopySubgraphError::CantCopyBoundary`] if there is a boundary edge and + /// the underlying graph is not a [`MultiMut`]. In this case the underlying + /// graph's nodes and edges will be unchanged, but capacity may have changed. + /// + /// [`MultiMut`]: crate::MultiMut + pub fn copy_in_parent(&mut self) -> Result, CopySubgraphError> { + self.graph.reserve(self.node_count(), self.port_count()); + let g = &mut self.graph; + let node_map = self + .nodes + .iter() + .map(|node| (*node, g.add_node(g.num_inputs(*node), g.num_outputs(*node)))) + .collect::>(); + for (node, new_node) in node_map.iter() { + for (node_p, other_p) in self.graph.all_links(*node).collect::>() { + let new_node_p = self + .graph + .port_index(*new_node, self.graph.port_offset(node_p).unwrap()) + .unwrap(); + match node_map.get(&self.graph.port_node(other_p).unwrap()) { + Some(new_other) => { + // Internal edge. Add once only, not once per end. (`all_links` reports self-cycles only once.) + if new_other < new_node { + continue; + } + let offset = self.graph.port_offset(other_p).unwrap(); + let new_other_p = self.graph.port_index(*new_other, offset).unwrap(); + self.graph + .link_ports(new_node_p, new_other_p) + .expect("Copying known-good edge"); + } + None => { + // Boundary edge. Keep same external endpoint + match self.graph.link_ports(new_node_p, other_p.into()) { + Err(LinkError::AlreadyLinked { port }) => { + // Must undo insertion + for n in node_map.values() { + self.graph.remove_node(*n); + } + return Err(CopySubgraphError::CantCopyBoundary { + external: port, + internal: node_p.into(), + }); + } + Err(e) => panic!("Unexpected error copying boundary edge: {}", e), + Ok(_) => (), + } + } + } + } + } + Ok(node_map) + } +} + impl HasBoundary for Subgraph { fn port_boundary(&self) -> Cow<'_, Boundary> { Cow::Borrowed(&self.boundary) @@ -332,6 +401,7 @@ mod tests { use std::collections::HashSet; use itertools::Itertools; + use rstest::rstest; use crate::multiportgraph::SubportIndex; use crate::{LinkMut, MultiPortGraph, PortGraph, PortMut, PortView}; @@ -636,4 +706,173 @@ mod tests { assert_eq!(subg.nodes_iter().collect_vec(), [n0, n2]); assert!(!subg.is_convex()); } + + #[test] + fn test_copy_in_parent() { + let mut graph = PortGraph::new(); + // First component: n0 -> n1 + cycle + let n0 = graph.add_node(0, 1); + let n1 = graph.add_node(2, 1); + graph.link_nodes(n0, 0, n1, 0).unwrap(); + graph.link_nodes(n1, 0, n1, 1).unwrap(); + // Second component: n2 -> n3 + let n2 = graph.add_node(0, 1); + let n3 = graph.add_node(1, 0); + graph.link_nodes(n2, 0, n3, 0).unwrap(); + let backup = graph.clone(); + + let mut subg = Subgraph::with_nodes(&mut graph, [n0, n1]); + let mut node_map = subg.copy_in_parent().unwrap(); + assert_eq!(subg.nodes_iter().collect_vec(), vec![n0, n1]); + assert_eq!(graph.node_count(), 6); + let n0_copy = node_map.remove(&n0).unwrap(); + let n1_copy = node_map.remove(&n1).unwrap(); + assert!(node_map.is_empty()); // No other keys + assert_eq!( + graph.input_links(n1_copy).collect_vec(), + vec![ + ( + graph.input(n1_copy, 0).unwrap(), + graph.output(n0_copy, 0).unwrap() + ), + ( + graph.input(n1_copy, 1).unwrap(), + graph.output(n1_copy, 0).unwrap() + ) + ] + ); + assert_same_for_nodes(&graph, &backup, backup.nodes_iter()); // Rest of graph unchanged + } + + fn assert_same_for_nodes( + a: A, + b: B, + nodes: impl IntoIterator, + ) where + PortIndex: From + From, + { + for node in nodes { + assert_eq!(a.num_inputs(node), b.num_inputs(node)); + assert_eq!(a.num_outputs(node), b.num_outputs(node)); + for (a_link, b_link) in a.all_links(node).zip_eq(b.all_links(node)) { + assert_eq!(PortIndex::from(a_link.0), PortIndex::from(b_link.0)); + assert_eq!(PortIndex::from(a_link.1), PortIndex::from(b_link.1)); + } + } + } + + #[rstest] + #[case(Direction::Incoming)] + #[case(Direction::Outgoing)] + fn test_copy_in_parent_bad_boundary(#[case] edge: Direction) { + let mut graph = PortGraph::new(); + let n0 = graph.add_node(0, 1); + let n1 = graph.add_node(1, 1); + let n2 = graph.add_node(1, 0); + graph.link_nodes(n0, 0, n1, 0).unwrap(); + graph.link_nodes(n1, 0, n2, 0).unwrap(); + let backup = graph.clone(); + + let (subg_nodes, internal, external) = if edge == Direction::Incoming { + ( + [n1, n2], + graph.input(n1, 0).unwrap(), + graph.output(n0, 0).unwrap(), + ) + } else { + ( + [n0, n1], + graph.output(n1, 0).unwrap(), + graph.input(n2, 0).unwrap(), + ) + }; + + let mut subg = Subgraph::with_nodes(&mut graph, subg_nodes); + + assert_eq!( + subg.copy_in_parent(), + Err(CopySubgraphError::CantCopyBoundary { internal, external }) + ); + assert_eq!( + graph.nodes_iter().collect_vec(), + backup.nodes_iter().collect_vec() + ); + assert_same_for_nodes(&graph, &backup, backup.nodes_iter()); + } + + #[test] + fn test_copy_in_parent_multi_input() { + let mut graph = MultiPortGraph::new(); + let n0 = graph.add_node(0, 1); + let n1 = graph.add_node(1, 1); + let n2 = graph.add_node(1, 0); + graph.link_nodes(n0, 0, n1, 0).unwrap(); + graph.link_nodes(n1, 0, n2, 0).unwrap(); + + let backup = graph.clone(); + + let subg_nodes = [n1, n2]; // Edge n0 -> n1 is incoming + let mut subg = Subgraph::with_nodes(&mut graph, subg_nodes); + let mut node_map = subg.copy_in_parent().unwrap(); + + let in_edge = |n| graph.inputs(n).exactly_one().ok().unwrap(); + let out_edge = |n| graph.outputs(n).exactly_one().ok().unwrap(); + + assert_eq!(graph.node_count(), 5); + assert_eq!(node_map.keys().copied().sorted().collect_vec(), subg_nodes); + let n1_copy = node_map.remove(&n1).unwrap(); + let n2_copy = *node_map.values().exactly_one().unwrap(); + assert_same_for_nodes(&graph, &backup, subg_nodes); + let (sp2, sp1) = graph.all_links(n2_copy).exactly_one().ok().unwrap(); + // Check the copied graph is correct. Its internal edge is n1 -> n2. + assert_eq!(sp2.port(), in_edge(n2_copy)); + assert_eq!(sp1.port(), out_edge(n1_copy)); + + let multiport = out_edge(n0); + assert_eq!( + graph + .all_links(n0) + .map(|(sp1, sp2)| (sp1.port(), sp2.port())) + .collect_vec(), + [(multiport, in_edge(n1)), (multiport, in_edge(n1_copy))] + ); + } + + #[test] + fn test_copy_in_parent_multi_output() { + let mut graph = MultiPortGraph::new(); + let n0 = graph.add_node(0, 1); + let n1 = graph.add_node(1, 1); + let n2 = graph.add_node(1, 0); + graph.link_nodes(n0, 0, n1, 0).unwrap(); + graph.link_nodes(n1, 0, n2, 0).unwrap(); + + let backup = graph.clone(); + + let subg_nodes = [n0, n1]; // Edge n1 -> n2 is outgoing + let mut subg = Subgraph::with_nodes(&mut graph, subg_nodes); + let mut node_map = subg.copy_in_parent().unwrap(); + + let out_edge = |n| graph.outputs(n).exactly_one().ok().unwrap(); + let in_edge = |n| graph.inputs(n).exactly_one().ok().unwrap(); + + assert_eq!(graph.node_count(), 5); + assert_eq!(node_map.keys().copied().sorted().collect_vec(), subg_nodes); + let n1_copy = node_map.remove(&n1).unwrap(); + let n0_copy = *node_map.values().exactly_one().unwrap(); + assert_same_for_nodes(&graph, &backup, subg_nodes); + let (sp0, sp1) = graph.all_links(n0_copy).exactly_one().ok().unwrap(); + // Check the copied graph is correct. Its internal edge is n0 -> n1. + assert_eq!(sp0.port(), out_edge(n0_copy)); + assert_eq!(sp1.port(), in_edge(n1_copy)); + + let multiport = in_edge(n2); + assert_eq!( + graph + .all_links(n2) + .map(|(sp1, sp2)| (sp1.port(), sp2.port())) + .collect_vec(), + [(multiport, out_edge(n1)), (multiport, out_edge(n1_copy))] + ); + } }