Skip to content

Commit

Permalink
feat: Add StaticSizeCircuit
Browse files Browse the repository at this point in the history
  • Loading branch information
lmondada committed Aug 19, 2024
1 parent b537ed8 commit 8a46ea8
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 49 deletions.
2 changes: 2 additions & 0 deletions tket2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion tket2/src/portmatching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
48 changes: 1 addition & 47 deletions tket2/src/portmatching/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,67 +12,21 @@ 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;
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<Vec<u8>>,
}

impl From<OpType> 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<Vec<u8>> {
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<u8> = 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
Expand Down
3 changes: 2 additions & 1 deletion tket2/src/portmatching/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
123 changes: 123 additions & 0 deletions tket2/src/static_circ.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<StaticOp>>,
}

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<StaticQubitIndex>,
// TODO: clbits
}

impl<H: HugrView> TryFrom<&Circuit<H>> for StaticSizeCircuit {
type Error = StaticSizeCircuitError;

fn try_from(circuit: &Circuit<H>) -> Result<Self, Self::Error> {
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::<Result<Vec<_>, _>>()?;
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
}
}
47 changes: 47 additions & 0 deletions tket2/src/static_circ/match_op.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<u8>>,
}

impl From<OpType> 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<Vec<u8>> {
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<u8> = 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(),
}
}
15 changes: 15 additions & 0 deletions tket2/tests/split.rs
Original file line number Diff line number Diff line change
@@ -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);
}

0 comments on commit 8a46ea8

Please sign in to comment.