diff --git a/tket2/src/lib.rs b/tket2/src/lib.rs index b3d801ff..395bfc6e 100644 --- a/tket2/src/lib.rs +++ b/tket2/src/lib.rs @@ -50,6 +50,8 @@ pub mod optimiser; pub mod passes; pub mod rewrite; pub mod serialize; +#[cfg(feature = "portmatching")] +pub mod static_circ; #[cfg(feature = "portmatching")] pub mod portmatching; diff --git a/tket2/src/portmatching.rs b/tket2/src/portmatching.rs index 29644b11..2aa3e02b 100644 --- a/tket2/src/portmatching.rs +++ b/tket2/src/portmatching.rs @@ -62,11 +62,11 @@ use itertools::Itertools; pub use matcher::{PatternMatch, PatternMatcher}; pub use pattern::CircuitPattern; +use crate::static_circ::MatchOp; use hugr::{ ops::{OpTag, OpTrait}, Node, Port, }; -use matcher::MatchOp; use thiserror::Error; use crate::{circuit::Circuit, utils::type_is_linear}; diff --git a/tket2/src/portmatching/matcher.rs b/tket2/src/portmatching/matcher.rs index 03b7e5d4..7c291a87 100644 --- a/tket2/src/portmatching/matcher.rs +++ b/tket2/src/portmatching/matcher.rs @@ -12,7 +12,6 @@ use hugr::hugr::views::sibling_subgraph::{ InvalidReplacement, InvalidSubgraph, InvalidSubgraphBoundary, TopoConvexChecker, }; use hugr::hugr::views::SiblingSubgraph; -use hugr::ops::{CustomOp, NamedOp, OpType}; use hugr::{HugrView, IncomingPort, Node, OutgoingPort, Port, PortIndex}; use itertools::Itertools; use portgraph::algorithms::ConvexChecker; @@ -20,59 +19,14 @@ use portmatching::{ automaton::{LineBuilder, ScopeAutomaton}, EdgeProperty, PatternID, }; -use smol_str::SmolStr; use thiserror::Error; use crate::{ circuit::Circuit, rewrite::{CircuitRewrite, Subcircuit}, + static_circ::MatchOp, }; -/// Matchable operations in a circuit. -#[derive( - Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, -)] -pub(crate) struct MatchOp { - /// The operation identifier - op_name: SmolStr, - /// The encoded operation, if necessary for comparisons. - /// - /// This as a temporary hack for comparing parametric operations, since - /// OpType doesn't implement Eq, Hash, or Ord. - encoded: Option>, -} - -impl From for MatchOp { - fn from(op: OpType) -> Self { - let op_name = op.name(); - let encoded = encode_op(op); - Self { op_name, encoded } - } -} - -/// Encode a unique identifier for an operation. -/// -/// Avoids encoding some data if we know the operation can be uniquely -/// identified by their name. -fn encode_op(op: OpType) -> Option> { - match op { - OpType::Module(_) => None, - OpType::CustomOp(op) => { - let opaque = match op { - CustomOp::Extension(ext_op) => ext_op.make_opaque(), - CustomOp::Opaque(opaque) => *opaque, - }; - let mut encoded: Vec = Vec::new(); - // Ignore irrelevant fields - rmp_serde::encode::write(&mut encoded, opaque.extension()).ok()?; - rmp_serde::encode::write(&mut encoded, opaque.name()).ok()?; - rmp_serde::encode::write(&mut encoded, opaque.args()).ok()?; - Some(encoded) - } - _ => rmp_serde::encode::to_vec(&op).ok(), - } -} - /// A convex pattern match in a circuit. /// /// The pattern is identified by a [`PatternID`] that can be used to retrieve the diff --git a/tket2/src/portmatching/pattern.rs b/tket2/src/portmatching/pattern.rs index b241bf77..684487d6 100644 --- a/tket2/src/portmatching/pattern.rs +++ b/tket2/src/portmatching/pattern.rs @@ -3,7 +3,8 @@ use hugr::{HugrView, IncomingPort}; use hugr::{Node, Port}; use itertools::Itertools; -use portmatching::{patterns::NoRootFound, HashMap, Pattern, SinglePatternMatcher}; +use portmatching::patterns::NoRootFound; +use portmatching::{HashMap, Pattern, SinglePatternMatcher}; use std::fmt::Debug; use thiserror::Error; diff --git a/tket2/src/static_circ.rs b/tket2/src/static_circ.rs new file mode 100644 index 00000000..d34692d4 --- /dev/null +++ b/tket2/src/static_circ.rs @@ -0,0 +1,123 @@ +//! A 2d array-like representation of simple quantum circuits. + +mod match_op; + +use hugr::{Direction, HugrView}; +pub(crate) use match_op::MatchOp; + +use derive_more::{From, Into}; + +use crate::{circuit::units::filter, Circuit}; + +/// A circuit with a fixed number of qubits numbered from 0 to `num_qubits - 1`. +pub(crate) struct StaticSizeCircuit { + /// All quantum operations on qubits. + qubit_ops: Vec>, +} + +impl StaticSizeCircuit { + /// Returns the number of qubits in the circuit. + #[allow(unused)] + pub fn qubit_count(&self) -> usize { + self.qubit_ops.len() + } + + /// Returns the operations on a given qubit. + #[allow(unused)] + pub fn qubit_ops(&self, qubit: usize) -> &[StaticOp] { + &self.qubit_ops[qubit] + } +} + +/// A qubit index within a `StaticSizeCircuit`. +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, From, Into)] +pub(crate) struct StaticQubitIndex(usize); + +/// An operation in a `StaticSizeCircuit`. +/// +/// Currently only support quantum operations without any classical IO. +#[derive(Debug, Clone)] +pub(crate) struct StaticOp { + #[allow(unused)] + op: MatchOp, + #[allow(unused)] + qubits: Vec, + // TODO: clbits +} + +impl TryFrom<&Circuit> for StaticSizeCircuit { + type Error = StaticSizeCircuitError; + + fn try_from(circuit: &Circuit) -> Result { + let mut qubit_ops = vec![Vec::new(); circuit.qubit_count()]; + for cmd in circuit.commands() { + let qubits = cmd + .units(Direction::Incoming) + .map(|unit| { + let Some((qb, _, _)) = filter::filter_qubit(unit) else { + return Err(StaticSizeCircuitError::NonQubitInput); + }; + Ok(qb) + }) + .collect::, _>>()?; + if cmd.units(Direction::Outgoing).count() != qubits.len() { + return Err(StaticSizeCircuitError::InvalidCircuit); + } + let op = StaticOp { + op: cmd.optype().clone().into(), + qubits: qubits + .iter() + .copied() + .map(|u| StaticQubitIndex(u.index())) + .collect(), + }; + for qb in qubits { + qubit_ops[qb.index()].push(op.clone()); + } + } + Ok(Self { qubit_ops }) + } +} + +use thiserror::Error; + +/// Errors that can occur when converting a `Circuit` to a `StaticSizeCircuit`. +#[derive(Debug, Error)] +pub enum StaticSizeCircuitError { + /// An input to a gate was not a qubit. + #[error("Only qubits are supported as inputs")] + NonQubitInput, + + /// The given tket2 circuit cannot be expressed as a StaticSizeCircuit. + #[error("The given tket2 circuit cannot be expressed as a StaticSizeCircuit")] + InvalidCircuit, +} + +#[cfg(test)] +mod tests { + use super::StaticSizeCircuit; + use crate::ops::Tk2Op; + use crate::utils::build_simple_circuit; + + #[test] + fn test_convert_to_static_size_circuit() { + // Create a circuit with 2 qubits, a CX gate, and two H gates + let circuit = build_simple_circuit(2, |circ| { + circ.append(Tk2Op::H, [0])?; + circ.append(Tk2Op::CX, [0, 1])?; + circ.append(Tk2Op::H, [1])?; + Ok(()) + }) + .unwrap(); + + // Convert the circuit to StaticSizeCircuit + let static_circuit: StaticSizeCircuit = (&circuit).try_into().unwrap(); + + // Check the conversion + assert_eq!(static_circuit.qubit_count(), 2); + assert_eq!(static_circuit.qubit_ops(0).len(), 2); // H gate on qubit 0 + dbg!(static_circuit.qubit_ops(0)); + assert_eq!(static_circuit.qubit_ops(1).len(), 2); // CX and H gate on qubit 1 + } +} diff --git a/tket2/src/static_circ/match_op.rs b/tket2/src/static_circ/match_op.rs new file mode 100644 index 00000000..c52f8345 --- /dev/null +++ b/tket2/src/static_circ/match_op.rs @@ -0,0 +1,47 @@ +use hugr::ops::{CustomOp, NamedOp, OpType}; +use smol_str::SmolStr; + +/// Matchable operations in a circuit. +#[derive( + Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, +)] +pub(crate) struct MatchOp { + /// The operation identifier + op_name: SmolStr, + /// The encoded operation, if necessary for comparisons. + /// + /// This as a temporary hack for comparing parametric operations, since + /// OpType doesn't implement Eq, Hash, or Ord. + encoded: Option>, +} + +impl From for MatchOp { + fn from(op: OpType) -> Self { + let op_name = op.name(); + let encoded = encode_op(op); + Self { op_name, encoded } + } +} + +/// Encode a unique identifier for an operation. +/// +/// Avoids encoding some data if we know the operation can be uniquely +/// identified by their name. +fn encode_op(op: OpType) -> Option> { + match op { + OpType::Module(_) => None, + OpType::CustomOp(op) => { + let opaque = match op { + CustomOp::Extension(ext_op) => ext_op.make_opaque(), + CustomOp::Opaque(opaque) => *opaque, + }; + let mut encoded: Vec = Vec::new(); + // Ignore irrelevant fields + rmp_serde::encode::write(&mut encoded, opaque.extension()).ok()?; + rmp_serde::encode::write(&mut encoded, opaque.name()).ok()?; + rmp_serde::encode::write(&mut encoded, opaque.args()).ok()?; + Some(encoded) + } + _ => rmp_serde::encode::to_vec(&op).ok(), + } +} diff --git a/tket2/tests/split.rs b/tket2/tests/split.rs new file mode 100644 index 00000000..6de46063 --- /dev/null +++ b/tket2/tests/split.rs @@ -0,0 +1,15 @@ +use rstest::{fixture, rstest}; +use tket2::{passes::CircuitChunks, serialize::TKETDecode, Circuit}; +use tket_json_rs::SerialCircuit; + +#[fixture] +fn cx_rz() -> Circuit { + let json = std::fs::read_to_string("../test_files/split_circ.json").unwrap(); + let ser: SerialCircuit = serde_json::from_str(&json).unwrap(); + ser.decode().unwrap() +} + +#[rstest] +fn split_circuit(cx_rz: Circuit) { + CircuitChunks::split_with_cost(&cx_rz, 1, |_| 0); +}