Skip to content

Commit

Permalink
feat: Circuit indexing scheme
Browse files Browse the repository at this point in the history
  • Loading branch information
lmondada committed Oct 17, 2024
1 parent 4495280 commit d02ccf8
Show file tree
Hide file tree
Showing 3 changed files with 598 additions and 0 deletions.
1 change: 1 addition & 0 deletions tket2/src/portmatching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
//! # }
//! ```
pub mod indexing;
// pub mod matcher;
// pub mod pattern;

Expand Down
216 changes: 216 additions & 0 deletions tket2/src/portmatching/indexing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
//! Indexing schemes for pattern matching with portmatching.
//!
//! Indexing schemes assign a unique variable name for every variable (i.e every
//! port and node in the hugr). This is used by the portmatcher to express
//! constraints to be checked whilst matching.
use std::collections::BTreeMap;

use derive_more::From;
use hugr::HugrView;
use itertools::Itertools;
use portmatching as pm;

use crate::Circuit;

mod path;
use path::HugrPath;

////////////////////////////////////////////////////////////////////////////////
//////////////////// Variable Naming scheme used for Hugrs /////////////////////
////////////////////////////////////////////////////////////////////////////////

/// Variables refer to either a node or a port in the hugr.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, From)]
pub enum HugrVariableID {
/// A variable that binds to a node.
Node(HugrNodeID),
/// A variable that binds to a port.
Port(HugrPortID),
}

impl HugrVariableID {
/// Resolve the variable ID to a unique value in the hugr given `bindings`.
///
/// Any non-incoming port variable and non-root node variable can be
/// resolved uniquely (if it exists) to a value. Calling `resolve` on an
/// incoming port variable will panic.
///
/// This assumes that the required incoming port bindings are in `bindings`.
fn resolve(&self, bindings: &HugrBindMap, hugr: &impl HugrView) -> Option<HugrVariableValue> {
match self {
HugrVariableID::Node(node) => node.resolve(bindings),
HugrVariableID::Port(port) => port.resolve(bindings, hugr),
}
}
}

/// The value of a variable in the indexing scheme, either a node or a port.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum HugrVariableValue {
/// The value of a HugrVariableID::Node variable.
Node(hugr::Node),
/// The value of a HugrVariableID::Port(HugrPortID::Incoming) variable.
IncomingPort(hugr::Node, hugr::IncomingPort),
/// The value of a HugrVariableID::Port(HugrPortID::Outgoing) variable.
OutgoingPort(hugr::Node, hugr::OutgoingPort),
}

impl HugrVariableValue {
fn node(&self) -> hugr::Node {
match *self {
HugrVariableValue::Node(node) => node,
HugrVariableValue::IncomingPort(node, _) => node,
HugrVariableValue::OutgoingPort(node, _) => node,
}
}
}

/// A map to store bindings for variables in a hugr.
pub type HugrBindMap = BTreeMap<HugrVariableID, HugrVariableValue>;

/// A port variable ID, given based on the unique IDs given to incoming ports.
///
/// - An incoming port ID is given by a path from a root node to an incoming port.
/// - An outgoing port ID is given by the opposite incoming port ID. This defines
/// the outgoing port uniquely as there is a one-to-many outgoing port to
/// incoming port relationship.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum HugrPortID {
/// A port variable that binds to an outgoing port.
Outgoing {
/// The path from the root node to an incoming port opposite.
opposite_port: HugrPath,
},
/// A port variable that binds to an incoming port.
Incoming {
/// The path from the root node to the incoming port.
path_from_root: HugrPath,
},
}

