From a952d752830e7325c7e7b20570a0ea9ad09be17d Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Mon, 2 Sep 2024 18:07:25 +0100 Subject: [PATCH 01/18] feat: lowering tk2ops -> hseriesops add test for hadamard lowering --- tket2-hseries/src/extension/hseries.rs | 110 ++++++++++++++++++++++++- tket2/src/passes.rs | 51 ++++++++++++ 2 files changed, 159 insertions(+), 2 deletions(-) diff --git a/tket2-hseries/src/extension/hseries.rs b/tket2-hseries/src/extension/hseries.rs index 96ec8145..570f26d7 100644 --- a/tket2-hseries/src/extension/hseries.rs +++ b/tket2-hseries/src/extension/hseries.rs @@ -5,13 +5,14 @@ //! laziness is represented by returning `tket2.futures.Future` classical //! values. Qubits are never lazy. use hugr::{ - builder::{BuildError, Dataflow}, + builder::{BuildError, DFGBuilder, Dataflow, DataflowHugr}, extension::{ prelude::{BOOL_T, QB_T}, simple_op::{try_from_name, MakeOpDef, MakeRegisteredOp}, ExtensionId, ExtensionRegistry, OpDef, SignatureFunc, Version, PRELUDE, }, - std_extensions::arithmetic::float_types::{EXTENSION as FLOAT_TYPES, FLOAT64_TYPE}, + ops::{self, OpTrait}, + std_extensions::arithmetic::float_types::{ConstF64, EXTENSION as FLOAT_TYPES, FLOAT64_TYPE}, type_row, types::Signature, Extension, Wire, @@ -19,6 +20,7 @@ use hugr::{ use lazy_static::lazy_static; use strum_macros::{EnumIter, EnumString, IntoStaticStr}; +use tket2::Tk2Op; use crate::extension::futures; @@ -190,6 +192,51 @@ pub trait HSeriesOpBuilder: Dataflow { impl HSeriesOpBuilder for D {} +/// Lower `Tk2Op` operations to `HSeriesOp` operations. +pub fn lower_tk2_op( + mut hugr: impl hugr::hugr::hugrmut::HugrMut, +) -> Result<(), Box> { + tket2::passes::replace_ops(&mut 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, + Tk2Op::Rz => HSeriesOp::Rz, + _ => return None, + }) + })?; + fn pi_mul(builder: &mut impl Dataflow, multiplier: f64) -> Wire { + builder.add_load_const(ops::Const::new( + ConstF64::new(multiplier * std::f64::consts::PI).into(), + )) + } + tket2::passes::lower_ops(&mut hugr, |op| { + let sig = op.dataflow_signature()?; + let sig = Signature::new(sig.input, sig.output); // ignore extension delta + let op = op.cast()?; + let mut b = DFGBuilder::new(sig).ok()?; + Some(match op { + Tk2Op::H => { + let pi = pi_mul(&mut b, 1.0); + let pi_2 = pi_mul(&mut b, 0.5); + let pi_minus_2 = pi_mul(&mut b, -0.5); + + let [q] = b.input_wires_arr(); + + let q = b.add_phased_x(q, pi_2, pi_minus_2).ok()?; + let q = b.add_rz(q, pi).ok()?; + + b.finish_hugr_with_outputs([q], ®ISTRY).ok()? + } + _ => return None, + }) + })?; + + Ok(()) +} + #[cfg(test)] mod test { use std::sync::Arc; @@ -199,8 +246,10 @@ mod test { use hugr::{ builder::{DataflowHugr, FunctionBuilder}, ops::{NamedOp, OpType}, + HugrView, }; use strum::IntoEnumIterator as _; + use tket2::Circuit; use super::*; @@ -266,4 +315,61 @@ mod test { assert_eq!(optype.cast::(), None); } } + + #[test] + fn test_lower_direct() { + let mut b = FunctionBuilder::new( + "circuit", + Signature::new(type_row![FLOAT64_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::Reset, [q]).unwrap().outputs_arr(); + let [q] = b + .add_dataflow_op(Tk2Op::Rz, [q, angle]) + .unwrap() + .outputs_arr(); + let [q, _] = b + .add_dataflow_op(Tk2Op::Measure, [q]) + .unwrap() + .outputs_arr(); + b.add_dataflow_op(Tk2Op::QFree, [q]).unwrap(); + // TODO remaining ops + let mut h = b.finish_hugr_with_outputs([], ®ISTRY).unwrap(); + lower_tk2_op(&mut h).unwrap(); + 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::Rz, + HSeriesOp::Measure, + HSeriesOp::QFree + ] + ); + } + + #[test] + fn test_lower_circuit() { + let mut b = DFGBuilder::new(Signature::new_endo(QB_T)).unwrap(); + let [q] = b + .add_dataflow_op(Tk2Op::H, [b.input_wires().next().unwrap()]) + .unwrap() + .outputs_arr(); + let mut h = b.finish_hugr_with_outputs([q], ®ISTRY).unwrap(); + + lower_tk2_op(&mut h).unwrap(); + let circ = Circuit::new(&h, h.root()); + let ops: Vec = circ + .commands() + .filter_map(|com| com.optype().cast()) + .collect(); + assert_eq!(ops, vec![HSeriesOp::PhasedX, HSeriesOp::Rz]); + } } diff --git a/tket2/src/passes.rs b/tket2/src/passes.rs index 2b3d7ec9..97debec2 100644 --- a/tket2/src/passes.rs +++ b/tket2/src/passes.rs @@ -1,13 +1,64 @@ //! Optimisation passes and related utilities for circuits. mod commutation; +use std::error::Error; + pub use commutation::{apply_greedy_commutation, PullForwardError}; pub mod chunks; pub use chunks::CircuitChunks; pub mod pytket; +use hugr::{ + hugr::{hugrmut::HugrMut, views::SiblingSubgraph, HugrError}, + ops::OpType, + Hugr, +}; pub use pytket::lower_to_pytket; pub mod tuple_unpack; pub use tuple_unpack::find_tuple_unpack_rewrites; + +// TODO use HUGR versions once they are available + +/// Replace all operations in a HUGR according to a mapping. +pub fn replace_ops>( + hugr: &mut impl HugrMut, + mapping: impl Fn(&OpType) -> Option, +) -> Result<(), HugrError> { + let replacements = hugr + .nodes() + .filter_map(|node| { + let new_op = mapping(hugr.get_optype(node))?; + Some((node, new_op)) + }) + .collect::>(); + + for (node, new_op) in replacements { + hugr.replace_op(node, new_op)?; + } + + Ok(()) +} + +/// Lower operations in a circuit according to a mapping to a new HUGR. +pub fn lower_ops( + hugr: &mut impl HugrMut, + lowering: impl Fn(&OpType) -> Option, +) -> Result<(), Box> { + let replacements = hugr + .nodes() + .filter_map(|node| { + let hugr = lowering(hugr.get_optype(node))?; + Some((node, hugr)) + }) + .collect::>(); + + for (node, replacement) in replacements { + let subcirc = SiblingSubgraph::try_from_nodes([node], hugr)?; + let rw = subcirc.create_simple_replacement(hugr, replacement)?; + hugr.apply_rewrite(rw)?; + } + + Ok(()) +} From dec97fd40f120acb2f471dfbebd7d0d563edbb81 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Wed, 4 Sep 2024 11:40:05 +0100 Subject: [PATCH 02/18] use hugr lowering functions and can't lower rz directly any more (mismatched angle types) --- Cargo.lock | 25 +++++++++---- Cargo.toml | 4 +-- tket2-hseries/src/extension/hseries.rs | 31 +++++++--------- tket2/src/passes.rs | 50 -------------------------- 4 files changed, 33 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75265692..a1fd86c6 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", 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/src/extension/hseries.rs b/tket2-hseries/src/extension/hseries.rs index 570f26d7..493d271e 100644 --- a/tket2-hseries/src/extension/hseries.rs +++ b/tket2-hseries/src/extension/hseries.rs @@ -195,24 +195,27 @@ impl HSeriesOpBuilder for D {} /// Lower `Tk2Op` operations to `HSeriesOp` operations. pub fn lower_tk2_op( mut hugr: impl hugr::hugr::hugrmut::HugrMut, -) -> Result<(), Box> { - tket2::passes::replace_ops(&mut hugr, |op| { +) -> Result, Box> { + let replaced_nodes = hugr::algorithms::replace_many_ops(&mut 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, - Tk2Op::Rz => HSeriesOp::Rz, _ => return None, }) - })?; + })? + .into_iter() + .map(|(node, _)| node) + .collect::>(); fn pi_mul(builder: &mut impl Dataflow, multiplier: f64) -> Wire { builder.add_load_const(ops::Const::new( ConstF64::new(multiplier * std::f64::consts::PI).into(), )) } - tket2::passes::lower_ops(&mut hugr, |op| { + + let lowered_nodes = hugr::algorithms::lower_ops(&mut hugr, |op| { let sig = op.dataflow_signature()?; let sig = Signature::new(sig.input, sig.output); // ignore extension delta let op = op.cast()?; @@ -234,7 +237,7 @@ pub fn lower_tk2_op( }) })?; - Ok(()) + Ok([replaced_nodes, lowered_nodes].concat()) } #[cfg(test)] @@ -318,18 +321,9 @@ mod test { #[test] fn test_lower_direct() { - let mut b = FunctionBuilder::new( - "circuit", - Signature::new(type_row![FLOAT64_TYPE], type_row![]), - ) - .unwrap(); - let [angle] = b.input_wires_arr(); + 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::Rz, [q, angle]) - .unwrap() - .outputs_arr(); let [q, _] = b .add_dataflow_op(Tk2Op::Measure, [q]) .unwrap() @@ -337,7 +331,9 @@ mod test { b.add_dataflow_op(Tk2Op::QFree, [q]).unwrap(); // TODO remaining ops let mut h = b.finish_hugr_with_outputs([], ®ISTRY).unwrap(); - lower_tk2_op(&mut h).unwrap(); + + let lowered = lower_tk2_op(&mut h).unwrap(); + assert_eq!(lowered.len(), 4); let circ = Circuit::new(&h, h.root()); let ops: Vec = circ .commands() @@ -348,7 +344,6 @@ mod test { vec![ HSeriesOp::QAlloc, HSeriesOp::Reset, - HSeriesOp::Rz, HSeriesOp::Measure, HSeriesOp::QFree ] diff --git a/tket2/src/passes.rs b/tket2/src/passes.rs index 97debec2..f560fc26 100644 --- a/tket2/src/passes.rs +++ b/tket2/src/passes.rs @@ -1,7 +1,6 @@ //! Optimisation passes and related utilities for circuits. mod commutation; -use std::error::Error; pub use commutation::{apply_greedy_commutation, PullForwardError}; @@ -9,56 +8,7 @@ pub mod chunks; pub use chunks::CircuitChunks; pub mod pytket; -use hugr::{ - hugr::{hugrmut::HugrMut, views::SiblingSubgraph, HugrError}, - ops::OpType, - Hugr, -}; pub use pytket::lower_to_pytket; pub mod tuple_unpack; pub use tuple_unpack::find_tuple_unpack_rewrites; - -// TODO use HUGR versions once they are available - -/// Replace all operations in a HUGR according to a mapping. -pub fn replace_ops>( - hugr: &mut impl HugrMut, - mapping: impl Fn(&OpType) -> Option, -) -> Result<(), HugrError> { - let replacements = hugr - .nodes() - .filter_map(|node| { - let new_op = mapping(hugr.get_optype(node))?; - Some((node, new_op)) - }) - .collect::>(); - - for (node, new_op) in replacements { - hugr.replace_op(node, new_op)?; - } - - Ok(()) -} - -/// Lower operations in a circuit according to a mapping to a new HUGR. -pub fn lower_ops( - hugr: &mut impl HugrMut, - lowering: impl Fn(&OpType) -> Option, -) -> Result<(), Box> { - let replacements = hugr - .nodes() - .filter_map(|node| { - let hugr = lowering(hugr.get_optype(node))?; - Some((node, hugr)) - }) - .collect::>(); - - for (node, replacement) in replacements { - let subcirc = SiblingSubgraph::try_from_nodes([node], hugr)?; - let rw = subcirc.create_simple_replacement(hugr, replacement)?; - hugr.apply_rewrite(rw)?; - } - - Ok(()) -} From a7f88d9022a7deee660db28af3b0110d60417884 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Wed, 4 Sep 2024 12:20:40 +0100 Subject: [PATCH 03/18] add rz lowering --- Cargo.lock | 1 + tket2-hseries/Cargo.toml | 1 + tket2-hseries/src/extension/hseries.rs | 49 ++++++++++++++++++++------ 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a1fd86c6..88128dda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1853,6 +1853,7 @@ dependencies = [ "itertools 0.13.0", "lazy_static", "petgraph", + "rstest", "serde", "serde_json", "smol_str", 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 493d271e..bedc456b 100644 --- a/tket2-hseries/src/extension/hseries.rs +++ b/tket2-hseries/src/extension/hseries.rs @@ -20,7 +20,10 @@ use hugr::{ use lazy_static::lazy_static; use strum_macros::{EnumIter, EnumString, IntoStaticStr}; -use tket2::Tk2Op; +use tket2::{ + extension::{angle::AngleOp, TKET2_EXTENSION}, + Tk2Op, +}; use crate::extension::futures; @@ -46,6 +49,7 @@ lazy_static! { PRELUDE.to_owned(), EXTENSION.to_owned(), FLOAT_TYPES.to_owned(), + TKET2_EXTENSION.to_owned() ]).unwrap(); } @@ -209,12 +213,22 @@ pub fn lower_tk2_op( .into_iter() .map(|(node, _)| node) .collect::>(); + fn pi_mul(builder: &mut impl Dataflow, multiplier: f64) -> Wire { builder.add_load_const(ops::Const::new( ConstF64::new(multiplier * std::f64::consts::PI).into(), )) } + fn atorad(builder: &mut impl Dataflow, angle: Wire) -> Wire { + builder + .add_dataflow_op(AngleOp::atorad, [angle]) + .unwrap() + .outputs() + .next() + .unwrap() + } + let lowered_nodes = hugr::algorithms::lower_ops(&mut hugr, |op| { let sig = op.dataflow_signature()?; let sig = Signature::new(sig.input, sig.output); // ignore extension delta @@ -233,6 +247,13 @@ pub fn lower_tk2_op( b.finish_hugr_with_outputs([q], ®ISTRY).ok()? } + Tk2Op::Rz => { + let [q, angle] = b.input_wires_arr(); + let float = atorad(&mut b, angle); + let q = b.add_rz(q, float).ok()?; + + b.finish_hugr_with_outputs([q], ®ISTRY).ok()? + } _ => return None, }) })?; @@ -255,6 +276,7 @@ mod test { use tket2::Circuit; use super::*; + use rstest::rstest; fn get_opdef(op: impl NamedOp) -> Option<&'static Arc> { EXTENSION.get_op(&op.name()) @@ -350,21 +372,26 @@ mod test { ); } - #[test] - fn test_lower_circuit() { - let mut b = DFGBuilder::new(Signature::new_endo(QB_T)).unwrap(); - let [q] = b - .add_dataflow_op(Tk2Op::H, [b.input_wires().next().unwrap()]) - .unwrap() - .outputs_arr(); - let mut h = b.finish_hugr_with_outputs([q], ®ISTRY).unwrap(); + #[rstest] + #[case(Tk2Op::H, vec![HSeriesOp::PhasedX, HSeriesOp::Rz])] + #[case(Tk2Op::Rz, vec![HSeriesOp::Rz])] + fn test_lower(#[case] t2op: Tk2Op, #[case] hseries_ops: Vec) { + // build dfg with just the op - lower_tk2_op(&mut h).unwrap(); + use ops::handle::NodeHandle; + let optype: OpType = t2op.into(); + let sig = optype.dataflow_signature().unwrap(); + let mut b = DFGBuilder::new(sig).unwrap(); + let n = b.add_dataflow_op(optype, b.input_wires()).unwrap(); + let mut h = b.finish_hugr_with_outputs(n.outputs(), ®ISTRY).unwrap(); + + let lowered = lower_tk2_op(&mut h).unwrap(); + assert_eq!(lowered, vec![n.node()]); let circ = Circuit::new(&h, h.root()); let ops: Vec = circ .commands() .filter_map(|com| com.optype().cast()) .collect(); - assert_eq!(ops, vec![HSeriesOp::PhasedX, HSeriesOp::Rz]); + assert_eq!(ops, hseries_ops); } } From 1ec47715642637da4ffa5938e51564e82824cc9f Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Wed, 4 Sep 2024 12:52:02 +0100 Subject: [PATCH 04/18] refactor in to lower module --- tket2-hseries/src/extension/hseries.rs | 155 +++---------------- tket2-hseries/src/extension/hseries/lower.rs | 142 +++++++++++++++++ 2 files changed, 164 insertions(+), 133 deletions(-) create mode 100644 tket2-hseries/src/extension/hseries/lower.rs diff --git a/tket2-hseries/src/extension/hseries.rs b/tket2-hseries/src/extension/hseries.rs index bedc456b..c571551c 100644 --- a/tket2-hseries/src/extension/hseries.rs +++ b/tket2-hseries/src/extension/hseries.rs @@ -5,14 +5,13 @@ //! laziness is represented by returning `tket2.futures.Future` classical //! values. Qubits are never lazy. use hugr::{ - builder::{BuildError, DFGBuilder, Dataflow, DataflowHugr}, + builder::{BuildError, Dataflow}, extension::{ prelude::{BOOL_T, QB_T}, simple_op::{try_from_name, MakeOpDef, MakeRegisteredOp}, ExtensionId, ExtensionRegistry, OpDef, SignatureFunc, Version, PRELUDE, }, - ops::{self, OpTrait}, - std_extensions::arithmetic::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, @@ -20,15 +19,16 @@ use hugr::{ use lazy_static::lazy_static; use strum_macros::{EnumIter, EnumString, IntoStaticStr}; -use tket2::{ - extension::{angle::AngleOp, TKET2_EXTENSION}, - Tk2Op, -}; +use tket2::extension::TKET2_EXTENSION; use crate::extension::futures; use super::futures::future_type; +mod lower; +pub use lower::lower_tk2_op; +use lower::pi_mul; + /// The "tket2.hseries" extension id. pub const EXTENSION_ID: ExtensionId = ExtensionId::new_unchecked("tket2.hseries"); /// The "tket2.hseries" extension version. @@ -192,91 +192,34 @@ pub trait HSeriesOpBuilder: Dataflow { self.add_dataflow_op(HSeriesOp::QFree, [qb])?; Ok(()) } -} - -impl HSeriesOpBuilder for D {} -/// Lower `Tk2Op` operations to `HSeriesOp` operations. -pub fn lower_tk2_op( - mut hugr: impl hugr::hugr::hugrmut::HugrMut, -) -> Result, Box> { - let replaced_nodes = hugr::algorithms::replace_many_ops(&mut 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::>(); - - fn pi_mul(builder: &mut impl Dataflow, multiplier: f64) -> Wire { - builder.add_load_const(ops::Const::new( - ConstF64::new(multiplier * std::f64::consts::PI).into(), - )) - } - - fn atorad(builder: &mut impl Dataflow, angle: Wire) -> Wire { - builder - .add_dataflow_op(AngleOp::atorad, [angle]) - .unwrap() - .outputs() - .next() - .unwrap() + /// Build a hadamard gate in terms of HSeries primitives. + fn build_h(&mut self, qb: Wire) -> Result + where + Self: Sized, + { + let pi = pi_mul(self, 1.0); + let pi_2 = pi_mul(self, 0.5); + let pi_minus_2 = pi_mul(self, -0.5); + + let q = self.add_phased_x(qb, pi_2, pi_minus_2)?; + self.add_rz(q, pi) } - - let lowered_nodes = hugr::algorithms::lower_ops(&mut hugr, |op| { - let sig = op.dataflow_signature()?; - let sig = Signature::new(sig.input, sig.output); // ignore extension delta - let op = op.cast()?; - let mut b = DFGBuilder::new(sig).ok()?; - Some(match op { - Tk2Op::H => { - let pi = pi_mul(&mut b, 1.0); - let pi_2 = pi_mul(&mut b, 0.5); - let pi_minus_2 = pi_mul(&mut b, -0.5); - - let [q] = b.input_wires_arr(); - - let q = b.add_phased_x(q, pi_2, pi_minus_2).ok()?; - let q = b.add_rz(q, pi).ok()?; - - b.finish_hugr_with_outputs([q], ®ISTRY).ok()? - } - Tk2Op::Rz => { - let [q, angle] = b.input_wires_arr(); - let float = atorad(&mut b, angle); - let q = b.add_rz(q, float).ok()?; - - b.finish_hugr_with_outputs([q], ®ISTRY).ok()? - } - _ => return None, - }) - })?; - - Ok([replaced_nodes, lowered_nodes].concat()) } +impl HSeriesOpBuilder for D {} + #[cfg(test)] mod test { use std::sync::Arc; use cool_asserts::assert_matches; use futures::FutureOpBuilder as _; - use hugr::{ - builder::{DataflowHugr, FunctionBuilder}, - ops::{NamedOp, OpType}, - HugrView, - }; + use hugr::builder::{DataflowHugr, FunctionBuilder}; + use hugr::ops::{NamedOp, OpType}; use strum::IntoEnumIterator as _; - use tket2::Circuit; use super::*; - use rstest::rstest; fn get_opdef(op: impl NamedOp) -> Option<&'static Arc> { EXTENSION.get_op(&op.name()) @@ -340,58 +283,4 @@ mod test { assert_eq!(optype.cast::(), None); } } - - #[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(); - // TODO remaining ops - let mut h = b.finish_hugr_with_outputs([], ®ISTRY).unwrap(); - - let lowered = lower_tk2_op(&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 - ] - ); - } - - #[rstest] - #[case(Tk2Op::H, vec![HSeriesOp::PhasedX, HSeriesOp::Rz])] - #[case(Tk2Op::Rz, vec![HSeriesOp::Rz])] - fn test_lower(#[case] t2op: Tk2Op, #[case] hseries_ops: Vec) { - // build dfg with just the op - - use ops::handle::NodeHandle; - let optype: OpType = t2op.into(); - let sig = optype.dataflow_signature().unwrap(); - let mut b = DFGBuilder::new(sig).unwrap(); - let n = b.add_dataflow_op(optype, b.input_wires()).unwrap(); - let mut h = b.finish_hugr_with_outputs(n.outputs(), ®ISTRY).unwrap(); - - let lowered = lower_tk2_op(&mut h).unwrap(); - assert_eq!(lowered, vec![n.node()]); - let circ = Circuit::new(&h, h.root()); - let ops: Vec = circ - .commands() - .filter_map(|com| com.optype().cast()) - .collect(); - assert_eq!(ops, hseries_ops); - } } diff --git a/tket2-hseries/src/extension/hseries/lower.rs b/tket2-hseries/src/extension/hseries/lower.rs new file mode 100644 index 00000000..3ac49026 --- /dev/null +++ b/tket2-hseries/src/extension/hseries/lower.rs @@ -0,0 +1,142 @@ +use hugr::{ + builder::{DFGBuilder, Dataflow, DataflowHugr}, + ops::{self, OpTrait}, + std_extensions::arithmetic::float_types::ConstF64, + types::Signature, + Wire, +}; +use itertools::Itertools; +use tket2::{extension::angle::AngleOp, Tk2Op}; + +use crate::extension::hseries::{HSeriesOp, HSeriesOpBuilder}; + +use super::REGISTRY; + +pub(super) fn pi_mul(builder: &mut impl Dataflow, multiplier: f64) -> Wire { + const_f64(builder, multiplier * std::f64::consts::PI) +} + +fn const_f64(builder: &mut impl Dataflow, value: f64) -> Wire { + builder.add_load_const(ops::Const::new(ConstF64::new(value).into())) +} + +fn atorad(builder: &mut impl Dataflow, angle: Wire) -> Wire { + builder + .add_dataflow_op(AngleOp::atorad, [angle]) + .unwrap() + .outputs() + .next() + .unwrap() +} + +fn build_angle_rz( + b: &mut impl Dataflow, + q: Wire, + angle: Wire, +) -> Result { + let float = atorad(b, angle); + b.add_rz(q, float) +} + +/// Lower `Tk2Op` operations to `HSeriesOp` operations. +pub fn lower_tk2_op( + mut hugr: impl hugr::hugr::hugrmut::HugrMut, +) -> Result, Box> { + let replaced_nodes = hugr::algorithms::replace_many_ops(&mut 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::>(); + + let lowered_nodes = hugr::algorithms::lower_ops(&mut hugr, |op| { + let sig = op.dataflow_signature()?; + let sig = Signature::new(sig.input, sig.output); // ignore extension delta + let op = op.cast()?; + let mut b = DFGBuilder::new(sig).ok()?; + let mut inputs = b.input_wires(); + let outputs = match op { + Tk2Op::H => vec![b.build_h(inputs.next()?).ok()?], + + Tk2Op::Rz => { + let [q, angle] = inputs.collect_vec().try_into().ok()?; + + vec![build_angle_rz(&mut b, q, angle).ok()?] + } + _ => return None, + }; + b.finish_hugr_with_outputs(outputs, ®ISTRY).ok() + })?; + + Ok([replaced_nodes, lowered_nodes].concat()) +} + +#[cfg(test)] +mod test { + use hugr::{builder::FunctionBuilder, type_row, HugrView}; + use tket2::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(); + // TODO remaining ops + let mut h = b.finish_hugr_with_outputs([], ®ISTRY).unwrap(); + + let lowered = lower_tk2_op(&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 + ] + ); + } + + #[rstest] + #[case(Tk2Op::H, vec![HSeriesOp::PhasedX, HSeriesOp::Rz])] + #[case(Tk2Op::Rz, vec![HSeriesOp::Rz])] + fn test_lower(#[case] t2op: Tk2Op, #[case] hseries_ops: Vec) { + // build dfg with just the op + + use ops::{handle::NodeHandle, OpType}; + let optype: OpType = t2op.into(); + let sig = optype.dataflow_signature().unwrap(); + let mut b = DFGBuilder::new(sig).unwrap(); + let n = b.add_dataflow_op(optype, b.input_wires()).unwrap(); + let mut h = b.finish_hugr_with_outputs(n.outputs(), ®ISTRY).unwrap(); + + let lowered = lower_tk2_op(&mut h).unwrap(); + assert_eq!(lowered, vec![n.node()]); + let circ = Circuit::new(&h, h.root()); + let ops: Vec = circ + .commands() + .filter_map(|com| com.optype().cast()) + .collect(); + assert_eq!(ops, hseries_ops); + } +} From a0e04374e10ebd1e7a154990ff4cf307128d150c Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Thu, 5 Sep 2024 12:40:12 +0100 Subject: [PATCH 05/18] feat: implement remaining lowerings --- tket2-hseries/src/extension/hseries.rs | 235 ++++++++++++++++++- tket2-hseries/src/extension/hseries/lower.rs | 157 +++++++++---- 2 files changed, 342 insertions(+), 50 deletions(-) diff --git a/tket2-hseries/src/extension/hseries.rs b/tket2-hseries/src/extension/hseries.rs index c571551c..e7164ccd 100644 --- a/tket2-hseries/src/extension/hseries.rs +++ b/tket2-hseries/src/extension/hseries.rs @@ -11,7 +11,11 @@ use hugr::{ simple_op::{try_from_name, MakeOpDef, MakeRegisteredOp}, ExtensionId, ExtensionRegistry, OpDef, SignatureFunc, Version, PRELUDE, }, - std_extensions::arithmetic::float_types::{EXTENSION as FLOAT_TYPES, FLOAT64_TYPE}, + ops::Value, + std_extensions::arithmetic::{ + float_ops::FloatOps, + float_types::{ConstF64, EXTENSION as FLOAT_TYPES, FLOAT64_TYPE}, + }, type_row, types::Signature, Extension, Wire, @@ -198,6 +202,12 @@ pub trait HSeriesOpBuilder: Dataflow { where Self: Sized, { + // Clifford gate: Hadamard + // gate h() a + // { + // PhasedX(pi/2, -pi/2) a; + // Rz(pi) a; + // } let pi = pi_mul(self, 1.0); let pi_2 = pi_mul(self, 0.5); let pi_minus_2 = pi_mul(self, -0.5); @@ -205,6 +215,229 @@ pub trait HSeriesOpBuilder: Dataflow { 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 + where + Self: Sized, + { + // Pauli gate: bit-flip + // gate x() a + // { + // PhasedX(pi, 0) a; + // } + let pi = pi_mul(self, 1.0); + let zero = pi_mul(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 + where + Self: Sized, + { + // Pauli gate: bit and phase flip + // gate y() a + // { + // PhasedX(pi, pi/2) a; + // } + let pi = pi_mul(self, 1.0); + let pi_2 = pi_mul(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 + where + Self: Sized, + { + let pi = pi_mul(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 + where + Self: Sized, + { + let pi_2 = pi_mul(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 + where + Self: Sized, + { + let pi_minus_2 = pi_mul(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 + where + Self: Sized, + { + let pi_4 = pi_mul(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 + where + Self: Sized, + { + let pi_minus_4 = pi_mul(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> + where + Self: Sized, + { + // Clifford gate: CNOT + // gate CX() c,t + // { + // PhasedX(-pi/2, pi/2) t; + // ZZ() c, t; + // Rz(-pi/2) c; + // PhasedX(pi/2, pi) t; + // Rz(-pi/2) t; + // } + let pi = pi_mul(self, 1.0); + let pi_2 = pi_mul(self, 0.5); + let pi_minus_2 = pi_mul(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> + where + Self: Sized, + { + // gate cy() a,b + // { + // sdg b; + // cx a,b; + // s b; + // } + let b = self.build_sdg(b)?; + let [a, b] = self.build_cx(a, b)?; + let b = self.build_s(b)?; + 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> + where + Self: Sized, + { + // gate cz() a,b + // { + // h b; + // cx a,b; + // h b; + // } + let b = self.build_h(b)?; + let [a, b] = self.build_cx(a, b)?; + let b = self.build_h(b)?; + Ok([a, b]) + } + + /// Build a RX gate in terms of HSeries primitives. + fn build_rx(&mut self, qb: Wire, theta: Wire) -> Result + where + Self: Sized, + { + // Rotation around X-axis + // gate rx(theta) a + // { + // phased_x(theta, 0) a; + // } + let zero = pi_mul(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 + where + Self: Sized, + { + // Rotation around Y-axis + // gate ry(theta) a + // { + // phased_x(theta, pi/2) a; + // } + let pi_2 = pi_mul(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> + where + Self: Sized, + { + // gate crz(lambda) a,b + // { + // Rz(lambda/2) b; + // cx a, b; + // Rz(-lambda/2) b; + // cx a, b; + // } + 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 b = self.add_rz(b, lambda_2)?; + let [a, b] = self.build_cx(a, b)?; + let lambda_minus_2 = self + .add_dataflow_op(FloatOps::fneg, [lambda_2])? + .out_wire(0); + let b = self.add_rz(b, lambda_minus_2)?; + let [a, b] = self.build_cx(a, b)?; + 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> + where + Self: Sized, + { + // gate ccx() a,b,c + // { + // h c; + // cx b,c; tdg c; + // cx a,c; t c; + // cx b,c; tdg c; + // cx a,c; t b; t c; h c; + // cx a,b; t a; tdg b; + // cx a,b; + // } + let c = self.build_h(c)?; + let [b, c] = self.build_cx(b, c)?; + let c = self.build_tdg(c)?; + let [a, c] = self.build_cx(a, c)?; + let c = self.build_t(c)?; + let [b, c] = self.build_cx(b, c)?; + let c = self.build_tdg(c)?; + let [a, c] = self.build_cx(a, c)?; + let b = self.build_t(b)?; + let c = self.build_t(c)?; + let c = self.build_h(c)?; + let [a, b] = self.build_cx(a, b)?; + let a = self.build_t(a)?; + let b = self.build_tdg(b)?; + let [a, b] = self.build_cx(a, b)?; + Ok([a, b, c]) + } } impl HSeriesOpBuilder for D {} diff --git a/tket2-hseries/src/extension/hseries/lower.rs b/tket2-hseries/src/extension/hseries/lower.rs index 3ac49026..8c8d67e3 100644 --- a/tket2-hseries/src/extension/hseries/lower.rs +++ b/tket2-hseries/src/extension/hseries/lower.rs @@ -1,11 +1,13 @@ use hugr::{ - builder::{DFGBuilder, Dataflow, DataflowHugr}, + builder::{BuildError, DFGBuilder, Dataflow, DataflowHugr}, + hugr::{hugrmut::HugrMut, HugrError}, ops::{self, OpTrait}, std_extensions::arithmetic::float_types::ConstF64, types::Signature, - Wire, + Hugr, Node, Wire, }; -use itertools::Itertools; +use itertools::Either; +use thiserror::Error; use tket2::{extension::angle::AngleOp, Tk2Op}; use crate::extension::hseries::{HSeriesOp, HSeriesOpBuilder}; @@ -29,20 +31,74 @@ fn atorad(builder: &mut impl Dataflow, angle: Wire) -> Wire { .unwrap() } -fn build_angle_rz( - b: &mut impl Dataflow, - q: Wire, - angle: Wire, -) -> Result { - let float = atorad(b, angle); - b.add_rz(q, float) +/// Errors produced by the [`op_to_hugr`] function. +#[derive(Debug, Error)] +#[error(transparent)] +pub enum LowerBuildError { + #[error("Error when building the circuit: {0}")] + BuildError(#[from] BuildError), + + #[error("Unrecognised operation: {0:?} with {1} inputs")] + UnknownOp(Tk2Op, usize), +} + +fn op_to_hugr(op: Tk2Op) -> Result { + let optype: ops::OpType = op.into(); + let sig = optype.dataflow_signature().expect("known to be dataflow"); + 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 = atorad(&mut b, *angle); + vec![b.build_rx(*q, float)?] + } + (Tk2Op::Ry, [q, angle]) => { + let float = atorad(&mut b, *angle); + vec![b.build_ry(*q, float)?] + } + (Tk2Op::Rz, [q, angle]) => { + let float = atorad(&mut b, *angle); + vec![b.add_rz(*q, float)?] + } + (Tk2Op::CRz, [c, t, angle]) => { + let float = atorad(&mut b, *angle); + b.build_crz(*c, *t, float)?.into() + } + (Tk2Op::Toffoli, [a, b_, c]) => b.build_toffoli(*a, *b_, *c)?.into(), + (Tk2Op::QAlloc | Tk2Op::QFree | Tk2Op::Reset | Tk2Op::Measure, _) => { + unreachable!("should be covered by lower_direct") + } + _ => return Err(LowerBuildError::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( - mut hugr: impl hugr::hugr::hugrmut::HugrMut, -) -> Result, Box> { - let replaced_nodes = hugr::algorithms::replace_many_ops(&mut hugr, |op| { + mut hugr: impl HugrMut, +) -> Result, Either> { + let replaced_nodes = lower_direct(&mut hugr).map_err(Either::Left)?; + let lowered_nodes = hugr::algorithms::lower_ops(&mut hugr, |op| op_to_hugr(op.cast()?).ok()) + .map_err(Either::Right)?; + + Ok([replaced_nodes, lowered_nodes].concat()) +} + +fn lower_direct(hugr: &mut impl HugrMut) -> Result, HugrError> { + Ok(hugr::algorithms::replace_many_ops(hugr, |op| { let op: Tk2Op = op.cast()?; Some(match op { Tk2Op::QAlloc => HSeriesOp::QAlloc, @@ -54,28 +110,7 @@ pub fn lower_tk2_op( })? .into_iter() .map(|(node, _)| node) - .collect::>(); - - let lowered_nodes = hugr::algorithms::lower_ops(&mut hugr, |op| { - let sig = op.dataflow_signature()?; - let sig = Signature::new(sig.input, sig.output); // ignore extension delta - let op = op.cast()?; - let mut b = DFGBuilder::new(sig).ok()?; - let mut inputs = b.input_wires(); - let outputs = match op { - Tk2Op::H => vec![b.build_h(inputs.next()?).ok()?], - - Tk2Op::Rz => { - let [q, angle] = inputs.collect_vec().try_into().ok()?; - - vec![build_angle_rz(&mut b, q, angle).ok()?] - } - _ => return None, - }; - b.finish_hugr_with_outputs(outputs, ®ISTRY).ok() - })?; - - Ok([replaced_nodes, lowered_nodes].concat()) + .collect()) } #[cfg(test)] @@ -99,7 +134,7 @@ mod test { // TODO remaining ops let mut h = b.finish_hugr_with_outputs([], ®ISTRY).unwrap(); - let lowered = lower_tk2_op(&mut h).unwrap(); + let lowered = lower_direct(&mut h).unwrap(); assert_eq!(lowered.len(), 4); let circ = Circuit::new(&h, h.root()); let ops: Vec = circ @@ -118,25 +153,49 @@ mod test { } #[rstest] - #[case(Tk2Op::H, vec![HSeriesOp::PhasedX, HSeriesOp::Rz])] - #[case(Tk2Op::Rz, vec![HSeriesOp::Rz])] - fn test_lower(#[case] t2op: Tk2Op, #[case] hseries_ops: Vec) { + #[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 - use ops::{handle::NodeHandle, OpType}; - let optype: OpType = t2op.into(); - let sig = optype.dataflow_signature().unwrap(); - let mut b = DFGBuilder::new(sig).unwrap(); - let n = b.add_dataflow_op(optype, b.input_wires()).unwrap(); - let mut h = b.finish_hugr_with_outputs(n.outputs(), ®ISTRY).unwrap(); - - let lowered = lower_tk2_op(&mut h).unwrap(); - assert_eq!(lowered, vec![n.node()]); + 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(); - assert_eq!(ops, hseries_ops); + if let Some(hseries_ops) = hseries_ops { + assert_eq!(ops, hseries_ops); + } + } + + #[test] + fn test_mixed() { + let mut b = DFGBuilder::new(Signature::new_endo(type_row![])).unwrap(); + let [q] = b.add_dataflow_op(Tk2Op::QAlloc, []).unwrap().outputs_arr(); + let [q] = b.add_dataflow_op(Tk2Op::H, [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_tk2_op(&mut h).unwrap(); + assert_eq!(lowered.len(), 3); + println!("{}", h.mermaid_string()); + + assert_eq!(h.node_count(), 13); // dfg, input, output, alloc, phasedx, rz, free + 3x(float + load) } } From 053843cc9fd5e7d8601f4b62985bcb35ffc69d8c Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Thu, 5 Sep 2024 14:00:09 +0100 Subject: [PATCH 06/18] feat: `check_lowered` function for checking all Tk2ops have been removed --- tket2-hseries/src/extension/hseries.rs | 2 +- tket2-hseries/src/extension/hseries/lower.rs | 44 ++++++++++++++++---- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/tket2-hseries/src/extension/hseries.rs b/tket2-hseries/src/extension/hseries.rs index e7164ccd..1275f7e6 100644 --- a/tket2-hseries/src/extension/hseries.rs +++ b/tket2-hseries/src/extension/hseries.rs @@ -30,8 +30,8 @@ use crate::extension::futures; use super::futures::future_type; mod lower; -pub use lower::lower_tk2_op; use lower::pi_mul; +pub use lower::{check_lowered, lower_tk2_op}; /// The "tket2.hseries" extension id. pub const EXTENSION_ID: ExtensionId = ExtensionId::new_unchecked("tket2.hseries"); diff --git a/tket2-hseries/src/extension/hseries/lower.rs b/tket2-hseries/src/extension/hseries/lower.rs index 8c8d67e3..2832a332 100644 --- a/tket2-hseries/src/extension/hseries/lower.rs +++ b/tket2-hseries/src/extension/hseries/lower.rs @@ -4,7 +4,7 @@ use hugr::{ ops::{self, OpTrait}, std_extensions::arithmetic::float_types::ConstF64, types::Signature, - Hugr, Node, Wire, + Hugr, HugrView, Node, Wire, }; use itertools::Either; use thiserror::Error; @@ -113,10 +113,32 @@ fn lower_direct(hugr: &mut impl HugrMut) -> Result, HugrError> { .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::Circuit; + use tket2::{extension::angle::ANGLE_TYPE, Circuit}; use super::*; use rstest::rstest; @@ -150,6 +172,7 @@ mod test { HSeriesOp::QFree ] ); + assert_eq!(check_lowered(&h), Ok(())); } #[rstest] @@ -182,20 +205,27 @@ mod test { 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_endo(type_row![])).unwrap(); + 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(), 3); - println!("{}", h.mermaid_string()); - - assert_eq!(h.node_count(), 13); // dfg, input, output, alloc, phasedx, rz, free + 3x(float + load) + assert_eq!(lowered.len(), 4); + // dfg, input, output, alloc, phasedx, rz, phasedx, free + 4x(float + load) + assert_eq!(h.node_count(), 16); + assert_eq!(check_lowered(&h), Ok(())); } } From 3d338f300f9dbae5eb04f9809f3eb01606e0d322 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Thu, 5 Sep 2024 14:18:20 +0100 Subject: [PATCH 07/18] add angle extension to registry and fix test --- tket2-hseries/src/extension/hseries.rs | 3 ++- tket2-hseries/src/extension/hseries/lower.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tket2-hseries/src/extension/hseries.rs b/tket2-hseries/src/extension/hseries.rs index 1275f7e6..876bd69e 100644 --- a/tket2-hseries/src/extension/hseries.rs +++ b/tket2-hseries/src/extension/hseries.rs @@ -53,7 +53,8 @@ lazy_static! { PRELUDE.to_owned(), EXTENSION.to_owned(), FLOAT_TYPES.to_owned(), - TKET2_EXTENSION.to_owned() + TKET2_EXTENSION.to_owned(), + tket2::extension::angle::ANGLE_EXTENSION.to_owned(), ]).unwrap(); } diff --git a/tket2-hseries/src/extension/hseries/lower.rs b/tket2-hseries/src/extension/hseries/lower.rs index 2832a332..4fd0ba48 100644 --- a/tket2-hseries/src/extension/hseries/lower.rs +++ b/tket2-hseries/src/extension/hseries/lower.rs @@ -224,8 +224,8 @@ mod test { let lowered = lower_tk2_op(&mut h).unwrap(); assert_eq!(lowered.len(), 4); - // dfg, input, output, alloc, phasedx, rz, phasedx, free + 4x(float + load) - assert_eq!(h.node_count(), 16); + // dfg, input, output, alloc, phasedx, rz, atorad, phasedx, free + 4x(float + load) + assert_eq!(h.node_count(), 17); assert_eq!(check_lowered(&h), Ok(())); } } From 1499fb0e8a3625067e5ec0a8d449b886e92783e8 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Thu, 5 Sep 2024 14:22:44 +0100 Subject: [PATCH 08/18] remove unused TKET2 extension from hseries registry --- tket2-hseries/src/extension/hseries.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tket2-hseries/src/extension/hseries.rs b/tket2-hseries/src/extension/hseries.rs index 876bd69e..7f8b22ec 100644 --- a/tket2-hseries/src/extension/hseries.rs +++ b/tket2-hseries/src/extension/hseries.rs @@ -23,7 +23,6 @@ use hugr::{ use lazy_static::lazy_static; use strum_macros::{EnumIter, EnumString, IntoStaticStr}; -use tket2::extension::TKET2_EXTENSION; use crate::extension::futures; @@ -53,7 +52,6 @@ lazy_static! { PRELUDE.to_owned(), EXTENSION.to_owned(), FLOAT_TYPES.to_owned(), - TKET2_EXTENSION.to_owned(), tket2::extension::angle::ANGLE_EXTENSION.to_owned(), ]).unwrap(); } From 13b97350bda275e5cc63d3ed23b2cac0ea25a071 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Thu, 5 Sep 2024 16:32:00 +0100 Subject: [PATCH 09/18] use angle op building --- tket2-hseries/src/extension/hseries/lower.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/tket2-hseries/src/extension/hseries/lower.rs b/tket2-hseries/src/extension/hseries/lower.rs index 4fd0ba48..8d4988ee 100644 --- a/tket2-hseries/src/extension/hseries/lower.rs +++ b/tket2-hseries/src/extension/hseries/lower.rs @@ -8,7 +8,7 @@ use hugr::{ }; use itertools::Either; use thiserror::Error; -use tket2::{extension::angle::AngleOp, Tk2Op}; +use tket2::{extension::angle::AngleOpBuilder, Tk2Op}; use crate::extension::hseries::{HSeriesOp, HSeriesOpBuilder}; @@ -22,15 +22,6 @@ fn const_f64(builder: &mut impl Dataflow, value: f64) -> Wire { builder.add_load_const(ops::Const::new(ConstF64::new(value).into())) } -fn atorad(builder: &mut impl Dataflow, angle: Wire) -> Wire { - builder - .add_dataflow_op(AngleOp::atorad, [angle]) - .unwrap() - .outputs() - .next() - .unwrap() -} - /// Errors produced by the [`op_to_hugr`] function. #[derive(Debug, Error)] #[error(transparent)] @@ -62,19 +53,19 @@ fn op_to_hugr(op: Tk2Op) -> Result { (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 = atorad(&mut b, *angle); + let float = b.add_atorad(*angle)?; vec![b.build_rx(*q, float)?] } (Tk2Op::Ry, [q, angle]) => { - let float = atorad(&mut b, *angle); + let float = b.add_atorad(*angle)?; vec![b.build_ry(*q, float)?] } (Tk2Op::Rz, [q, angle]) => { - let float = atorad(&mut b, *angle); + let float = b.add_atorad(*angle)?; vec![b.add_rz(*q, float)?] } (Tk2Op::CRz, [c, t, angle]) => { - let float = atorad(&mut b, *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(), From c63982d6038ca88aa6ad6a22aa197150a29a4238 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Fri, 6 Sep 2024 11:40:28 +0100 Subject: [PATCH 10/18] review suggestions --- tket2-hseries/src/extension/hseries.rs | 131 ++++++------------- tket2-hseries/src/extension/hseries/lower.rs | 12 +- 2 files changed, 42 insertions(+), 101 deletions(-) diff --git a/tket2-hseries/src/extension/hseries.rs b/tket2-hseries/src/extension/hseries.rs index 7f8b22ec..faaedbe0 100644 --- a/tket2-hseries/src/extension/hseries.rs +++ b/tket2-hseries/src/extension/hseries.rs @@ -29,7 +29,7 @@ use crate::extension::futures; use super::futures::future_type; mod lower; -use lower::pi_mul; +use lower::pi_mul_f64; pub use lower::{check_lowered, lower_tk2_op}; /// The "tket2.hseries" extension id. @@ -142,11 +142,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. @@ -167,27 +163,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. @@ -197,104 +185,77 @@ pub trait HSeriesOpBuilder: Dataflow { } /// Build a hadamard gate in terms of HSeries primitives. - fn build_h(&mut self, qb: Wire) -> Result - where - Self: Sized, - { + fn build_h(&mut self, qb: Wire) -> Result { // Clifford gate: Hadamard // gate h() a // { // PhasedX(pi/2, -pi/2) a; // Rz(pi) a; // } - let pi = pi_mul(self, 1.0); - let pi_2 = pi_mul(self, 0.5); - let pi_minus_2 = pi_mul(self, -0.5); + 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 - where - Self: Sized, - { + fn build_x(&mut self, qb: Wire) -> Result { // Pauli gate: bit-flip // gate x() a // { // PhasedX(pi, 0) a; // } - let pi = pi_mul(self, 1.0); - let zero = pi_mul(self, 0.0); + 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 - where - Self: Sized, - { + fn build_y(&mut self, qb: Wire) -> Result { // Pauli gate: bit and phase flip // gate y() a // { // PhasedX(pi, pi/2) a; // } - let pi = pi_mul(self, 1.0); - let pi_2 = pi_mul(self, 0.5); + 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 - where - Self: Sized, - { - let pi = pi_mul(self, 1.0); + 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 - where - Self: Sized, - { - let pi_2 = pi_mul(self, 0.5); + 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 - where - Self: Sized, - { - let pi_minus_2 = pi_mul(self, -0.5); + 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 - where - Self: Sized, - { - let pi_4 = pi_mul(self, 0.25); + 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 - where - Self: Sized, - { - let pi_minus_4 = pi_mul(self, -0.25); + 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> - where - Self: Sized, - { + fn build_cx(&mut self, c: Wire, t: Wire) -> Result<[Wire; 2], BuildError> { // Clifford gate: CNOT // gate CX() c,t // { @@ -304,9 +265,9 @@ pub trait HSeriesOpBuilder: Dataflow { // PhasedX(pi/2, pi) t; // Rz(-pi/2) t; // } - let pi = pi_mul(self, 1.0); - let pi_2 = pi_mul(self, 0.5); - let pi_minus_2 = pi_mul(self, -0.5); + 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)?; @@ -318,10 +279,7 @@ pub trait HSeriesOpBuilder: Dataflow { } /// Build a CY gate in terms of HSeries primitives. - fn build_cy(&mut self, a: Wire, b: Wire) -> Result<[Wire; 2], BuildError> - where - Self: Sized, - { + fn build_cy(&mut self, a: Wire, b: Wire) -> Result<[Wire; 2], BuildError> { // gate cy() a,b // { // sdg b; @@ -335,10 +293,7 @@ pub trait HSeriesOpBuilder: Dataflow { } /// Build a CZ gate in terms of HSeries primitives. - fn build_cz(&mut self, a: Wire, b: Wire) -> Result<[Wire; 2], BuildError> - where - Self: Sized, - { + fn build_cz(&mut self, a: Wire, b: Wire) -> Result<[Wire; 2], BuildError> { // gate cz() a,b // { // h b; @@ -352,38 +307,29 @@ pub trait HSeriesOpBuilder: Dataflow { } /// Build a RX gate in terms of HSeries primitives. - fn build_rx(&mut self, qb: Wire, theta: Wire) -> Result - where - Self: Sized, - { + fn build_rx(&mut self, qb: Wire, theta: Wire) -> Result { // Rotation around X-axis // gate rx(theta) a // { // phased_x(theta, 0) a; // } - let zero = pi_mul(self, 0.0); + 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 - where - Self: Sized, - { + fn build_ry(&mut self, qb: Wire, theta: Wire) -> Result { // Rotation around Y-axis // gate ry(theta) a // { // phased_x(theta, pi/2) a; // } - let pi_2 = pi_mul(self, 0.5); + 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> - where - Self: Sized, - { + fn build_crz(&mut self, a: Wire, b: Wire, lambda: Wire) -> Result<[Wire; 2], BuildError> { // gate crz(lambda) a,b // { // Rz(lambda/2) b; @@ -406,10 +352,7 @@ pub trait HSeriesOpBuilder: Dataflow { } /// 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> - where - Self: Sized, - { + fn build_toffoli(&mut self, a: Wire, b: Wire, c: Wire) -> Result<[Wire; 3], BuildError> { // gate ccx() a,b,c // { // h c; diff --git a/tket2-hseries/src/extension/hseries/lower.rs b/tket2-hseries/src/extension/hseries/lower.rs index 8d4988ee..b3dc3f7c 100644 --- a/tket2-hseries/src/extension/hseries/lower.rs +++ b/tket2-hseries/src/extension/hseries/lower.rs @@ -1,7 +1,7 @@ use hugr::{ builder::{BuildError, DFGBuilder, Dataflow, DataflowHugr}, hugr::{hugrmut::HugrMut, HugrError}, - ops::{self, OpTrait}, + ops::{self, DataflowOpTrait}, std_extensions::arithmetic::float_types::ConstF64, types::Signature, Hugr, HugrView, Node, Wire, @@ -14,17 +14,16 @@ use crate::extension::hseries::{HSeriesOp, HSeriesOpBuilder}; use super::REGISTRY; -pub(super) fn pi_mul(builder: &mut impl Dataflow, multiplier: f64) -> Wire { +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 impl Dataflow, value: f64) -> Wire { +fn const_f64(builder: &mut T, value: f64) -> Wire { builder.add_load_const(ops::Const::new(ConstF64::new(value).into())) } /// Errors produced by the [`op_to_hugr`] function. #[derive(Debug, Error)] -#[error(transparent)] pub enum LowerBuildError { #[error("Error when building the circuit: {0}")] BuildError(#[from] BuildError), @@ -34,8 +33,8 @@ pub enum LowerBuildError { } fn op_to_hugr(op: Tk2Op) -> Result { - let optype: ops::OpType = op.into(); - let sig = optype.dataflow_signature().expect("known to be dataflow"); + let optype: ops::ExtensionOp = op.into_extension_op(); + let sig = optype.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(); @@ -144,7 +143,6 @@ mod test { .unwrap() .outputs_arr(); b.add_dataflow_op(Tk2Op::QFree, [q]).unwrap(); - // TODO remaining ops let mut h = b.finish_hugr_with_outputs([], ®ISTRY).unwrap(); let lowered = lower_direct(&mut h).unwrap(); From ef2a2e0bec853b824931d644523381f7924d11f0 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Fri, 6 Sep 2024 11:42:44 +0100 Subject: [PATCH 11/18] driveby typo --- tket2-hseries/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From bbd8f22c600d3d26f51fd8fbf9554bbae4718970 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Fri, 6 Sep 2024 11:49:56 +0100 Subject: [PATCH 12/18] expand error enum --- tket2-hseries/src/extension/hseries/lower.rs | 26 +++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/tket2-hseries/src/extension/hseries/lower.rs b/tket2-hseries/src/extension/hseries/lower.rs index b3dc3f7c..402cb0cb 100644 --- a/tket2-hseries/src/extension/hseries/lower.rs +++ b/tket2-hseries/src/extension/hseries/lower.rs @@ -6,7 +6,6 @@ use hugr::{ types::Signature, Hugr, HugrView, Node, Wire, }; -use itertools::Either; use thiserror::Error; use tket2::{extension::angle::AngleOpBuilder, Tk2Op}; @@ -22,17 +21,23 @@ fn const_f64(builder: &mut T, value: f64) -> Wire { builder.add_load_const(ops::Const::new(ConstF64::new(value).into())) } -/// Errors produced by the [`op_to_hugr`] function. +/// Errors produced by lowering [Tk2Op]s. #[derive(Debug, Error)] -pub enum LowerBuildError { +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 { +fn op_to_hugr(op: Tk2Op) -> Result { let optype: ops::ExtensionOp = op.into_extension_op(); let sig = optype.signature(); let sig = Signature::new(sig.input, sig.output); // ignore extension delta @@ -71,23 +76,20 @@ fn op_to_hugr(op: Tk2Op) -> Result { (Tk2Op::QAlloc | Tk2Op::QFree | Tk2Op::Reset | Tk2Op::Measure, _) => { unreachable!("should be covered by lower_direct") } - _ => return Err(LowerBuildError::UnknownOp(op, inputs.len())), // non-exhaustive + _ => 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( - mut hugr: impl HugrMut, -) -> Result, Either> { - let replaced_nodes = lower_direct(&mut hugr).map_err(Either::Left)?; - let lowered_nodes = hugr::algorithms::lower_ops(&mut hugr, |op| op_to_hugr(op.cast()?).ok()) - .map_err(Either::Right)?; +pub fn lower_tk2_op(mut hugr: impl HugrMut) -> Result, LowerTk2Error> { + let replaced_nodes = lower_direct(&mut hugr)?; + let lowered_nodes = hugr::algorithms::lower_ops(&mut hugr, |op| op_to_hugr(op.cast()?).ok())?; Ok([replaced_nodes, lowered_nodes].concat()) } -fn lower_direct(hugr: &mut impl HugrMut) -> Result, HugrError> { +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 { From c1edf5f0f40d98df83415812c878eac327c98dd6 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Fri, 6 Sep 2024 11:57:49 +0100 Subject: [PATCH 13/18] add extension requirements --- tket2-hseries/src/extension/hseries.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tket2-hseries/src/extension/hseries.rs b/tket2-hseries/src/extension/hseries.rs index faaedbe0..5762e994 100644 --- a/tket2-hseries/src/extension/hseries.rs +++ b/tket2-hseries/src/extension/hseries.rs @@ -9,7 +9,7 @@ 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::{ @@ -40,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 }; @@ -48,9 +53,9 @@ 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(); From 39fa8653a03bca8a9d11474dc237941bc3463ea4 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Fri, 6 Sep 2024 12:28:55 +0100 Subject: [PATCH 14/18] use zzphase for crz --- tket2-hseries/src/extension/hseries.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tket2-hseries/src/extension/hseries.rs b/tket2-hseries/src/extension/hseries.rs index 5762e994..1c9d3aae 100644 --- a/tket2-hseries/src/extension/hseries.rs +++ b/tket2-hseries/src/extension/hseries.rs @@ -337,22 +337,19 @@ pub trait HSeriesOpBuilder: Dataflow { fn build_crz(&mut self, a: Wire, b: Wire, lambda: Wire) -> Result<[Wire; 2], BuildError> { // gate crz(lambda) a,b // { + // ZZPhase(-lambda/2) a, b; // Rz(lambda/2) b; - // cx a, b; - // Rz(-lambda/2) b; - // cx a, b; // } 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 b = self.add_rz(b, lambda_2)?; - let [a, b] = self.build_cx(a, b)?; let lambda_minus_2 = self .add_dataflow_op(FloatOps::fneg, [lambda_2])? .out_wire(0); - let b = self.add_rz(b, lambda_minus_2)?; - let [a, b] = self.build_cx(a, b)?; + + let [a, b] = self.add_zz_phase(a, b, lambda_minus_2)?; + let b = self.add_rz(b, lambda_2)?; Ok([a, b]) } From 52b64df15b0501583e8d32c4806a5ff47be8c3bb Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Fri, 6 Sep 2024 13:35:24 +0100 Subject: [PATCH 15/18] use direct decompositions and remove comments --- tket2-hseries/src/extension/hseries.rs | 127 +++++++++---------------- 1 file changed, 44 insertions(+), 83 deletions(-) diff --git a/tket2-hseries/src/extension/hseries.rs b/tket2-hseries/src/extension/hseries.rs index 1c9d3aae..f1d7a898 100644 --- a/tket2-hseries/src/extension/hseries.rs +++ b/tket2-hseries/src/extension/hseries.rs @@ -191,12 +191,6 @@ pub trait HSeriesOpBuilder: Dataflow { /// Build a hadamard gate in terms of HSeries primitives. fn build_h(&mut self, qb: Wire) -> Result { - // Clifford gate: Hadamard - // gate h() a - // { - // PhasedX(pi/2, -pi/2) a; - // Rz(pi) a; - // } 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); @@ -207,11 +201,6 @@ pub trait HSeriesOpBuilder: Dataflow { /// Build an X gate in terms of HSeries primitives. fn build_x(&mut self, qb: Wire) -> Result { - // Pauli gate: bit-flip - // gate x() a - // { - // PhasedX(pi, 0) a; - // } let pi = pi_mul_f64(self, 1.0); let zero = pi_mul_f64(self, 0.0); self.add_phased_x(qb, pi, zero) @@ -219,11 +208,6 @@ pub trait HSeriesOpBuilder: Dataflow { /// Build a Y gate in terms of HSeries primitives. fn build_y(&mut self, qb: Wire) -> Result { - // Pauli gate: bit and phase flip - // gate y() a - // { - // PhasedX(pi, pi/2) a; - // } 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) @@ -261,15 +245,6 @@ pub trait HSeriesOpBuilder: Dataflow { /// Build a CNOT gate in terms of HSeries primitives. fn build_cx(&mut self, c: Wire, t: Wire) -> Result<[Wire; 2], BuildError> { - // Clifford gate: CNOT - // gate CX() c,t - // { - // PhasedX(-pi/2, pi/2) t; - // ZZ() c, t; - // Rz(-pi/2) c; - // PhasedX(pi/2, pi) t; - // Rz(-pi/2) t; - // } 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); @@ -285,61 +260,47 @@ pub trait HSeriesOpBuilder: Dataflow { /// Build a CY gate in terms of HSeries primitives. fn build_cy(&mut self, a: Wire, b: Wire) -> Result<[Wire; 2], BuildError> { - // gate cy() a,b - // { - // sdg b; - // cx a,b; - // s b; - // } - let b = self.build_sdg(b)?; - let [a, b] = self.build_cx(a, b)?; - let b = self.build_s(b)?; + 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> { - // gate cz() a,b - // { - // h b; - // cx a,b; - // h b; - // } - let b = self.build_h(b)?; - let [a, b] = self.build_cx(a, b)?; - let b = self.build_h(b)?; + 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 { - // Rotation around X-axis - // gate rx(theta) a - // { - // phased_x(theta, 0) a; - // } 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 { - // Rotation around Y-axis - // gate ry(theta) a - // { - // phased_x(theta, pi/2) a; - // } 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> { - // gate crz(lambda) a,b - // { - // ZZPhase(-lambda/2) a, b; - // Rz(lambda/2) b; - // } let two = self.add_load_const(Value::from(ConstF64::new(2.0))); let lambda_2 = self .add_dataflow_op(FloatOps::fdiv, [lambda, two])? @@ -355,31 +316,31 @@ pub trait HSeriesOpBuilder: Dataflow { /// 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> { - // gate ccx() a,b,c - // { - // h c; - // cx b,c; tdg c; - // cx a,c; t c; - // cx b,c; tdg c; - // cx a,c; t b; t c; h c; - // cx a,b; t a; tdg b; - // cx a,b; - // } - let c = self.build_h(c)?; - let [b, c] = self.build_cx(b, c)?; - let c = self.build_tdg(c)?; - let [a, c] = self.build_cx(a, c)?; - let c = self.build_t(c)?; - let [b, c] = self.build_cx(b, c)?; - let c = self.build_tdg(c)?; - let [a, c] = self.build_cx(a, c)?; - let b = self.build_t(b)?; - let c = self.build_t(c)?; - let c = self.build_h(c)?; - let [a, b] = self.build_cx(a, b)?; - let a = self.build_t(a)?; - let b = self.build_tdg(b)?; - let [a, b] = self.build_cx(a, b)?; + 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]) } } From 2626722f23dc53c24016cb5364cf9d48cbb8e18b Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Fri, 6 Sep 2024 13:46:27 +0100 Subject: [PATCH 16/18] build hugr table ahead of time --- tket2-hseries/src/extension/hseries/lower.rs | 21 ++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/tket2-hseries/src/extension/hseries/lower.rs b/tket2-hseries/src/extension/hseries/lower.rs index 402cb0cb..e45956d6 100644 --- a/tket2-hseries/src/extension/hseries/lower.rs +++ b/tket2-hseries/src/extension/hseries/lower.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use hugr::{ builder::{BuildError, DFGBuilder, Dataflow, DataflowHugr}, hugr::{hugrmut::HugrMut, HugrError}, @@ -6,6 +8,7 @@ use hugr::{ types::Signature, Hugr, HugrView, Node, Wire, }; +use strum::IntoEnumIterator; use thiserror::Error; use tket2::{extension::angle::AngleOpBuilder, Tk2Op}; @@ -73,9 +76,6 @@ fn op_to_hugr(op: Tk2Op) -> Result { b.build_crz(*c, *t, float)?.into() } (Tk2Op::Toffoli, [a, b_, c]) => b.build_toffoli(*a, *b_, *c)?.into(), - (Tk2Op::QAlloc | Tk2Op::QFree | Tk2Op::Reset | Tk2Op::Measure, _) => { - unreachable!("should be covered by lower_direct") - } _ => return Err(LowerTk2Error::UnknownOp(op, inputs.len())), // non-exhaustive }; Ok(b.finish_hugr_with_outputs(outputs, ®ISTRY)?) @@ -84,7 +84,20 @@ fn op_to_hugr(op: Tk2Op) -> Result { /// Lower `Tk2Op` operations to `HSeriesOp` operations. pub fn lower_tk2_op(mut hugr: impl HugrMut) -> Result, LowerTk2Error> { let replaced_nodes = lower_direct(&mut hugr)?; - let lowered_nodes = hugr::algorithms::lower_ops(&mut hugr, |op| op_to_hugr(op.cast()?).ok())?; + 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(&mut hugr, |op| { + let op: Tk2Op = op.cast()?; + hugr_map.get(&op).cloned() + })?; Ok([replaced_nodes, lowered_nodes].concat()) } From e2188123a03464869559864d03e35f66c2dca2af Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Fri, 6 Sep 2024 13:48:49 +0100 Subject: [PATCH 17/18] minor tidy --- tket2-hseries/src/extension/hseries/lower.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tket2-hseries/src/extension/hseries/lower.rs b/tket2-hseries/src/extension/hseries/lower.rs index e45956d6..b64f300a 100644 --- a/tket2-hseries/src/extension/hseries/lower.rs +++ b/tket2-hseries/src/extension/hseries/lower.rs @@ -41,8 +41,7 @@ pub enum LowerTk2Error { } fn op_to_hugr(op: Tk2Op) -> Result { - let optype: ops::ExtensionOp = op.into_extension_op(); - let sig = optype.signature(); + 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(); @@ -94,10 +93,8 @@ pub fn lower_tk2_op(mut hugr: impl HugrMut) -> Result, LowerTk2E }; } - let lowered_nodes = hugr::algorithms::lower_ops(&mut hugr, |op| { - let op: Tk2Op = op.cast()?; - hugr_map.get(&op).cloned() - })?; + let lowered_nodes = + hugr::algorithms::lower_ops(&mut hugr, |op| hugr_map.get(&op.cast()?).cloned())?; Ok([replaced_nodes, lowered_nodes].concat()) } From fcff28e6c78c17432f943ed2b7e1336fedf9ba76 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Fri, 6 Sep 2024 13:55:26 +0100 Subject: [PATCH 18/18] fixup function signature --- tket2-hseries/src/extension/hseries/lower.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tket2-hseries/src/extension/hseries/lower.rs b/tket2-hseries/src/extension/hseries/lower.rs index b64f300a..a7f755cb 100644 --- a/tket2-hseries/src/extension/hseries/lower.rs +++ b/tket2-hseries/src/extension/hseries/lower.rs @@ -81,8 +81,8 @@ fn op_to_hugr(op: Tk2Op) -> Result { } /// Lower `Tk2Op` operations to `HSeriesOp` operations. -pub fn lower_tk2_op(mut hugr: impl HugrMut) -> Result, LowerTk2Error> { - let replaced_nodes = lower_direct(&mut hugr)?; +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) { @@ -93,8 +93,7 @@ pub fn lower_tk2_op(mut hugr: impl HugrMut) -> Result, LowerTk2E }; } - let lowered_nodes = - hugr::algorithms::lower_ops(&mut hugr, |op| hugr_map.get(&op.cast()?).cloned())?; + let lowered_nodes = hugr::algorithms::lower_ops(hugr, |op| hugr_map.get(&op.cast()?).cloned())?; Ok([replaced_nodes, lowered_nodes].concat()) }