Skip to content

Commit

Permalink
Add Subgraph (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
lmondada authored Aug 8, 2023
1 parent 5908c5c commit 6f6d627
Show file tree
Hide file tree
Showing 5 changed files with 483 additions and 61 deletions.
8 changes: 8 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Release notes

## unreleased (2023-XX-XX)

### Added

- `views::SubGraph` for views into non-hierarchical subgraphs ([#100][])

[#100]: https://github.com/CQCL/portgraph/issues/100

## v0.7.2 (2023-07-31)

### Added
Expand Down
2 changes: 2 additions & 0 deletions src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pub mod filter;
pub mod refs;
pub mod region;
pub mod subgraph;

#[cfg(feature = "petgraph")]
pub mod petgraph;
Expand All @@ -13,6 +14,7 @@ use crate::{portgraph::PortOperation, Direction, LinkError, NodeIndex, PortIndex

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

/// Core capabilities for querying a graph containing nodes and ports.
pub trait PortView {
Expand Down
183 changes: 124 additions & 59 deletions src/view/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,47 @@
use crate::{Direction, LinkView, MultiView, NodeIndex, PortIndex, PortView};

use context_iterators::{ContextIterator, FilterWithCtx, IntoContextIterator};
use context_iterators::{ContextIterator, FilterWithCtx, IntoContextIterator, MapWithCtx};
use delegate::delegate;

/// Node filter used by [`NodeFiltered`].
/// Node filter used by [`FilteredGraph`].
pub type NodeFilter<Ctx> = fn(NodeIndex, &Ctx) -> bool;
/// Link filter used by [`FilteredGraph`].
///
/// Ports that don't match this predicate will appear disconnected.
pub type LinkFilter<Ctx> = fn(PortIndex, &Ctx) -> bool;

/// A wrapper around a portgraph that filters out nodes.
/// A wrapper around a [`PortView`] that filters out nodes and links.
///
/// Both nodes and links can be filtered by providing the filter functions
/// `node_filter` and `link_filter`.
///
/// As ports always occopy a contiguous interval of indices, they cannot be
/// filtered out directly. Instead, when `link_filter` filters out a port it
/// appears as disconnected, effectively remove the link between ports. A link
/// is removed whenever either of its ports is filtered out.
///
/// For the special case of filtering out nodes only, the type alias
/// [`NodeFiltered`] is provided, along with [`NodeFiltered::new_node_filtered`].
#[derive(Debug, Clone, PartialEq)]
pub struct NodeFiltered<'a, G, F, Context = ()> {
pub struct FilteredGraph<'a, G, FN, FP, Context = ()> {
graph: &'a G,
filter: F,
node_filter: FN,
link_filter: FP,
context: Context,
}

impl<'a, G, F, Ctx> NodeFiltered<'a, G, F, Ctx> {
/// A wrapper around a portgraph that filters out nodes.
pub type NodeFiltered<'a, G, FN, Context = ()> =
FilteredGraph<'a, G, FN, LinkFilter<Context>, Context>;

impl<'a, G, FN, FP, Ctx> FilteredGraph<'a, G, FN, FP, Ctx> {
/// Create a new node filtered portgraph.
pub fn new(graph: &'a G, filter: F, context: Ctx) -> Self {
pub fn new(graph: &'a G, node_filter: FN, link_filter: FP, context: Ctx) -> Self {
Self {
graph,
filter,
node_filter,
link_filter,
context,
}
}
Expand All @@ -30,72 +51,113 @@ impl<'a, G, F, Ctx> NodeFiltered<'a, G, F, Ctx> {
pub fn context(&self) -> &Ctx {
&self.context
}

pub(super) fn graph(&self) -> &'a G {
self.graph
}
}

impl<'a, G, F, Ctx> NodeFiltered<'a, G, F, Ctx> {
/// Create a new node filtered portgraph.
pub fn new_node_filtered(graph: &'a G, node_filter: F, context: Ctx) -> Self {
Self::new(graph, node_filter, |_, _| true, context)
}
}

/// Filter functions used on the items of the [`NodeFiltered`] iterators.
impl<G, Ctx> NodeFiltered<'_, G, NodeFilter<Ctx>, Ctx> {
/// Filter functions used on the items of the [`FilteredGraph`] iterators.
impl<G, Ctx> FilteredGraph<'_, G, NodeFilter<Ctx>, LinkFilter<Ctx>, Ctx> {
/// Node filter used for the iterators
fn node_filter(node: &NodeIndex, ctx: &NodeFilterCtx<G, Ctx>) -> bool
fn node_filter(node: &NodeIndex, ctx: &FilteredGraphCtx<G, Ctx>) -> bool
where
G: PortView,
{
(ctx.filter)(*node, ctx.context)
(ctx.node_filter)(*node, ctx.context)
}

/// Port filter used for the iterators
fn port_filter(port: &(impl Into<PortIndex> + Copy), ctx: &NodeFilterCtx<G, Ctx>) -> bool
///
/// A port exists iff its node exists (don't use `link_filter`!)
fn port_filter(&port: &(impl Into<PortIndex> + Copy), ctx: &FilteredGraphCtx<G, Ctx>) -> bool
where
G: PortView,
{
let node = ctx.graph.port_node(*port).unwrap();
(ctx.filter)(node, ctx.context)
let node = ctx.graph.port_node(port).unwrap();
FilteredGraph::node_filter(&node, ctx)
}

/// Link filter used for the iterators
fn link_filter(link: &(G::LinkEndpoint, G::LinkEndpoint), ctx: &NodeFilterCtx<G, Ctx>) -> bool
///
/// A link exists if both its ports exist and satisfy `link_filter`.
fn link_filter(
link: &(G::LinkEndpoint, G::LinkEndpoint),
ctx: &FilteredGraphCtx<G, Ctx>,
) -> bool
where
G: LinkView,
{
let (from, to) = link;
let from_node = ctx.graph.port_node(*from).unwrap();
let to_node = ctx.graph.port_node(*to).unwrap();
(ctx.filter)(from_node, ctx.context) && (ctx.filter)(to_node, ctx.context)
let &(from, to) = link;
FilteredGraph::port_filter(&from, ctx)
&& FilteredGraph::port_filter(&to, ctx)
&& (ctx.link_filter)(from.into(), ctx.context)
&& (ctx.link_filter)(to.into(), ctx.context)
}

/// The full context used for the iterators
fn as_context(&self) -> FilteredGraphCtx<G, Ctx>
where
G: PortView,
{
FilteredGraphCtx::new(
self.graph,
self.node_filter,
self.link_filter,
&self.context,
)
}
}

/// Context used internally for the [`NodeFiltered`] iterators.
/// Context used internally for the [`FilteredGraph`] iterators.
///
/// This is a named struct to make the iterator signatures more readable.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NodeFilterCtx<'a, G, Ctx> {
pub struct FilteredGraphCtx<'a, G, Ctx> {
pub(self) graph: &'a G,
pub(self) filter: NodeFilter<Ctx>,
pub(self) node_filter: NodeFilter<Ctx>,
pub(self) link_filter: LinkFilter<Ctx>,
pub(self) context: &'a Ctx,
}

impl<'a, G, Ctx> NodeFilterCtx<'a, G, Ctx> {
impl<'a, G, Ctx> FilteredGraphCtx<'a, G, Ctx> {
/// Create a new context.
pub(self) fn new(graph: &'a G, filter: NodeFilter<Ctx>, context: &'a Ctx) -> Self {
pub(self) fn new(
graph: &'a G,
node_filter: NodeFilter<Ctx>,
link_filter: LinkFilter<Ctx>,
context: &'a Ctx,
) -> Self {
Self {
graph,
filter,
node_filter,
link_filter,
context,
}
}
}

/// Node-filtered iterator wrapper used by [`NodeFiltered`].
pub type NodeFilteredIter<'a, G, Ctx, I> = FilterWithCtx<I, NodeFilterCtx<'a, G, Ctx>>;
/// Filtered iterator wrapper used by [`FilteredGraph`].
pub type FilteredGraphIter<'a, G, Ctx, I> = FilterWithCtx<I, FilteredGraphCtx<'a, G, Ctx>>;
/// Filtered + mapped iterator wrapper used by [`FilteredGraph`].
pub type MapFilteredGraphIter<'a, G, Ctx, I, O> = MapWithCtx<I, FilteredGraphCtx<'a, G, Ctx>, O>;

impl<G, Ctx> PortView for NodeFiltered<'_, G, NodeFilter<Ctx>, Ctx>
impl<G, Ctx> PortView for FilteredGraph<'_, G, NodeFilter<Ctx>, LinkFilter<Ctx>, Ctx>
where
G: PortView,
{
type Nodes<'a> = NodeFilteredIter<'a, G, Ctx, <G as PortView>::Nodes<'a>>
type Nodes<'a> = FilteredGraphIter<'a, G, Ctx, <G as PortView>::Nodes<'a>>
where
Self: 'a;

type Ports<'a> = NodeFilteredIter<'a, G, Ctx, <G as PortView>::Ports<'a>>
type Ports<'a> = FilteredGraphIter<'a, G, Ctx, <G as PortView>::Ports<'a>>
where
Self: 'a;

Expand All @@ -109,14 +171,14 @@ where

#[inline]
fn contains_node(&'_ self, node: NodeIndex) -> bool {
self.graph.contains_node(node) && (self.filter)(node, &self.context)
self.graph.contains_node(node) && (self.node_filter)(node, &self.context)
}

#[inline]
fn contains_port(&self, port: PortIndex) -> bool {
if self.graph.contains_port(port) {
let node = self.graph.port_node(port).unwrap();
(self.filter)(node, &self.context)
self.contains_node(node)
} else {
false
}
Expand All @@ -141,15 +203,15 @@ where
fn nodes_iter(&self) -> Self::Nodes<'_> {
self.graph
.nodes_iter()
.with_context(NodeFilterCtx::new(self.graph, self.filter, &self.context))
.with_context(self.as_context())
.filter_with_context(Self::node_filter)
}

#[inline]
fn ports_iter(&self) -> Self::Ports<'_> {
self.graph
.ports_iter()
.with_context(NodeFilterCtx::new(self.graph, self.filter, &self.context))
.with_context(self.as_context())
.filter_with_context(Self::port_filter)
}

Expand All @@ -173,68 +235,66 @@ where
}
}

impl<G, Ctx> LinkView for NodeFiltered<'_, G, NodeFilter<Ctx>, Ctx>
impl<G, Ctx> LinkView for FilteredGraph<'_, G, NodeFilter<Ctx>, LinkFilter<Ctx>, Ctx>
where
G: LinkView,
{
type LinkEndpoint = G::LinkEndpoint;

type Neighbours<'a> = NodeFilteredIter<'a, G, Ctx, <G as LinkView>::Neighbours<'a>>
type Neighbours<'a> = MapFilteredGraphIter<'a, G, Ctx, Self::NodeLinks<'a>, NodeIndex>
where
Self: 'a;

type NodeConnections<'a> = NodeFilteredIter<'a, G, Ctx, <G as LinkView>::NodeConnections<'a>>
type NodeConnections<'a> = FilteredGraphIter<'a, G, Ctx, <G as LinkView>::NodeConnections<'a>>
where
Self: 'a;

type NodeLinks<'a> = NodeFilteredIter<'a, G, Ctx, <G as LinkView>::NodeLinks<'a>>
type NodeLinks<'a> = FilteredGraphIter<'a, G, Ctx, <G as LinkView>::NodeLinks<'a>>
where
Self: 'a;

type PortLinks<'a> = NodeFilteredIter<'a, G, Ctx, <G as LinkView>::PortLinks<'a>>
type PortLinks<'a> = FilteredGraphIter<'a, G, Ctx, <G as LinkView>::PortLinks<'a>>
where
Self: 'a;

fn get_connections(&self, from: NodeIndex, to: NodeIndex) -> Self::NodeConnections<'_> {
self.graph
.get_connections(from, to)
.with_context(NodeFilterCtx::new(self.graph, self.filter, &self.context))
.with_context(self.as_context())
.filter_with_context(Self::link_filter)
}

fn port_links(&self, port: PortIndex) -> Self::PortLinks<'_> {
self.graph
.port_links(port)
.with_context(NodeFilterCtx::new(self.graph, self.filter, &self.context))
.with_context(self.as_context())
.filter_with_context(Self::link_filter)
}

fn links(&self, node: NodeIndex, direction: Direction) -> Self::NodeLinks<'_> {
self.graph
.links(node, direction)
.with_context(NodeFilterCtx::new(self.graph, self.filter, &self.context))
.with_context(self.as_context())
.filter_with_context(Self::link_filter)
}

fn all_links(&self, node: NodeIndex) -> Self::NodeLinks<'_> {
self.graph
.all_links(node)
.with_context(NodeFilterCtx::new(self.graph, self.filter, &self.context))
.with_context(self.as_context())
.filter_with_context(Self::link_filter)
}

fn neighbours(&self, node: NodeIndex, direction: Direction) -> Self::Neighbours<'_> {
self.graph
.neighbours(node, direction)
.with_context(NodeFilterCtx::new(self.graph, self.filter, &self.context))
.filter_with_context(Self::node_filter)
self.links(node, direction)
.with_context(self.as_context())
.map_with_context(|(_, p), ctx| ctx.graph.port_node(p).unwrap())
}

fn all_neighbours(&self, node: NodeIndex) -> Self::Neighbours<'_> {
self.graph
.all_neighbours(node)
.with_context(NodeFilterCtx::new(self.graph, self.filter, &self.context))
.filter_with_context(Self::node_filter)
self.all_links(node)
.with_context(self.as_context())
.map_with_context(|(_, p), ctx| ctx.graph.port_node(p).unwrap())
}