impl HugrPortID {
/// Resolve an outgoing port ID given `bindings` that specify the opposite port.
///
/// Returns a HugrVariableValue::OutgoingPort variant. Calling this on an
/// incoming port ID will result in a panic.
fn resolve(&self, bindings: &HugrBindMap, hugr: &impl HugrView) -> Option<HugrVariableValue> {
match self {
&HugrPortID::Outgoing { opposite_port } => {
let opp_var = HugrPortID::new_incoming(opposite_port).into();
let &HugrVariableValue::IncomingPort(opp_node, opp_port) =
bindings.get(&opp_var)?
else {
panic!("expected opposite port to be incoming");
};
// Currently, silently fail if there is more than one output
let (node, port) = hugr.single_linked_output(opp_node, opp_port)?;
HugrVariableValue::OutgoingPort(node, port).into()
}
HugrPortID::Incoming { .. } => {
panic!("Incoming port IDs do not resolve uniquely to a value")
}
}
}

fn new_incoming(path_from_root: HugrPath) -> Self {
Self::Incoming { path_from_root }
}
}

/// A node variable ID, given based on a unique ID given to one of its ports.
///
/// The root is a special case that must be handled separately, as it might not
/// have any ports.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum HugrNodeID {
/// Variable that binds to the root node.
Root,
/// Variable that binds to a non-root node.
NonRoot {
/// A port that is incident to the node.
incident_port: HugrPortID,
},
}

impl HugrNodeID {
/// Resolve a non-root node ID to a node in the hugr given `bindings`.
///
/// Root IDs cannot be resolved uniquely to a value. Calling this on a
/// HugrNodeID::Root will result in a panic.
fn resolve(&self, bindings: &HugrBindMap) -> Option<HugrVariableValue> {
match self {
HugrNodeID::Root => panic!("Root node IDs do not resolve uniquely to a value"),
HugrNodeID::NonRoot { incident_port } => {
let node = bindings.get(&(*incident_port).into())?.node();
HugrVariableValue::Node(node).into()
}
}
}
}

////////////////////////////////////////////////////////////////////////////////
////////////// Indexing scheme: resolve variable IDs to values /////////////////
////////////////////////////////////////////////////////////////////////////////

/// An indexing scheme for hugrs that does not handle hierarchy.
#[derive(Clone, Debug, Default)]
pub struct FlatHugrIndexingScheme;

impl pm::IndexingScheme for FlatHugrIndexingScheme {
type BindMap = HugrBindMap;

fn required_bindings(&self, key: &HugrVariableID) -> Vec<HugrVariableID> {
match key {
HugrVariableID::Node(node) => match node {
// The root node can be bound to any node.
HugrNodeID::Root => vec![],
// Otherwise require the incident port to be bound.
&HugrNodeID::NonRoot { incident_port } => vec![incident_port.into()],
},
HugrVariableID::Port(port) => match port {
&HugrPortID::Outgoing { opposite_port } => {
// Require the opposite port to be bound.
let port_id = HugrPortID::new_incoming(opposite_port);
vec![port_id.into()]
}
HugrPortID::Incoming { path_from_root } => {
// Require the parent of the incoming port to be bound.
if let Some(parent_path) = path_from_root.parent() {
let port_id = HugrPortID::new_incoming(parent_path);
vec![port_id.into()]
} else {
vec![HugrNodeID::Root.into()]
}
}
},
}
}
}

impl<H: HugrView> pm::IndexedData for Circuit<H> {
type IndexingScheme = FlatHugrIndexingScheme;

fn list_bind_options(
&self,
key: &HugrVariableID,
known_bindings: &HugrBindMap,
) -> Vec<HugrVariableValue> {
match key {
HugrVariableID::Node(HugrNodeID::Root) => {
// Every hugr node is a valid binding for the root node.
let nodes = self.commands().map(|cmd| cmd.node());
nodes.map(HugrVariableValue::Node).map_into().collect()
}
HugrVariableID::Port(HugrPortID::Incoming { path_from_root }) => {
let ports = path_from_root.list_bind_options(known_bindings, self.hugr());
ports
.into_iter()
.map(|(n, p)| HugrVariableValue::IncomingPort(n, p))
.collect()
}
// Otherwise, resolves uniquely
key @ _ => Vec::from_iter(key.resolve(known_bindings, self.hugr())),
}
}
}
Loading

0 comments on commit d02ccf8

Please sign in to comment.