From dcc562d27c2b436000a1a4e673bfec680d7c7d95 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Fri, 6 Sep 2024 11:35:42 +0100 Subject: [PATCH 1/4] feat: dataflow builder methods for angle ops (#596) --- tket2/src/extension/angle.rs | 109 ++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/tket2/src/extension/angle.rs b/tket2/src/extension/angle.rs index 315c7f87..44da13aa 100644 --- a/tket2/src/extension/angle.rs +++ b/tket2/src/extension/angle.rs @@ -1,9 +1,10 @@ +use hugr::builder::{BuildError, Dataflow}; use hugr::extension::prelude::{sum_with_error, BOOL_T, USIZE_T}; use hugr::extension::simple_op::{MakeOpDef, MakeRegisteredOp}; use hugr::extension::{ExtensionId, ExtensionSet, Version}; use hugr::ops::constant::{downcast_equal_consts, CustomConst}; use hugr::std_extensions::arithmetic::float_types::FLOAT64_TYPE; -use hugr::type_row; +use hugr::{type_row, Wire}; use hugr::{ types::{ConstTypeError, CustomType, Signature, Type, TypeBound}, Extension, @@ -261,11 +262,94 @@ pub(super) fn add_to_extension(extension: &mut Extension) { AngleOp::load_all_ops(extension).expect("add fail"); } +/// An extension trait for [Dataflow] providing methods to add +/// "tket2.angle" operations. +pub trait AngleOpBuilder: Dataflow { + /// Add a "tket2.angle.atrunc" op. + fn add_atrunc(&mut self, angle: Wire, log_denom: Wire) -> Result { + Ok(self + .add_dataflow_op(AngleOp::atrunc, [angle, log_denom])? + .out_wire(0)) + } + /// Add a "tket2.angle.aadd" op. + fn add_aadd(&mut self, angle1: Wire, angle2: Wire) -> Result { + Ok(self + .add_dataflow_op(AngleOp::aadd, [angle1, angle2])? + .out_wire(0)) + } + + /// Add a "tket2.angle.asub" op. + fn add_asub(&mut self, angle1: Wire, angle2: Wire) -> Result { + Ok(self + .add_dataflow_op(AngleOp::asub, [angle1, angle2])? + .out_wire(0)) + } + + /// Add a "tket2.angle.aneg" op. + fn add_aneg(&mut self, angle: Wire) -> Result { + Ok(self.add_dataflow_op(AngleOp::aneg, [angle])?.out_wire(0)) + } + + /// Add a "tket2.angle.anew" op. + fn add_anew(&mut self, numerator: Wire, log_denominator: Wire) -> Result { + Ok(self + .add_dataflow_op(AngleOp::anew, [numerator, log_denominator])? + .out_wire(0)) + } + + /// Add a "tket2.angle.aparts" op. + fn add_aparts(&mut self, angle: Wire) -> Result<[Wire; 2], BuildError> { + Ok(self + .add_dataflow_op(AngleOp::aparts, [angle])? + .outputs_arr()) + } + + /// Add a "tket2.angle.afromrad" op. + fn add_afromrad(&mut self, log_denominator: Wire, radians: Wire) -> Result { + Ok(self + .add_dataflow_op(AngleOp::afromrad, [log_denominator, radians])? + .out_wire(0)) + } + + /// Add a "tket2.angle.atorad" op. + fn add_atorad(&mut self, angle: Wire) -> Result { + Ok(self.add_dataflow_op(AngleOp::atorad, [angle])?.out_wire(0)) + } + + /// Add a "tket2.angle.aeq" op. + fn add_aeq(&mut self, angle1: Wire, angle2: Wire) -> Result { + Ok(self + .add_dataflow_op(AngleOp::aeq, [angle1, angle2])? + .out_wire(0)) + } + + /// Add a "tket2.angle.amul" op. + fn add_amul(&mut self, angle: Wire, scalar: Wire) -> Result { + Ok(self + .add_dataflow_op(AngleOp::amul, [angle, scalar])? + .out_wire(0)) + } + + /// Add a "tket2.angle.adiv" op. + fn add_adiv(&mut self, angle: Wire, scalar: Wire) -> Result { + Ok(self + .add_dataflow_op(AngleOp::adiv, [angle, scalar])? + .out_wire(0)) + } +} + +impl AngleOpBuilder for D {} + #[cfg(test)] mod test { - use hugr::ops::OpType; + use hugr::{ + builder::{DFGBuilder, DataflowHugr}, + ops::OpType, + }; use strum::IntoEnumIterator; + use crate::extension::REGISTRY; + use super::*; #[test] @@ -306,4 +390,25 @@ mod test { assert_eq!(optype.cast(), Some(op)); } } + + #[test] + fn test_builder() { + let mut builder = + DFGBuilder::new(Signature::new(vec![ANGLE_TYPE, USIZE_T], vec![BOOL_T])).unwrap(); + + let [angle, scalar] = builder.input_wires_arr(); + let radians = builder.add_atorad(angle).unwrap(); + let angle = builder.add_afromrad(scalar, radians).unwrap(); + let angle = builder.add_amul(angle, scalar).unwrap(); + let angle = builder.add_adiv(angle, scalar).unwrap(); + let angle = builder.add_aadd(angle, angle).unwrap(); + let angle = builder.add_asub(angle, angle).unwrap(); + let [num, log_denom] = builder.add_aparts(angle).unwrap(); + let _angle_sum = builder.add_anew(num, log_denom).unwrap(); + let angle = builder.add_aneg(angle).unwrap(); + let angle = builder.add_atrunc(angle, log_denom).unwrap(); + let bool = builder.add_aeq(angle, angle).unwrap(); + + let _hugr = builder.finish_hugr_with_outputs([bool], ®ISTRY).unwrap(); + } } From 7ac02063365e7bbf56a0d3a94145bf394438381d Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Fri, 6 Sep 2024 14:22:18 +0100 Subject: [PATCH 2/4] feat: lowering tk2ops -> hseriesops (#579) Closes #490 Using definitions in hqslib1 https://github.com/CQCL/pytket-quantinuum/blob/main/pytket/extensions/quantinuum/backends/hqslib1.inc --- Cargo.lock | 26 ++- Cargo.toml | 4 +- tket2-hseries/Cargo.toml | 1 + tket2-hseries/src/extension/hseries.rs | 203 ++++++++++++++-- tket2-hseries/src/extension/hseries/lower.rs | 231 +++++++++++++++++++ tket2-hseries/src/lib.rs | 2 +- tket2/src/passes.rs | 1 + 7 files changed, 434 insertions(+), 34 deletions(-) create mode 100644 tket2-hseries/src/extension/hseries/lower.rs diff --git a/Cargo.lock b/Cargo.lock index 75265692..88128dda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -473,6 +473,17 @@ dependencies = [ "syn 2.0.71", ] +[[package]] +name = "delegate" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5060bb0febb73fa907273f8a7ed17ab4bf831d585eac835b28ec24a1e2460956" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "deranged" version = "0.3.11" @@ -741,9 +752,9 @@ dependencies = [ [[package]] name = "hugr" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41def210e277099199acba8a9f5d74938612607a0309cf695dc147b0f5870c74" +checksum = "34ee4f66c9add4abc4b1ed5895b8f4ca1ee3727a0aacbb011696bb0a5946be01" dependencies = [ "hugr-core", "hugr-passes", @@ -751,14 +762,14 @@ dependencies = [ [[package]] name = "hugr-core" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce2072c663e82ec9cc43282ff555c90fa139f3e5396a693f2b2a14d72e694e0" +checksum = "b9cadea7900319ff43c7ee211a28e7de26a3d3f3b1d1bdd4c3de3dfee1199d3e" dependencies = [ "bitvec", "cgmath", "context-iterators", - "delegate 0.12.0", + "delegate 0.13.0", "derive_more 1.0.0", "downcast-rs", "enum_dispatch", @@ -782,9 +793,9 @@ dependencies = [ [[package]] name = "hugr-passes" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c47d41300b40e1dfd53f17382b2fe4dc815c9707d7507dc68846562ea7ee78b9" +checksum = "6636bd4e828751880354ea8000bcc0be0d753aed9062783b72c78b668b813aa8" dependencies = [ "hugr-core", "itertools 0.13.0", @@ -1842,6 +1853,7 @@ dependencies = [ "itertools 0.13.0", "lazy_static", "petgraph", + "rstest", "serde", "serde_json", "smol_str", diff --git a/Cargo.toml b/Cargo.toml index dc4a5db4..f1a178fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,8 +26,8 @@ missing_docs = "warn" [workspace.dependencies] # Make sure to run `just recompile-eccs` if the hugr serialisation format changes. -hugr = "0.12.0" -hugr-core = "0.9.0" +hugr = "0.12.1" +hugr-core = "0.9.1" portgraph = "0.12" pyo3 = "0.21.2" itertools = "0.13.0" diff --git a/tket2-hseries/Cargo.toml b/tket2-hseries/Cargo.toml index dd3ec6be..5534a985 100644 --- a/tket2-hseries/Cargo.toml +++ b/tket2-hseries/Cargo.toml @@ -28,6 +28,7 @@ itertools.workspace = true [dev-dependencies] cool_asserts.workspace = true petgraph.workspace = true +rstest.workspace = true [lints] workspace = true diff --git a/tket2-hseries/src/extension/hseries.rs b/tket2-hseries/src/extension/hseries.rs index 96ec8145..f1d7a898 100644 --- a/tket2-hseries/src/extension/hseries.rs +++ b/tket2-hseries/src/extension/hseries.rs @@ -9,9 +9,13 @@ use hugr::{ extension::{ prelude::{BOOL_T, QB_T}, simple_op::{try_from_name, MakeOpDef, MakeRegisteredOp}, - ExtensionId, ExtensionRegistry, OpDef, SignatureFunc, Version, PRELUDE, + ExtensionId, ExtensionRegistry, ExtensionSet, OpDef, SignatureFunc, Version, PRELUDE, + }, + ops::Value, + std_extensions::arithmetic::{ + float_ops::FloatOps, + float_types::{ConstF64, EXTENSION as FLOAT_TYPES, FLOAT64_TYPE}, }, - std_extensions::arithmetic::float_types::{EXTENSION as FLOAT_TYPES, FLOAT64_TYPE}, type_row, types::Signature, Extension, Wire, @@ -24,6 +28,10 @@ use crate::extension::futures; use super::futures::future_type; +mod lower; +use lower::pi_mul_f64; +pub use lower::{check_lowered, lower_tk2_op}; + /// The "tket2.hseries" extension id. pub const EXTENSION_ID: ExtensionId = ExtensionId::new_unchecked("tket2.hseries"); /// The "tket2.hseries" extension version. @@ -32,7 +40,12 @@ pub const EXTENSION_VERSION: Version = Version::new(0, 1, 0); lazy_static! { /// The "tket2.hseries" extension. pub static ref EXTENSION: Extension = { - let mut ext = Extension::new(EXTENSION_ID, EXTENSION_VERSION); + let mut ext = Extension::new(EXTENSION_ID, EXTENSION_VERSION).with_reqs(ExtensionSet::from_iter([ + futures::EXTENSION.name(), + PRELUDE.name(), + FLOAT_TYPES.name(), + tket2::extension::angle::ANGLE_EXTENSION.name(), + ].into_iter().cloned())); HSeriesOp::load_all_ops(&mut ext).unwrap(); ext }; @@ -40,10 +53,11 @@ lazy_static! { /// Extension registry including the "tket2.hseries" extension and /// dependencies. pub static ref REGISTRY: ExtensionRegistry = ExtensionRegistry::try_new([ + EXTENSION.to_owned(), futures::EXTENSION.to_owned(), PRELUDE.to_owned(), - EXTENSION.to_owned(), FLOAT_TYPES.to_owned(), + tket2::extension::angle::ANGLE_EXTENSION.to_owned(), ]).unwrap(); } @@ -133,11 +147,7 @@ pub trait HSeriesOpBuilder: Dataflow { /// Add a "tket2.hseries.Reset" op. fn add_reset(&mut self, qb: Wire) -> Result { - Ok(self - .add_dataflow_op(HSeriesOp::Reset, [qb])? - .outputs() - .next() - .unwrap()) + Ok(self.add_dataflow_op(HSeriesOp::Reset, [qb])?.out_wire(0)) } /// Add a "tket2.hseries.ZZMax" op. @@ -158,27 +168,19 @@ pub trait HSeriesOpBuilder: Dataflow { fn add_phased_x(&mut self, qb: Wire, angle1: Wire, angle2: Wire) -> Result { Ok(self .add_dataflow_op(HSeriesOp::PhasedX, [qb, angle1, angle2])? - .outputs() - .next() - .unwrap()) + .out_wire(0)) } /// Add a "tket2.hseries.Rz" op. fn add_rz(&mut self, qb: Wire, angle: Wire) -> Result { Ok(self .add_dataflow_op(HSeriesOp::Rz, [qb, angle])? - .outputs() - .next() - .unwrap()) + .out_wire(0)) } /// Add a "tket2.hseries.QAlloc" op. fn add_qalloc(&mut self) -> Result { - Ok(self - .add_dataflow_op(HSeriesOp::QAlloc, [])? - .outputs() - .next() - .unwrap()) + Ok(self.add_dataflow_op(HSeriesOp::QAlloc, [])?.out_wire(0)) } /// Add a "tket2.hseries.QFree" op. @@ -186,6 +188,161 @@ pub trait HSeriesOpBuilder: Dataflow { self.add_dataflow_op(HSeriesOp::QFree, [qb])?; Ok(()) } + + /// Build a hadamard gate in terms of HSeries primitives. + fn build_h(&mut self, qb: Wire) -> Result { + let pi = pi_mul_f64(self, 1.0); + let pi_2 = pi_mul_f64(self, 0.5); + let pi_minus_2 = pi_mul_f64(self, -0.5); + + let q = self.add_phased_x(qb, pi_2, pi_minus_2)?; + self.add_rz(q, pi) + } + + /// Build an X gate in terms of HSeries primitives. + fn build_x(&mut self, qb: Wire) -> Result { + let pi = pi_mul_f64(self, 1.0); + let zero = pi_mul_f64(self, 0.0); + self.add_phased_x(qb, pi, zero) + } + + /// Build a Y gate in terms of HSeries primitives. + fn build_y(&mut self, qb: Wire) -> Result { + let pi = pi_mul_f64(self, 1.0); + let pi_2 = pi_mul_f64(self, 0.5); + self.add_phased_x(qb, pi, pi_2) + } + + /// Build a Z gate in terms of HSeries primitives. + fn build_z(&mut self, qb: Wire) -> Result { + let pi = pi_mul_f64(self, 1.0); + self.add_rz(qb, pi) + } + + /// Build an S gate in terms of HSeries primitives. + fn build_s(&mut self, qb: Wire) -> Result { + let pi_2 = pi_mul_f64(self, 0.5); + self.add_rz(qb, pi_2) + } + + /// Build an Sdg gate in terms of HSeries primitives. + fn build_sdg(&mut self, qb: Wire) -> Result { + let pi_minus_2 = pi_mul_f64(self, -0.5); + self.add_rz(qb, pi_minus_2) + } + + /// Build a T gate in terms of HSeries primitives. + fn build_t(&mut self, qb: Wire) -> Result { + let pi_4 = pi_mul_f64(self, 0.25); + self.add_rz(qb, pi_4) + } + + /// Build a Tdg gate in terms of HSeries primitives. + fn build_tdg(&mut self, qb: Wire) -> Result { + let pi_minus_4 = pi_mul_f64(self, -0.25); + self.add_rz(qb, pi_minus_4) + } + + /// Build a CNOT gate in terms of HSeries primitives. + fn build_cx(&mut self, c: Wire, t: Wire) -> Result<[Wire; 2], BuildError> { + let pi = pi_mul_f64(self, 1.0); + let pi_2 = pi_mul_f64(self, 0.5); + let pi_minus_2 = pi_mul_f64(self, -0.5); + + let t = self.add_phased_x(t, pi_minus_2, pi_2)?; + let [c, t] = self.add_zz_max(c, t)?; + let c = self.add_rz(c, pi_minus_2)?; + let t = self.add_phased_x(t, pi_2, pi)?; + let t = self.add_rz(t, pi_minus_2)?; + + Ok([c, t]) + } + + /// Build a CY gate in terms of HSeries primitives. + fn build_cy(&mut self, a: Wire, b: Wire) -> Result<[Wire; 2], BuildError> { + let pi_2 = pi_mul_f64(self, 0.5); + let pi_minus_2 = pi_mul_f64(self, -0.5); + let zero = pi_mul_f64(self, 0.0); + + let a = self.add_phased_x(a, pi_minus_2, zero)?; + let [a, b] = self.add_zz_max(a, b)?; + let a = self.add_rz(a, pi_2)?; + let b = self.add_phased_x(b, pi_2, pi_2)?; + let b = self.add_rz(b, pi_minus_2)?; + Ok([a, b]) + } + + /// Build a CZ gate in terms of HSeries primitives. + fn build_cz(&mut self, a: Wire, b: Wire) -> Result<[Wire; 2], BuildError> { + let pi = pi_mul_f64(self, 1.0); + let pi_2 = pi_mul_f64(self, 0.5); + let pi_minus_2 = pi_mul_f64(self, -0.5); + + let a = self.add_phased_x(a, pi, pi)?; + let [a, b] = self.add_zz_max(a, b)?; + let a = self.add_phased_x(a, pi, pi_2)?; + let b = self.add_rz(b, pi_2)?; + let a = self.add_rz(a, pi_minus_2)?; + + Ok([a, b]) + } + + /// Build a RX gate in terms of HSeries primitives. + fn build_rx(&mut self, qb: Wire, theta: Wire) -> Result { + let zero = pi_mul_f64(self, 0.0); + self.add_phased_x(qb, theta, zero) + } + + /// Build a RY gate in terms of HSeries primitives. + fn build_ry(&mut self, qb: Wire, theta: Wire) -> Result { + let pi_2 = pi_mul_f64(self, 0.5); + self.add_phased_x(qb, theta, pi_2) + } + + /// Build a CRZ gate in terms of HSeries primitives. + fn build_crz(&mut self, a: Wire, b: Wire, lambda: Wire) -> Result<[Wire; 2], BuildError> { + let two = self.add_load_const(Value::from(ConstF64::new(2.0))); + let lambda_2 = self + .add_dataflow_op(FloatOps::fdiv, [lambda, two])? + .out_wire(0); + let lambda_minus_2 = self + .add_dataflow_op(FloatOps::fneg, [lambda_2])? + .out_wire(0); + + let [a, b] = self.add_zz_phase(a, b, lambda_minus_2)?; + let b = self.add_rz(b, lambda_2)?; + Ok([a, b]) + } + + /// Build a Toffoli (CCX) gate in terms of HSeries primitives. + fn build_toffoli(&mut self, a: Wire, b: Wire, c: Wire) -> Result<[Wire; 3], BuildError> { + let pi = pi_mul_f64(self, 1.0); + let pi_2 = pi_mul_f64(self, 0.5); + let pi_minus_2 = pi_mul_f64(self, -0.5); + let pi_4 = pi_mul_f64(self, 0.25); + let pi_minus_4 = pi_mul_f64(self, -0.25); + let pi_minus_3_4 = pi_mul_f64(self, -0.75); + let zero = pi_mul_f64(self, 0.0); + + let c = self.add_phased_x(c, pi, pi)?; + let [b, c] = self.add_zz_max(b, c)?; + let c = self.add_phased_x(c, pi_4, pi_minus_2)?; + let [a, c] = self.add_zz_max(a, c)?; + let c = self.add_phased_x(c, pi_minus_4, zero)?; + let [b, c] = self.add_zz_max(b, c)?; + let b = self.add_phased_x(b, pi_minus_2, pi_4)?; + let c = self.add_phased_x(c, pi_4, pi_2)?; + let [a, c] = self.add_zz_max(a, c)?; + let [a, b] = self.add_zz_max(a, b)?; + let c = self.add_phased_x(c, pi_minus_3_4, zero)?; + let b = self.add_phased_x(b, pi_4, pi_4)?; + let [a, b] = self.add_zz_max(a, b)?; + let a = self.add_rz(a, pi_4)?; + let b = self.add_phased_x(b, pi_minus_2, pi_4)?; + let b = self.add_rz(b, pi_4)?; + + Ok([a, b, c]) + } } impl HSeriesOpBuilder for D {} @@ -196,10 +353,8 @@ mod test { use cool_asserts::assert_matches; use futures::FutureOpBuilder as _; - use hugr::{ - builder::{DataflowHugr, FunctionBuilder}, - ops::{NamedOp, OpType}, - }; + use hugr::builder::{DataflowHugr, FunctionBuilder}; + use hugr::ops::{NamedOp, OpType}; use strum::IntoEnumIterator as _; use super::*; diff --git a/tket2-hseries/src/extension/hseries/lower.rs b/tket2-hseries/src/extension/hseries/lower.rs new file mode 100644 index 00000000..a7f755cb --- /dev/null +++ b/tket2-hseries/src/extension/hseries/lower.rs @@ -0,0 +1,231 @@ +use std::collections::HashMap; + +use hugr::{ + builder::{BuildError, DFGBuilder, Dataflow, DataflowHugr}, + hugr::{hugrmut::HugrMut, HugrError}, + ops::{self, DataflowOpTrait}, + std_extensions::arithmetic::float_types::ConstF64, + types::Signature, + Hugr, HugrView, Node, Wire, +}; +use strum::IntoEnumIterator; +use thiserror::Error; +use tket2::{extension::angle::AngleOpBuilder, Tk2Op}; + +use crate::extension::hseries::{HSeriesOp, HSeriesOpBuilder}; + +use super::REGISTRY; + +pub(super) fn pi_mul_f64(builder: &mut T, multiplier: f64) -> Wire { + const_f64(builder, multiplier * std::f64::consts::PI) +} + +fn const_f64(builder: &mut T, value: f64) -> Wire { + builder.add_load_const(ops::Const::new(ConstF64::new(value).into())) +} + +/// Errors produced by lowering [Tk2Op]s. +#[derive(Debug, Error)] +pub enum LowerTk2Error { + #[error("Error when building the circuit: {0}")] + BuildError(#[from] BuildError), + + #[error("Unrecognised operation: {0:?} with {1} inputs")] + UnknownOp(Tk2Op, usize), + + #[error("Error when replacing op: {0}")] + OpReplacement(#[from] HugrError), + + #[error("Error when lowering ops: {0}")] + CircuitReplacement(#[from] hugr::algorithms::lower::LowerError), +} + +fn op_to_hugr(op: Tk2Op) -> Result { + let sig = op.into_extension_op().signature(); + let sig = Signature::new(sig.input, sig.output); // ignore extension delta + let mut b = DFGBuilder::new(sig)?; + let inputs: Vec<_> = b.input_wires().collect(); + + let outputs = match (op, inputs.as_slice()) { + (Tk2Op::H, [q]) => vec![b.build_h(*q)?], + (Tk2Op::X, [q]) => vec![b.build_x(*q)?], + (Tk2Op::Y, [q]) => vec![b.build_y(*q)?], + (Tk2Op::Z, [q]) => vec![b.build_z(*q)?], + (Tk2Op::S, [q]) => vec![b.build_s(*q)?], + (Tk2Op::Sdg, [q]) => vec![b.build_sdg(*q)?], + (Tk2Op::T, [q]) => vec![b.build_t(*q)?], + (Tk2Op::Tdg, [q]) => vec![b.build_tdg(*q)?], + (Tk2Op::CX, [c, t]) => b.build_cx(*c, *t)?.into(), + (Tk2Op::CY, [c, t]) => b.build_cy(*c, *t)?.into(), + (Tk2Op::CZ, [c, t]) => b.build_cz(*c, *t)?.into(), + (Tk2Op::Rx, [q, angle]) => { + let float = b.add_atorad(*angle)?; + vec![b.build_rx(*q, float)?] + } + (Tk2Op::Ry, [q, angle]) => { + let float = b.add_atorad(*angle)?; + vec![b.build_ry(*q, float)?] + } + (Tk2Op::Rz, [q, angle]) => { + let float = b.add_atorad(*angle)?; + vec![b.add_rz(*q, float)?] + } + (Tk2Op::CRz, [c, t, angle]) => { + let float = b.add_atorad(*angle)?; + b.build_crz(*c, *t, float)?.into() + } + (Tk2Op::Toffoli, [a, b_, c]) => b.build_toffoli(*a, *b_, *c)?.into(), + _ => return Err(LowerTk2Error::UnknownOp(op, inputs.len())), // non-exhaustive + }; + Ok(b.finish_hugr_with_outputs(outputs, ®ISTRY)?) +} + +/// Lower `Tk2Op` operations to `HSeriesOp` operations. +pub fn lower_tk2_op(hugr: &mut impl HugrMut) -> Result, LowerTk2Error> { + let replaced_nodes = lower_direct(hugr)?; + let mut hugr_map: HashMap = HashMap::new(); + for op in Tk2Op::iter() { + match op_to_hugr(op) { + Ok(h) => hugr_map.insert(op, h), + // filter out unknown ops, includes those covered by direct lowering + Err(LowerTk2Error::UnknownOp(_, _)) => continue, + Err(e) => return Err(e), + }; + } + + let lowered_nodes = hugr::algorithms::lower_ops(hugr, |op| hugr_map.get(&op.cast()?).cloned())?; + + Ok([replaced_nodes, lowered_nodes].concat()) +} + +fn lower_direct(hugr: &mut impl HugrMut) -> Result, LowerTk2Error> { + Ok(hugr::algorithms::replace_many_ops(hugr, |op| { + let op: Tk2Op = op.cast()?; + Some(match op { + Tk2Op::QAlloc => HSeriesOp::QAlloc, + Tk2Op::QFree => HSeriesOp::QFree, + Tk2Op::Reset => HSeriesOp::Reset, + Tk2Op::Measure => HSeriesOp::Measure, + _ => return None, + }) + })? + .into_iter() + .map(|(node, _)| node) + .collect()) +} + +/// Check there are no "tket2.quantum" ops left in the HUGR. +/// +/// # Errors +/// Returns vector of nodes that are not lowered. +pub fn check_lowered(hugr: &impl HugrView) -> Result<(), Vec> { + let unlowered: Vec = hugr + .nodes() + .filter_map(|node| { + let optype = hugr.get_optype(node); + optype.as_extension_op().and_then(|ext| { + (ext.def().extension() == &tket2::extension::TKET2_EXTENSION_ID).then_some(node) + }) + }) + .collect(); + + if unlowered.is_empty() { + Ok(()) + } else { + Err(unlowered) + } +} + +#[cfg(test)] +mod test { + use hugr::{builder::FunctionBuilder, type_row, HugrView}; + use tket2::{extension::angle::ANGLE_TYPE, Circuit}; + + use super::*; + use rstest::rstest; + + #[test] + fn test_lower_direct() { + let mut b = FunctionBuilder::new("circuit", Signature::new_endo(type_row![])).unwrap(); + let [q] = b.add_dataflow_op(Tk2Op::QAlloc, []).unwrap().outputs_arr(); + let [q] = b.add_dataflow_op(Tk2Op::Reset, [q]).unwrap().outputs_arr(); + let [q, _] = b + .add_dataflow_op(Tk2Op::Measure, [q]) + .unwrap() + .outputs_arr(); + b.add_dataflow_op(Tk2Op::QFree, [q]).unwrap(); + let mut h = b.finish_hugr_with_outputs([], ®ISTRY).unwrap(); + + let lowered = lower_direct(&mut h).unwrap(); + assert_eq!(lowered.len(), 4); + let circ = Circuit::new(&h, h.root()); + let ops: Vec = circ + .commands() + .map(|com| com.optype().cast().unwrap()) + .collect(); + assert_eq!( + ops, + vec![ + HSeriesOp::QAlloc, + HSeriesOp::Reset, + HSeriesOp::Measure, + HSeriesOp::QFree + ] + ); + assert_eq!(check_lowered(&h), Ok(())); + } + + #[rstest] + #[case(Tk2Op::H, Some(vec![HSeriesOp::PhasedX, HSeriesOp::Rz]))] + #[case(Tk2Op::X, Some(vec![HSeriesOp::PhasedX]))] + #[case(Tk2Op::Y, Some(vec![HSeriesOp::PhasedX]))] + #[case(Tk2Op::Z, Some(vec![HSeriesOp::Rz]))] + #[case(Tk2Op::S, Some(vec![HSeriesOp::Rz]))] + #[case(Tk2Op::Sdg, Some(vec![HSeriesOp::Rz]))] + #[case(Tk2Op::T, Some(vec![HSeriesOp::Rz]))] + #[case(Tk2Op::Tdg, Some(vec![HSeriesOp::Rz]))] + #[case(Tk2Op::Rx, Some(vec![HSeriesOp::PhasedX]))] + #[case(Tk2Op::Ry, Some(vec![HSeriesOp::PhasedX]))] + #[case(Tk2Op::Rz, Some(vec![HSeriesOp::Rz]))] + // multi qubit ordering is not deterministic + #[case(Tk2Op::CX, None)] + #[case(Tk2Op::CY, None)] + #[case(Tk2Op::CZ, None)] + #[case(Tk2Op::CRz, None)] + #[case(Tk2Op::Toffoli, None)] + fn test_lower(#[case] t2op: Tk2Op, #[case] hseries_ops: Option>) { + // build dfg with just the op + + let h = op_to_hugr(t2op).unwrap(); + let circ = Circuit::new(&h, h.root()); + let ops: Vec = circ + .commands() + .filter_map(|com| com.optype().cast()) + .collect(); + if let Some(hseries_ops) = hseries_ops { + assert_eq!(ops, hseries_ops); + } + + assert_eq!(check_lowered(&h), Ok(())); + } + + #[test] + fn test_mixed() { + let mut b = DFGBuilder::new(Signature::new(type_row![ANGLE_TYPE], type_row![])).unwrap(); + let [angle] = b.input_wires_arr(); + let [q] = b.add_dataflow_op(Tk2Op::QAlloc, []).unwrap().outputs_arr(); + let [q] = b.add_dataflow_op(Tk2Op::H, [q]).unwrap().outputs_arr(); + let [q] = b + .add_dataflow_op(Tk2Op::Rx, [q, angle]) + .unwrap() + .outputs_arr(); + b.add_dataflow_op(Tk2Op::QFree, [q]).unwrap(); + let mut h = b.finish_hugr_with_outputs([], ®ISTRY).unwrap(); + + let lowered = lower_tk2_op(&mut h).unwrap(); + assert_eq!(lowered.len(), 4); + // dfg, input, output, alloc, phasedx, rz, atorad, phasedx, free + 4x(float + load) + assert_eq!(h.node_count(), 17); + assert_eq!(check_lowered(&h), Ok(())); + } +} diff --git a/tket2-hseries/src/lib.rs b/tket2-hseries/src/lib.rs index e8e759da..f53fb643 100644 --- a/tket2-hseries/src/lib.rs +++ b/tket2-hseries/src/lib.rs @@ -22,7 +22,7 @@ pub mod lazify_measure; /// Modify a [hugr::Hugr] into a form that is acceptable for ingress into an H-series. /// Returns an error if this cannot be done. /// -/// To constuct a `HSeriesPass` use [Default::default]. +/// To construct a `HSeriesPass` use [Default::default]. #[derive(Debug, Clone, Copy, Default)] pub struct HSeriesPass { validation_level: ValidationLevel, diff --git a/tket2/src/passes.rs b/tket2/src/passes.rs index 2b3d7ec9..f560fc26 100644 --- a/tket2/src/passes.rs +++ b/tket2/src/passes.rs @@ -1,6 +1,7 @@ //! Optimisation passes and related utilities for circuits. mod commutation; + pub use commutation::{apply_greedy_commutation, PullForwardError}; pub mod chunks; From 58e162644ecd779dc100c21c3c2edde29ec98d7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 08:31:41 +0100 Subject: [PATCH 3/4] chore(deps-rs): bump the minor group with 2 updates (#600) Bumps the minor group with 2 updates: [bytemuck](https://github.com/Lokathor/bytemuck) and [delegate](https://github.com/kobzol/rust-delegate). Updates `bytemuck` from 1.17.1 to 1.18.0
Changelog

Sourced from bytemuck's changelog.

bytemuck changelog

1.18

  • Adds the latest_stable_rust cargo feature, which is a blanket feature that turns all other features on that are both sound and compatible with Stable rust.
Commits
  • 860c391 chore: Release bytemuck version 1.18.0
  • 4c535f9 Clean up Cargo.toml (#271)
  • 374d184 Update changelog.md
  • 1906570 Add a convenience feature indicating you're on the latest stable version of R...
  • 2d2b397 update the changelog to be less dismissive because we don't need that negativ...
  • See full diff in compare view

Updates `delegate` from 0.12.0 to 0.13.0
Changelog

Sourced from delegate's changelog.

0.13.0 (2. 9. 2024)

  • Generalize match arms handling. You can now combine a match expression target with annotations like #[into] and others:
struct A;

impl A { pub fn callable(self) -> Self { self } }

struct B;

impl B { pub fn callable(self) -> Self { self } }

enum Common { A(A), B(B), }

impl From<A> for Common { fn from(inner: A) -> Self { Self::A(inner) } }

impl From<B> for Common { fn from(inner: B) -> Self { Self::B(inner) } }

impl Common { delegate! { to match self { // ---------- match arms have incompatible types Common::A(inner) => inner; Common::B(inner) => inner; } { #[into] pub fn callable(self) -> Self; } } </tr></table>

... (truncated)

Commits
  • 0f61120 Bump version to 0.13.0
  • d36262b Modify changelog
  • 3b21055 Make #[into] and #[try_into] generate no_std-compatible code
  • 868eaee Fix doc indentation
  • c681324 Fix doc indentation
  • eb03baf Add example of delegating associated functions
  • 556adea Generalize handling of match expressions
  • See full diff in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 6 +++--- Cargo.toml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88128dda..87570247 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,9 +155,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" [[package]] name = "byteorder" @@ -1813,7 +1813,7 @@ dependencies = [ "criterion", "crossbeam-channel", "csv", - "delegate 0.12.0", + "delegate 0.13.0", "derive_more 0.99.18", "downcast-rs", "fxhash", diff --git a/Cargo.toml b/Cargo.toml index f1a178fd..bd1a0b6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,14 +34,14 @@ itertools = "0.13.0" tket-json-rs = "0.5.1" tracing = "0.1.37" portmatching = "0.3.1" -bytemuck = "1.17.1" +bytemuck = "1.18.0" cgmath = "0.18.0" chrono = "0.4.30" clap = "4.5.16" criterion = "0.5.1" crossbeam-channel = "0.5.8" csv = "1.2.2" -delegate = "0.12.0" +delegate = "0.13.0" derive_more = "0.99.18" downcast-rs = "1.2.0" fxhash = "0.2.1" From d9deacbc9a9e63c52efc57979dd8028397c2b466 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 09:04:24 +0100 Subject: [PATCH 4/4] chore(deps-rs): bump the patch group with 3 updates (#599) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the patch group with 3 updates: [clap](https://github.com/clap-rs/clap), [serde](https://github.com/serde-rs/serde) and [serde_json](https://github.com/serde-rs/json). Updates `clap` from 4.5.16 to 4.5.17
Release notes

Sourced from clap's releases.

v4.5.17

[4.5.17] - 2024-09-04

Fixes

  • (help) Style required argument groups
  • (derive) Improve error messages when unsupported fields are used
Changelog

Sourced from clap's changelog.

[4.5.17] - 2024-09-04

Fixes

  • (help) Style required argument groups
  • (derive) Improve error messages when unsupported fields are used
Commits
  • 6013ad4 chore: Release
  • f98e3ee docs: Update changelog
  • addec17 Merge pull request #5681 from epage/static
  • 3c69aaa docs(complete): Add stdout warning to env
  • e46263a docs(complete): Redistribute dynamic's documentation
  • de723aa fix(complete)!: Flatten in prep for stabilization
  • 6727c15 fix(complete): Section off existing completions
  • 6842ed9 refactor(complete): Remove low-value w macro
  • 17d6d24 Merge pull request #5680 from epage/unstable
  • 23fb056 Merge pull request #5679 from epage/api
  • Additional commits viewable in compare view

Updates `serde` from 1.0.209 to 1.0.210
Release notes

Sourced from serde's releases.

v1.0.210

  • Support serializing and deserializing IpAddr and SocketAddr in no-std mode on Rust 1.77+ (#2816, thanks @​MathiasKoch)
  • Make serde::ser::StdError and serde::de::StdError equivalent to core::error::Error on Rust 1.81+ (#2818)
Commits
  • 89c4b02 Release 1.0.210
  • eeb8e44 Merge pull request #2818 from dtolnay/coreerror
  • 785c2d9 Stabilize no-std StdError trait
  • d549f04 Reformat parse_ip_impl definition and calls
  • 4c0dd63 Delete attr support from core::net deserialization macros
  • 26fb134 Relocate cfg attrs out of parse_ip_impl and parse_socket_impl
  • 07e614b Merge pull request #2817 from dtolnay/corenet
  • b1f899f Delete doc(cfg) attribute from impls that are supported in no-std
  • b4f860e Merge pull request #2816 from MathiasKoch/chore/core-net
  • d940fe1 Reuse existing Buf wrapper as replacement for std::io::Write
  • Additional commits viewable in compare view

Updates `serde_json` from 1.0.127 to 1.0.128
Release notes

Sourced from serde_json's releases.

1.0.128

Commits
  • d96b1d9 Release 1.0.128
  • 599228d Merge pull request #1188 from Mrreadiness/feat/add-hashmap-key-128-serializer
  • 5416cee feat: add support for 128 bit HashMap key serialization
  • 27a4ca9 Upload CI Cargo.lock for reproducing failures
  • See full diff in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87570247..79cc6812 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,9 +253,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.16" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", "clap_derive", @@ -263,9 +263,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstream", "anstyle", @@ -1554,18 +1554,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -1574,9 +1574,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", diff --git a/Cargo.toml b/Cargo.toml index bd1a0b6a..7d503b6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ portmatching = "0.3.1" bytemuck = "1.18.0" cgmath = "0.18.0" chrono = "0.4.30" -clap = "4.5.16" +clap = "4.5.17" criterion = "0.5.1" crossbeam-channel = "0.5.8" csv = "1.2.2"