Skip to content

Commit

Permalink
feat: Support copies in pattern matching (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
lmondada authored Sep 21, 2023
1 parent 2aeba3e commit f7e9237
Show file tree
Hide file tree
Showing 6 changed files with 306 additions and 50 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ itertools = { workspace = true }
petgraph = { version = "0.6.3", default-features = false }
serde_yaml = "0.9.22"
# portmatching = { version = "0.2.0", optional = true, features = ["serde"]}
portmatching = { optional = true, git = "https://github.com/lmondada/portmatching", rev = "c4ad0ec", features = [
portmatching = { optional = true, git = "https://github.com/lmondada/portmatching", rev = "738c91c", features = [
"serde",
] }
derive_more = "0.99.17"
Expand Down
8 changes: 3 additions & 5 deletions src/circuit/units.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ use std::iter::FusedIterator;

use hugr::hugr::CircuitUnit;
use hugr::ops::OpTrait;
use hugr::types::{EdgeKind, Type, TypeBound, TypeRow};
use hugr::types::{EdgeKind, Type, TypeRow};
use hugr::{Direction, Node, Port, Wire};

use crate::utils::type_is_linear;

use self::filter::UnitFilter;

use super::Circuit;
Expand Down Expand Up @@ -215,7 +217,3 @@ impl UnitLabeller for DefaultUnitLabeller {
}
}
}

fn type_is_linear(typ: &Type) -> bool {
!TypeBound::Copyable.contains(typ.least_upper_bound())
}
130 changes: 128 additions & 2 deletions src/portmatching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,137 @@ pub mod pattern;
#[cfg(feature = "pyo3")]
pub mod pyo3;

use itertools::Itertools;
pub use matcher::{PatternMatch, PatternMatcher};
pub use pattern::CircuitPattern;

use hugr::Port;
use hugr::{
ops::{OpTag, OpTrait},
Node, Port,
};
use matcher::MatchOp;
use thiserror::Error;

use crate::{circuit::Circuit, utils::type_is_linear};

type PEdge = (Port, Port);
type PNode = MatchOp;

/// An edge property in a circuit pattern.
///
/// Edges are
/// Edges are reversible if the edge type is linear.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
enum PEdge {
/// A "normal" edge between src and dst within a pattern.
InternalEdge {
src: Port,
dst: Port,
is_reversible: bool,
},
/// An edge from a copied input to src.
///
/// Edges from inputs are typically not matched as part of the pattern,
/// unless a single input is copied multiple times. In this case, an
/// InputEdge is used to link the source port to the (usually hidden)
/// copy node.
///
/// Input edges are always irreversible.
InputEdge { src: Port },
}

#[derive(Debug, Clone, Error)]
enum InvalidEdgeProperty {
/// The port is linked to multiple edges.
#[error("port {0:?} is linked to multiple edges")]
AmbiguousEdge(Port),
/// The port is not linked to any edge.
#[error("port {0:?} is not linked to any edge")]
NoLinkedEdge(Port),
/// The port does not have a type.
#[error("port {0:?} does not have a type")]
UntypedPort(Port),
}

impl PEdge {
fn try_from_port(
node: Node,
port: Port,
circ: &impl Circuit,
) -> Result<Self, InvalidEdgeProperty> {
let src = port;
let (dst_node, dst) = circ
.linked_ports(node, src)
.exactly_one()
.map_err(|mut e| {
if e.next().is_some() {
InvalidEdgeProperty::AmbiguousEdge(src)
} else {
InvalidEdgeProperty::NoLinkedEdge(src)
}
})?;
if circ.get_optype(dst_node).tag() == OpTag::Input {
return Ok(Self::InputEdge { src });
}
let port_type = circ
.get_optype(node)
.signature()
.get(src)
.cloned()
.ok_or(InvalidEdgeProperty::UntypedPort(src))?;
let is_reversible = type_is_linear(&port_type);
Ok(Self::InternalEdge {
src,
dst,
is_reversible,
})
}
}

impl portmatching::EdgeProperty for PEdge {
type OffsetID = Port;

fn reverse(&self) -> Option<Self> {
match *self {
Self::InternalEdge {
src,
dst,
is_reversible,
} => is_reversible.then_some(Self::InternalEdge {
src: dst,
dst: src,
is_reversible,
}),
Self::InputEdge { .. } => None,
}
}

fn offset_id(&self) -> Self::OffsetID {
match *self {
Self::InternalEdge { src, .. } => src,
Self::InputEdge { src, .. } => src,
}
}
}

/// A node in a pattern.
///
/// A node is either a real node in the HUGR graph or a hidden copy node
/// that is identified by its node and outgoing port.
///
/// A NodeID::CopyNode can only be found as a target of a PEdge::InputEdge
/// property. Furthermore, a NodeID::CopyNode never has a node property.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub(super) enum NodeID {
HugrNode(Node),
CopyNode(Node, Port),
}