fn link_count(&self) -> usize {
Expand All @@ -244,31 +304,36 @@ where
}
}

impl<G, Ctx> MultiView for NodeFiltered<'_, G, NodeFilter<Ctx>, Ctx>
impl<G, Ctx> MultiView for FilteredGraph<'_, G, NodeFilter<Ctx>, LinkFilter<Ctx>, Ctx>
where
G: MultiView,
{
type NodeSubports<'a> = NodeFilteredIter<'a, G, Ctx, <G as MultiView>::NodeSubports<'a>>
type NodeSubports<'a> = FilteredGraphIter<'a, G, Ctx, <G as MultiView>::NodeSubports<'a>>
where
Self: 'a;

fn subports(&self, node: NodeIndex, direction: Direction) -> Self::NodeSubports<'_> {
self.graph
.subports(node, direction)
.with_context(NodeFilterCtx::new(self.graph, self.filter, &self.context))
.with_context(self.as_context())
.filter_with_context(Self::port_filter)
}

fn all_subports(&self, node: NodeIndex) -> Self::NodeSubports<'_> {
self.graph
.all_subports(node)
.with_context(NodeFilterCtx::new(self.graph, self.filter, &self.context))
.with_context(self.as_context())
.filter_with_context(Self::port_filter)
}

delegate! {
to self.graph {
fn subport_link(&self, subport: Self::LinkEndpoint) -> Option<Self::LinkEndpoint>;
fn subport_link(&self, subport: Self::LinkEndpoint) -> Option<Self::LinkEndpoint> {
if !(self.link_filter)(subport.into(), &self.context) {
return None;
}
let to = self.graph.subport_link(subport)?;
if !(self.link_filter)(to.into(), &self.context) {
return None;
}
Some(to)
}
}
Loading

0 comments on commit 6f6d627

Please sign in to comment.