impl From<Node> for NodeID {
fn from(node: Node) -> Self {
Self::HugrNode(node)
}
}
61 changes: 41 additions & 20 deletions src/portmatching/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ use std::{
path::{Path, PathBuf},
};

use super::{CircuitPattern, PEdge, PNode};
use super::{CircuitPattern, NodeID, PEdge, PNode};
use hugr::hugr::views::sibling_subgraph::{ConvexChecker, InvalidReplacement, InvalidSubgraph};
use hugr::{hugr::views::SiblingSubgraph, ops::OpType, Hugr, Node, Port};
use itertools::Itertools;
use portmatching::{
automaton::{LineBuilder, ScopeAutomaton},
PatternID,
EdgeProperty, PatternID,
};
use thiserror::Error;

Expand All @@ -30,6 +30,7 @@ use crate::{
/// Matchable operations in a circuit.
///
/// We currently support [`T2Op`] and a the HUGR load constant operation.
// TODO: Support OpType::Const, but blocked by use of F64 (Eq support required)
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
Expand Down Expand Up @@ -257,11 +258,11 @@ impl PatternMatcher {
) -> Vec<PatternMatch> {
self.automaton
.run(
root,
root.into(),
// Node weights (none)
validate_weighted_node(circ),
validate_circuit_node(circ),
// Check edge exist
validate_unweighted_edge(circ),
validate_circuit_edge(circ),
)
.filter_map(|pattern_id| {
handle_match_error(
Expand Down Expand Up @@ -380,28 +381,48 @@ impl From<InvalidSubgraph> for InvalidPatternMatch {
}
}

fn compatible_offsets((_, pout): &(Port, Port), (pin, _): &(Port, Port)) -> bool {
pout.direction() != pin.direction() && pout.index() == pin.index()
fn compatible_offsets(e1: &PEdge, e2: &PEdge) -> bool {
let PEdge::InternalEdge { dst: dst1, .. } = e1 else {
return false;
};
let src2 = e2.offset_id();
dst1.direction() != src2.direction() && dst1.index() == src2.index()
}

/// Check if an edge `e` is valid in a portgraph `g` without weights.
pub(crate) fn validate_unweighted_edge(
/// Returns a predicate checking that an edge at `src` satisfies `prop` in `circ`.
pub(super) fn validate_circuit_edge(
circ: &impl Circuit,
) -> impl for<'a> Fn(Node, &'a PEdge) -> Option<Node> + '_ {
move |src, &(src_port, tgt_port)| {
let (next_node, _) = circ
.linked_ports(src, src_port)
.find(|&(_, tgt)| tgt == tgt_port)?;
Some(next_node)
) -> impl for<'a> Fn(NodeID, &'a PEdge) -> Option<NodeID> + '_ {
move |src, &prop| {
let NodeID::HugrNode(src) = src else {
return None;
};
match prop {
PEdge::InternalEdge {
src: src_port,
dst: dst_port,
..
} => {
let (next_node, next_port) = circ.linked_ports(src, src_port).exactly_one().ok()?;
(dst_port == next_port).then_some(NodeID::HugrNode(next_node))
}
PEdge::InputEdge { src: src_port } => {
let (next_node, next_port) = circ.linked_ports(src, src_port).exactly_one().ok()?;
Some(NodeID::CopyNode(next_node, next_port))
}
}
}
}

/// Check if a node `n` is valid in a weighted portgraph `g`.
pub(crate) fn validate_weighted_node(
/// Returns a predicate checking that `node` satisfies `prop` in `circ`.
pub(crate) fn validate_circuit_node(
circ: &impl Circuit,
) -> impl for<'a> Fn(Node, &PNode) -> bool + '_ {
move |v, prop| {
let v_weight = MatchOp::try_from(circ.get_optype(v).clone());
) -> impl for<'a> Fn(NodeID, &PNode) -> bool + '_ {
move |node, prop| {
let NodeID::HugrNode(node) = node else {
return false;
};
let v_weight = MatchOp::try_from(circ.get_optype(node).clone());
v_weight.is_ok_and(|w| &w == prop)
}
}
Expand Down
Loading

0 comments on commit f7e9237

Please sign in to comment.