From 56e2e17f6f9410c85a988f36deb801c81bb903ae Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Fri, 28 Jun 2024 13:48:04 +0100 Subject: [PATCH 01/14] add Lazy Quantum extension --- tket2-hseries/src/extension.rs | 2 +- tket2-hseries/src/extension/lazy_measure.rs | 148 ++++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 tket2-hseries/src/extension/lazy_measure.rs diff --git a/tket2-hseries/src/extension.rs b/tket2-hseries/src/extension.rs index 7ec3fbdc..9cb65d00 100644 --- a/tket2-hseries/src/extension.rs +++ b/tket2-hseries/src/extension.rs @@ -1,3 +1,3 @@ //! This module defines the Hugr extensions used by tket2-hseries. - pub mod futures; +pub mod lazy_measure; diff --git a/tket2-hseries/src/extension/lazy_measure.rs b/tket2-hseries/src/extension/lazy_measure.rs new file mode 100644 index 00000000..492fc422 --- /dev/null +++ b/tket2-hseries/src/extension/lazy_measure.rs @@ -0,0 +1,148 @@ +//! TODO docs +use hugr::{ + builder::{BuildError, Dataflow}, + extension::{ + prelude::{BOOL_T, QB_T}, + simple_op::{try_from_name, MakeOpDef, MakeRegisteredOp}, + ExtensionId, ExtensionRegistry, OpDef, SignatureFunc, PRELUDE, + }, + ops::{NamedOp as _, OpType}, + types::FunctionType, + Extension, Wire, +}; + +use lazy_static::lazy_static; +use strum_macros::{EnumIter, EnumString, IntoStaticStr}; + +use crate::extension::lazy; + +use super::lazy::lazy_type; + +/// TODO docs +pub const EXTENSION_ID: ExtensionId = ExtensionId::new_unchecked("tket2.quantum.lazy"); + +lazy_static! { + /// The "tket2.quantum.lazy" extension + pub static ref EXTENSION: Extension = { + let mut ext = Extension::new(EXTENSION_ID); + LazyQuantumOp::load_all_ops(&mut ext).unwrap(); + ext + }; + + /// TODO docs + pub static ref REGISTRY: ExtensionRegistry = ExtensionRegistry::try_new([ + lazy::EXTENSION.to_owned(), + PRELUDE.to_owned(), + EXTENSION.to_owned() + ]).unwrap(); +} + +#[derive( + Clone, + Copy, + Debug, + serde::Serialize, + serde::Deserialize, + Hash, + PartialEq, + Eq, + PartialOrd, + Ord, + EnumIter, + IntoStaticStr, + EnumString, +)] +#[allow(missing_docs)] +#[non_exhaustive] +enum LazyQuantumOp { + Measure, +} + +impl MakeOpDef for LazyQuantumOp { + fn signature(&self) -> SignatureFunc { + FunctionType::new(QB_T, vec![QB_T, lazy_type(BOOL_T)]).into() + } + + fn from_def(op_def: &OpDef) -> Result { + try_from_name(op_def.name()) + } +} + +impl MakeRegisteredOp for LazyQuantumOp { + fn extension_id(&self) -> ExtensionId { + EXTENSION_ID + } + + fn registry<'s, 'r: 's>(&'s self) -> &'r ExtensionRegistry { + ®ISTRY + } +} + +impl TryFrom<&OpType> for LazyQuantumOp { + type Error = (); + fn try_from(value: &OpType) -> Result { + (|| { + let custom_op = value.as_custom_op()?; + try_from_name(&custom_op.name()).ok() + })() + .ok_or(()) + } +} + +/// TODO docs +pub trait LazyQuantumOpBuilder: Dataflow { + /// TODO docs + fn add_lazy_measure(&mut self, qb: Wire) -> Result<[Wire; 2], BuildError> { + Ok(self + .add_dataflow_op(LazyQuantumOp::Measure, [qb])? + .outputs_arr()) + } +} + +impl LazyQuantumOpBuilder for D {} + +#[cfg(test)] +mod test { + use std::sync::Arc; + + use cool_asserts::assert_matches; + use hugr::{ + builder::{DataflowHugr, FunctionBuilder}, + ops::NamedOp, + }; + use lazy::LazyOpBuilder as _; + use strum::IntoEnumIterator as _; + + use super::*; + + fn get_opdef(op: impl NamedOp) -> Option<&'static Arc> { + EXTENSION.get_op(&op.name()) + } + + #[test] + fn create_extension() { + assert_eq!(EXTENSION.name(), &EXTENSION_ID); + + for o in LazyQuantumOp::iter() { + assert_eq!(LazyQuantumOp::from_def(get_opdef(o).unwrap()), Ok(o)); + } + } + + #[test] + fn circuit() { + let hugr = { + let mut func_builder = FunctionBuilder::new( + "circuit", + FunctionType::new(QB_T, vec![QB_T, BOOL_T]).into(), + ) + .unwrap(); + let [qb] = func_builder.input_wires_arr(); + let [qb, lazy_b] = func_builder.add_lazy_measure(qb).unwrap(); + let [b] = func_builder.add_read(lazy_b, BOOL_T).unwrap(); + func_builder + .finish_hugr_with_outputs([qb, b], ®ISTRY) + .unwrap() + }; + assert_matches!(hugr.validate(®ISTRY), Ok(_)); + } +} From 517f8ecf486e82aad8516d8159530916b79f7dc8 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Fri, 28 Jun 2024 15:20:04 +0100 Subject: [PATCH 02/14] add lazify-measure pass --- tket2-hseries/src/extension.rs | 2 +- .../{lazy_measure.rs => quantum_lazy.rs} | 24 ++- tket2-hseries/src/lazify_measure.rs | 178 ++++++++++++++++++ tket2-hseries/src/lib.rs | 2 + 4 files changed, 196 insertions(+), 10 deletions(-) rename tket2-hseries/src/extension/{lazy_measure.rs => quantum_lazy.rs} (84%) create mode 100644 tket2-hseries/src/lazify_measure.rs diff --git a/tket2-hseries/src/extension.rs b/tket2-hseries/src/extension.rs index 9cb65d00..a7905746 100644 --- a/tket2-hseries/src/extension.rs +++ b/tket2-hseries/src/extension.rs @@ -1,3 +1,3 @@ //! This module defines the Hugr extensions used by tket2-hseries. pub mod futures; -pub mod lazy_measure; +pub mod quantum_lazy; diff --git a/tket2-hseries/src/extension/lazy_measure.rs b/tket2-hseries/src/extension/quantum_lazy.rs similarity index 84% rename from tket2-hseries/src/extension/lazy_measure.rs rename to tket2-hseries/src/extension/quantum_lazy.rs index 492fc422..fc41a512 100644 --- a/tket2-hseries/src/extension/lazy_measure.rs +++ b/tket2-hseries/src/extension/quantum_lazy.rs @@ -3,10 +3,10 @@ use hugr::{ builder::{BuildError, Dataflow}, extension::{ prelude::{BOOL_T, QB_T}, - simple_op::{try_from_name, MakeOpDef, MakeRegisteredOp}, + simple_op::{try_from_name, MakeExtensionOp, MakeOpDef, MakeRegisteredOp}, ExtensionId, ExtensionRegistry, OpDef, SignatureFunc, PRELUDE, }, - ops::{NamedOp as _, OpType}, + ops::{CustomOp, NamedOp as _, OpType}, types::FunctionType, Extension, Wire, }; @@ -54,8 +54,8 @@ lazy_static! { )] #[allow(missing_docs)] #[non_exhaustive] -enum LazyQuantumOp { - Measure, +pub enum LazyQuantumOp { + LazyMeasure, } impl MakeOpDef for LazyQuantumOp { @@ -81,10 +81,16 @@ impl MakeRegisteredOp for LazyQuantumOp { impl TryFrom<&OpType> for LazyQuantumOp { type Error = (); fn try_from(value: &OpType) -> Result { - (|| { - let custom_op = value.as_custom_op()?; - try_from_name(&custom_op.name()).ok() - })() + let Some(custom_op) = value.as_custom_op() else { + Err(())? + }; + match custom_op { + CustomOp::Extension(ext) => Self::from_extension_op(ext).ok(), + CustomOp::Opaque(opaque) if opaque.extension() == &EXTENSION_ID => { + try_from_name(opaque.name()).ok() + } + _ => None, + } .ok_or(()) } } @@ -94,7 +100,7 @@ pub trait LazyQuantumOpBuilder: Dataflow { /// TODO docs fn add_lazy_measure(&mut self, qb: Wire) -> Result<[Wire; 2], BuildError> { Ok(self - .add_dataflow_op(LazyQuantumOp::Measure, [qb])? + .add_dataflow_op(LazyQuantumOp::LazyMeasure, [qb])? .outputs_arr()) } } diff --git a/tket2-hseries/src/lazify_measure.rs b/tket2-hseries/src/lazify_measure.rs new file mode 100644 index 00000000..829ec4d9 --- /dev/null +++ b/tket2-hseries/src/lazify_measure.rs @@ -0,0 +1,178 @@ +use std::collections::{HashMap, HashSet}; + +use hugr::{ + builder::{DFGBuilder, Dataflow, DataflowHugr}, + extension::prelude::{BOOL_T, QB_T}, + hugr::{hugrmut::HugrMut, views::SiblingSubgraph, Rewrite}, + types::FunctionType, + Hugr, HugrView, IncomingPort, Node, SimpleReplacement, +}; +use tket2::Tk2Op; + +use lazy_static::lazy_static; + +use crate::extension::{ + lazy::LazyOpBuilder, + quantum_lazy::{self, LazyQuantumOpBuilder}, +}; +/// TODO docs +pub struct LazifyMeaurePass; + +type Error = Box; +impl LazifyMeaurePass { + /// TODO docs + pub fn run(&self, hugr: &mut impl HugrMut) -> Result<(), Error> { + let mut state = State::new(hugr.nodes().filter_map( + |n| match hugr.get_optype(n).try_into() { + Ok(Tk2Op::Measure) => Some(WorkItem::ReplaceMeasure(n)), + _ => None, + }, + )); + while state.work_one(hugr)? {} + Ok(()) + } +} + +enum WorkItem { + ReplaceMeasure(Node), +} + +struct State { + worklist: Vec, +} + +impl State { + fn new(items: impl IntoIterator) -> Self { + let worklist = items.into_iter().collect(); + Self { worklist } + } + + fn work_one(&mut self, hugr: &mut impl HugrMut) -> Result { + let Some(item) = self.worklist.pop() else { + return Ok(false); + }; + self.worklist.extend(item.work(hugr)?); + Ok(true) + } +} + +lazy_static! { + static ref MEASURE_READ_HUGR: Hugr = { + let mut builder = DFGBuilder::new(FunctionType::new(QB_T, vec![QB_T, BOOL_T])).unwrap(); + let [qb] = builder.input_wires_arr(); + let [qb, lazy_r] = builder.add_lazy_measure(qb).unwrap(); + let [r] = builder.add_read(lazy_r, BOOL_T).unwrap(); + builder + .finish_hugr_with_outputs([qb, r], &quantum_lazy::REGISTRY) + .unwrap() + }; +} + +fn simple_replace_measure( + hugr: &impl HugrView, + node: Node, +) -> (HashSet<(Node, IncomingPort)>, SimpleReplacement) { + assert!( + hugr.get_optype(node).try_into() == Ok(Tk2Op::Measure), + "{:?}", + hugr.get_optype(node) + ); + let g = SiblingSubgraph::try_from_nodes([node], hugr).unwrap(); + let replacement_hugr = MEASURE_READ_HUGR.clone(); + let [i, _] = replacement_hugr.get_io(replacement_hugr.root()).unwrap(); + + // A map from (target ports of edges from the Input node of `replacement`) to (target ports of + // edges from nodes not in `removal` to nodes in `removal`). + let nu_inp = replacement_hugr + .all_linked_inputs(i) + .map(|(n, p)| ((n, p), (node, p))) + .collect(); + + // A map from (target ports of edges from nodes in `removal` to nodes not in `removal`) to + // (input ports of the Output node of `replacement`). + let nu_out: HashMap<_, _> = hugr + .all_linked_inputs(node) + .map(|(n, p)| ((n, p), p)) + .collect(); + + let nu_out_set = nu_out.keys().copied().collect(); + ( + nu_out_set, + SimpleReplacement::new(g, replacement_hugr, nu_inp, nu_out), + ) +} + +impl WorkItem { + fn work(self, hugr: &mut impl HugrMut) -> Result, Error> { + match self { + Self::ReplaceMeasure(node) => { + // for now we read immeidately, but when we don't the first + // result are the linked inputs we must return + let (_, replace) = simple_replace_measure(hugr, node); + replace.apply(hugr)?; + Ok(std::iter::empty()) + } + } + } +} + +#[cfg(test)] +mod test { + use cool_asserts::assert_matches; + + use hugr::{ + extension::{ExtensionRegistry, PRELUDE}, + std_extensions::arithmetic::float_types, + }; + use tket2::extension::TKET2_EXTENSION; + + use crate::extension::{ + lazy::{self, LazyOp}, + quantum_lazy::LazyQuantumOp, + }; + + use super::*; + + lazy_static! { + pub static ref REGISTRY: ExtensionRegistry = ExtensionRegistry::try_new([ + quantum_lazy::EXTENSION.to_owned(), + lazy::EXTENSION.to_owned(), + TKET2_EXTENSION.to_owned(), + PRELUDE.to_owned(), + float_types::EXTENSION.clone(), + ]) + .unwrap(); + } + #[test] + fn simple() { + let mut hugr = { + let mut builder = DFGBuilder::new(FunctionType::new(QB_T, vec![QB_T, BOOL_T])).unwrap(); + let [qb] = builder.input_wires_arr(); + let outs = builder + .add_dataflow_op(Tk2Op::Measure, [qb]) + .unwrap() + .outputs(); + builder.finish_hugr_with_outputs(outs, ®ISTRY).unwrap() + }; + assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); + println!("{}", hugr.mermaid_string()); + LazifyMeaurePass.run(&mut hugr).unwrap(); + println!("{}", hugr.mermaid_string()); + assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); + let mut num_read = 0; + let mut num_lazy_measure = 0; + for n in hugr.nodes() { + let ot = hugr.get_optype(n); + if let Ok(LazyOp::Read) = ot.try_into() { + num_read += 1; + } else if let Ok(LazyQuantumOp::LazyMeasure) = ot.try_into() { + num_lazy_measure += 1; + } else { + assert_matches!(Tk2Op::try_from(ot), Err(_)) + } + } + + assert_eq!(1, num_read); + assert_eq!(1, num_lazy_measure); + } +} diff --git a/tket2-hseries/src/lib.rs b/tket2-hseries/src/lib.rs index d640c673..cea19dce 100644 --- a/tket2-hseries/src/lib.rs +++ b/tket2-hseries/src/lib.rs @@ -8,6 +8,8 @@ pub mod cli; pub mod extension; +pub mod lazify_measure; + /// Modify a [Hugr] into a form that is acceptable for ingress into an H-series. /// /// Returns an error if this cannot be done. From 07e4a396c2855c5712f977975ec2ab3d22cd110f Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Mon, 15 Jul 2024 09:16:26 +0100 Subject: [PATCH 03/14] fix up rebase --- tket2-hseries/src/extension/quantum_lazy.rs | 23 +++++++++++---------- tket2-hseries/src/lazify_measure.rs | 8 +++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/tket2-hseries/src/extension/quantum_lazy.rs b/tket2-hseries/src/extension/quantum_lazy.rs index fc41a512..38fb0221 100644 --- a/tket2-hseries/src/extension/quantum_lazy.rs +++ b/tket2-hseries/src/extension/quantum_lazy.rs @@ -14,9 +14,9 @@ use hugr::{ use lazy_static::lazy_static; use strum_macros::{EnumIter, EnumString, IntoStaticStr}; -use crate::extension::lazy; +use crate::extension::futures; -use super::lazy::lazy_type; +use super::futures::future_type; /// TODO docs pub const EXTENSION_ID: ExtensionId = ExtensionId::new_unchecked("tket2.quantum.lazy"); @@ -31,7 +31,7 @@ lazy_static! { /// TODO docs pub static ref REGISTRY: ExtensionRegistry = ExtensionRegistry::try_new([ - lazy::EXTENSION.to_owned(), + futures::EXTENSION.to_owned(), PRELUDE.to_owned(), EXTENSION.to_owned() ]).unwrap(); @@ -60,11 +60,15 @@ pub enum LazyQuantumOp { impl MakeOpDef for LazyQuantumOp { fn signature(&self) -> SignatureFunc { - FunctionType::new(QB_T, vec![QB_T, lazy_type(BOOL_T)]).into() + FunctionType::new(QB_T, vec![QB_T, future_type(BOOL_T)]).into() } fn from_def(op_def: &OpDef) -> Result { - try_from_name(op_def.name()) + try_from_name(op_def.name(), &EXTENSION_ID) + } + + fn extension(&self) -> ExtensionId { + EXTENSION_ID } } @@ -86,10 +90,7 @@ impl TryFrom<&OpType> for LazyQuantumOp { }; match custom_op { CustomOp::Extension(ext) => Self::from_extension_op(ext).ok(), - CustomOp::Opaque(opaque) if opaque.extension() == &EXTENSION_ID => { - try_from_name(opaque.name()).ok() - } - _ => None, + CustomOp::Opaque(opaque) => try_from_name(opaque.name(), &EXTENSION_ID).ok(), } .ok_or(()) } @@ -116,7 +117,7 @@ mod test { builder::{DataflowHugr, FunctionBuilder}, ops::NamedOp, }; - use lazy::LazyOpBuilder as _; + use futures::FutureOpBuilder as _; use strum::IntoEnumIterator as _; use super::*; @@ -139,7 +140,7 @@ mod test { let hugr = { let mut func_builder = FunctionBuilder::new( "circuit", - FunctionType::new(QB_T, vec![QB_T, BOOL_T]).into(), + FunctionType::new(QB_T, vec![QB_T, BOOL_T]), ) .unwrap(); let [qb] = func_builder.input_wires_arr(); diff --git a/tket2-hseries/src/lazify_measure.rs b/tket2-hseries/src/lazify_measure.rs index 829ec4d9..e0f6e39f 100644 --- a/tket2-hseries/src/lazify_measure.rs +++ b/tket2-hseries/src/lazify_measure.rs @@ -12,7 +12,7 @@ use tket2::Tk2Op; use lazy_static::lazy_static; use crate::extension::{ - lazy::LazyOpBuilder, + futures::FutureOpBuilder, quantum_lazy::{self, LazyQuantumOpBuilder}, }; /// TODO docs @@ -127,7 +127,7 @@ mod test { use tket2::extension::TKET2_EXTENSION; use crate::extension::{ - lazy::{self, LazyOp}, + futures::{self, FutureOp}, quantum_lazy::LazyQuantumOp, }; @@ -136,7 +136,7 @@ mod test { lazy_static! { pub static ref REGISTRY: ExtensionRegistry = ExtensionRegistry::try_new([ quantum_lazy::EXTENSION.to_owned(), - lazy::EXTENSION.to_owned(), + futures::EXTENSION.to_owned(), TKET2_EXTENSION.to_owned(), PRELUDE.to_owned(), float_types::EXTENSION.clone(), @@ -163,7 +163,7 @@ mod test { let mut num_lazy_measure = 0; for n in hugr.nodes() { let ot = hugr.get_optype(n); - if let Ok(LazyOp::Read) = ot.try_into() { + if let Ok(FutureOp::Read) = ot.try_into() { num_read += 1; } else if let Ok(LazyQuantumOp::LazyMeasure) = ot.try_into() { num_lazy_measure += 1; From 19beb6aedfdbff0728480c8f208d364e18a3853a Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Mon, 15 Jul 2024 13:03:27 +0100 Subject: [PATCH 04/14] lazy measure dups and frees --- tket2-hseries/src/extension/quantum_lazy.rs | 10 +-- tket2-hseries/src/lazify_measure.rs | 90 ++++++++++++++++++--- 2 files changed, 85 insertions(+), 15 deletions(-) diff --git a/tket2-hseries/src/extension/quantum_lazy.rs b/tket2-hseries/src/extension/quantum_lazy.rs index 38fb0221..8c075fa4 100644 --- a/tket2-hseries/src/extension/quantum_lazy.rs +++ b/tket2-hseries/src/extension/quantum_lazy.rs @@ -113,11 +113,11 @@ mod test { use std::sync::Arc; use cool_asserts::assert_matches; + use futures::FutureOpBuilder as _; use hugr::{ builder::{DataflowHugr, FunctionBuilder}, ops::NamedOp, }; - use futures::FutureOpBuilder as _; use strum::IntoEnumIterator as _; use super::*; @@ -138,11 +138,9 @@ mod test { #[test] fn circuit() { let hugr = { - let mut func_builder = FunctionBuilder::new( - "circuit", - FunctionType::new(QB_T, vec![QB_T, BOOL_T]), - ) - .unwrap(); + let mut func_builder = + FunctionBuilder::new("circuit", FunctionType::new(QB_T, vec![QB_T, BOOL_T])) + .unwrap(); let [qb] = func_builder.input_wires_arr(); let [qb, lazy_b] = func_builder.add_lazy_measure(qb).unwrap(); let [b] = func_builder.add_read(lazy_b, BOOL_T).unwrap(); diff --git a/tket2-hseries/src/lazify_measure.rs b/tket2-hseries/src/lazify_measure.rs index e0f6e39f..91e8b162 100644 --- a/tket2-hseries/src/lazify_measure.rs +++ b/tket2-hseries/src/lazify_measure.rs @@ -4,8 +4,7 @@ use hugr::{ builder::{DFGBuilder, Dataflow, DataflowHugr}, extension::prelude::{BOOL_T, QB_T}, hugr::{hugrmut::HugrMut, views::SiblingSubgraph, Rewrite}, - types::FunctionType, - Hugr, HugrView, IncomingPort, Node, SimpleReplacement, + types::FunctionType, Hugr, HugrView, IncomingPort, Node, OutgoingPort, SimpleReplacement, }; use tket2::Tk2Op; @@ -68,6 +67,37 @@ lazy_static! { }; } +fn measure_replacement(num_dups: usize) -> Hugr { + let mut out_types = vec![QB_T]; + out_types.extend((0..num_dups).map(|_| BOOL_T)); + let num_out_types = out_types.len(); + let mut builder = DFGBuilder::new(FunctionType::new(QB_T, out_types)).unwrap(); + let [qb] = builder.input_wires_arr(); + let [qb, mut future_r] = builder.add_lazy_measure(qb).unwrap(); + let mut future_rs = vec![]; + if num_dups > 0 { + for _ in 0..num_dups - 1 { + let [r1, r2] = builder.add_dup(future_r, BOOL_T).unwrap(); + future_rs.push(r1); + future_r = r2; + } + future_rs.push(future_r) + } else { + builder.add_free(future_r, BOOL_T).unwrap(); + } + let mut rs = vec![qb]; + rs.extend( + future_rs + .into_iter() + .map(|r| builder.add_read(r, BOOL_T).unwrap()[0]), + ); + assert_eq!(num_out_types, rs.len()); + assert_eq!(num_out_types, num_dups + 1); + builder + .finish_hugr_with_outputs(rs, &quantum_lazy::REGISTRY) + .unwrap() +} + fn simple_replace_measure( hugr: &impl HugrView, node: Node, @@ -78,8 +108,9 @@ fn simple_replace_measure( hugr.get_optype(node) ); let g = SiblingSubgraph::try_from_nodes([node], hugr).unwrap(); - let replacement_hugr = MEASURE_READ_HUGR.clone(); - let [i, _] = replacement_hugr.get_io(replacement_hugr.root()).unwrap(); + let num_uses_of_bool = hugr.linked_inputs(node, OutgoingPort::from(1)).count(); + let replacement_hugr = measure_replacement(num_uses_of_bool); + let [i, o] = replacement_hugr.get_io(replacement_hugr.root()).unwrap(); // A map from (target ports of edges from the Input node of `replacement`) to (target ports of // edges from nodes not in `removal` to nodes in `removal`). @@ -88,12 +119,22 @@ fn simple_replace_measure( .map(|(n, p)| ((n, p), (node, p))) .collect(); + // qubit is linear, there must be exactly one + let (target_node, target_port) = hugr + .single_linked_input(node, OutgoingPort::from(0)) + .unwrap(); // A map from (target ports of edges from nodes in `removal` to nodes not in `removal`) to // (input ports of the Output node of `replacement`). - let nu_out: HashMap<_, _> = hugr - .all_linked_inputs(node) - .map(|(n, p)| ((n, p), p)) + let mut nu_out: HashMap<_, _> = [((target_node, target_port), IncomingPort::from(0))] + .into_iter() .collect(); + nu_out.extend( + hugr.linked_inputs(node, OutgoingPort::from(1)) + .enumerate() + .map(|(i, target)| (target, IncomingPort::from(i + 1))), + ); + assert_eq!(nu_out.len(), 1 + num_uses_of_bool); + assert_eq!(nu_out.len(), hugr.in_value_types(o).count()); let nu_out_set = nu_out.keys().copied().collect(); ( @@ -155,9 +196,7 @@ mod test { builder.finish_hugr_with_outputs(outs, ®ISTRY).unwrap() }; assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); - println!("{}", hugr.mermaid_string()); LazifyMeaurePass.run(&mut hugr).unwrap(); - println!("{}", hugr.mermaid_string()); assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); let mut num_read = 0; let mut num_lazy_measure = 0; @@ -175,4 +214,37 @@ mod test { assert_eq!(1, num_read); assert_eq!(1, num_lazy_measure); } + + #[test] + fn multiple_uses() { + let mut builder = + DFGBuilder::new(FunctionType::new(QB_T, vec![QB_T, BOOL_T, BOOL_T])).unwrap(); + let [qb] = builder.input_wires_arr(); + let [qb, bool] = builder + .add_dataflow_op(Tk2Op::Measure, [qb]) + .unwrap() + .outputs_arr(); + let mut hugr = builder + .finish_hugr_with_outputs([qb, bool, bool], ®ISTRY) + .unwrap(); + + assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); + LazifyMeaurePass.run(&mut hugr).unwrap(); + assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); + } + + #[test] + fn no_uses() { + let mut builder = DFGBuilder::new(FunctionType::new_endo(QB_T)).unwrap(); + let [qb] = builder.input_wires_arr(); + let [qb, _] = builder + .add_dataflow_op(Tk2Op::Measure, [qb]) + .unwrap() + .outputs_arr(); + let mut hugr = builder.finish_hugr_with_outputs([qb], ®ISTRY).unwrap(); + + assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); + LazifyMeaurePass.run(&mut hugr).unwrap(); + assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); + } } From 869f84fb6756453c9c0055c58557ae4873238772 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Mon, 15 Jul 2024 13:49:10 +0100 Subject: [PATCH 05/14] wip --- tket2-hseries/src/extension/quantum_lazy.rs | 16 +++++++++------- tket2-hseries/src/lazify_measure.rs | 7 ++++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/tket2-hseries/src/extension/quantum_lazy.rs b/tket2-hseries/src/extension/quantum_lazy.rs index 8c075fa4..c0ee11ca 100644 --- a/tket2-hseries/src/extension/quantum_lazy.rs +++ b/tket2-hseries/src/extension/quantum_lazy.rs @@ -1,4 +1,4 @@ -//! TODO docs +//! Docs use hugr::{ builder::{BuildError, Dataflow}, extension::{ @@ -6,7 +6,7 @@ use hugr::{ simple_op::{try_from_name, MakeExtensionOp, MakeOpDef, MakeRegisteredOp}, ExtensionId, ExtensionRegistry, OpDef, SignatureFunc, PRELUDE, }, - ops::{CustomOp, NamedOp as _, OpType}, + ops::{CustomOp, OpType}, types::FunctionType, Extension, Wire, }; @@ -29,7 +29,8 @@ lazy_static! { ext }; - /// TODO docs + /// Extension registry including the "tket2.quantum.lazy" extension and + /// dependencies. pub static ref REGISTRY: ExtensionRegistry = ExtensionRegistry::try_new([ futures::EXTENSION.to_owned(), PRELUDE.to_owned(), @@ -55,7 +56,7 @@ lazy_static! { #[allow(missing_docs)] #[non_exhaustive] pub enum LazyQuantumOp { - LazyMeasure, + Measure, } impl MakeOpDef for LazyQuantumOp { @@ -96,12 +97,13 @@ impl TryFrom<&OpType> for LazyQuantumOp { } } -/// TODO docs +/// An extension trait for [Dataflow] providing methods to add +/// "tket2.quantum.lazy" operations. pub trait LazyQuantumOpBuilder: Dataflow { - /// TODO docs + /// Add a "tket2.quantum.lazy.Measure" op. fn add_lazy_measure(&mut self, qb: Wire) -> Result<[Wire; 2], BuildError> { Ok(self - .add_dataflow_op(LazyQuantumOp::LazyMeasure, [qb])? + .add_dataflow_op(LazyQuantumOp::Measure, [qb])? .outputs_arr()) } } diff --git a/tket2-hseries/src/lazify_measure.rs b/tket2-hseries/src/lazify_measure.rs index 91e8b162..29736b98 100644 --- a/tket2-hseries/src/lazify_measure.rs +++ b/tket2-hseries/src/lazify_measure.rs @@ -4,7 +4,8 @@ use hugr::{ builder::{DFGBuilder, Dataflow, DataflowHugr}, extension::prelude::{BOOL_T, QB_T}, hugr::{hugrmut::HugrMut, views::SiblingSubgraph, Rewrite}, - types::FunctionType, Hugr, HugrView, IncomingPort, Node, OutgoingPort, SimpleReplacement, + types::FunctionType, + Hugr, HugrView, IncomingPort, Node, OutgoingPort, SimpleReplacement, }; use tket2::Tk2Op; @@ -134,7 +135,7 @@ fn simple_replace_measure( .map(|(i, target)| (target, IncomingPort::from(i + 1))), ); assert_eq!(nu_out.len(), 1 + num_uses_of_bool); - assert_eq!(nu_out.len(), hugr.in_value_types(o).count()); + assert_eq!(nu_out.len(), replacement_hugr.in_value_types(o).count()); let nu_out_set = nu_out.keys().copied().collect(); ( @@ -204,7 +205,7 @@ mod test { let ot = hugr.get_optype(n); if let Ok(FutureOp::Read) = ot.try_into() { num_read += 1; - } else if let Ok(LazyQuantumOp::LazyMeasure) = ot.try_into() { + } else if let Ok(LazyQuantumOp::Measure) = ot.try_into() { num_lazy_measure += 1; } else { assert_matches!(Tk2Op::try_from(ot), Err(_)) From 69de79aa8868f5beea8d9d05df4b823e52762e96 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Mon, 15 Jul 2024 14:09:05 +0100 Subject: [PATCH 06/14] docs --- tket2-hseries/src/extension/quantum_lazy.rs | 10 ++- tket2-hseries/src/lazify_measure.rs | 67 +++++++++++++++------ 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/tket2-hseries/src/extension/quantum_lazy.rs b/tket2-hseries/src/extension/quantum_lazy.rs index c0ee11ca..8d495d3a 100644 --- a/tket2-hseries/src/extension/quantum_lazy.rs +++ b/tket2-hseries/src/extension/quantum_lazy.rs @@ -1,4 +1,8 @@ -//! Docs +//! This module defines the Hugr extension used to represent Lazy Quantum +//! Operations. +//! +//! Lazyness is represented by returning `tket2.futures.Future` classical +//! values. Qubits are never lazy. use hugr::{ builder::{BuildError, Dataflow}, extension::{ @@ -18,11 +22,11 @@ use crate::extension::futures; use super::futures::future_type; -/// TODO docs +/// The "tket2.quantum.lazy" extension id. pub const EXTENSION_ID: ExtensionId = ExtensionId::new_unchecked("tket2.quantum.lazy"); lazy_static! { - /// The "tket2.quantum.lazy" extension + /// The "tket2.quantum.lazy" extension. pub static ref EXTENSION: Extension = { let mut ext = Extension::new(EXTENSION_ID); LazyQuantumOp::load_all_ops(&mut ext).unwrap(); diff --git a/tket2-hseries/src/lazify_measure.rs b/tket2-hseries/src/lazify_measure.rs index 29736b98..ea4fa5b6 100644 --- a/tket2-hseries/src/lazify_measure.rs +++ b/tket2-hseries/src/lazify_measure.rs @@ -1,8 +1,17 @@ +//! Provides `LazifyMeasurePass` which replaces [Tket2Op::Measure] nodes with +//! [LazyQuantumOp::Measure] nodes. +//! +//! [Tket2Op::Measure]: tket2::Tk2Op::Measure +//! [LazyQuantumOp::Measure]: crate::extension::quantum_lazy::LazyQuantumOp::Measure use std::collections::{HashMap, HashSet}; use hugr::{ + algorithms::validation::ValidationLevel, builder::{DFGBuilder, Dataflow, DataflowHugr}, - extension::prelude::{BOOL_T, QB_T}, + extension::{ + prelude::{BOOL_T, QB_T}, + ExtensionRegistry, + }, hugr::{hugrmut::HugrMut, views::SiblingSubgraph, Rewrite}, types::FunctionType, Hugr, HugrView, IncomingPort, Node, OutgoingPort, SimpleReplacement, @@ -15,21 +24,39 @@ use crate::extension::{ futures::FutureOpBuilder, quantum_lazy::{self, LazyQuantumOpBuilder}, }; -/// TODO docs -pub struct LazifyMeaurePass; + +/// A `Hugr -> Hugr` pass that replaces [tket2::Tk2Op::Measure] nodes with +/// [quantum_lazy::LazyQuantumOp::Measure] nodes. To construct a `LazifyMeasurePass` use +/// [Default::default]. +#[derive(Default)] +pub struct LazifyMeaurePass(ValidationLevel); type Error = Box; + impl LazifyMeaurePass { - /// TODO docs - pub fn run(&self, hugr: &mut impl HugrMut) -> Result<(), Error> { - let mut state = State::new(hugr.nodes().filter_map( - |n| match hugr.get_optype(n).try_into() { - Ok(Tk2Op::Measure) => Some(WorkItem::ReplaceMeasure(n)), - _ => None, - }, - )); - while state.work_one(hugr)? {} - Ok(()) + /// Run `LazifyMeasurePass` on the given [HugrMut]. `registry` is used for + /// validation, if enabled. + pub fn run(&self, hugr: &mut impl HugrMut, registry: &ExtensionRegistry) -> Result<(), Error> { + self.0 + .run_validated_pass(hugr, registry, |hugr, _validation_level| { + // TODO: if _validation_level is not None, verify no non-local edges + let mut state = + State::new( + hugr.nodes() + .filter_map(|n| match hugr.get_optype(n).try_into() { + Ok(Tk2Op::Measure) => Some(WorkItem::ReplaceMeasure(n)), + _ => None, + }), + ); + while state.work_one(hugr)? {} + Ok(()) + }) + } + + /// Returns a new `LazifyMeasurePass` with the given [ValidationLevel]. + pub fn with_validation_level(mut self, level: ValidationLevel) -> Self { + self.0 = level; + self } } @@ -163,7 +190,7 @@ mod test { use cool_asserts::assert_matches; use hugr::{ - extension::{ExtensionRegistry, PRELUDE}, + extension::{ExtensionRegistry, EMPTY_REG, PRELUDE}, std_extensions::arithmetic::float_types, }; use tket2::extension::TKET2_EXTENSION; @@ -197,7 +224,9 @@ mod test { builder.finish_hugr_with_outputs(outs, ®ISTRY).unwrap() }; assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); - LazifyMeaurePass.run(&mut hugr).unwrap(); + LazifyMeaurePass::default() + .run(&mut hugr, &EMPTY_REG) + .unwrap(); assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); let mut num_read = 0; let mut num_lazy_measure = 0; @@ -230,7 +259,9 @@ mod test { .unwrap(); assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); - LazifyMeaurePass.run(&mut hugr).unwrap(); + LazifyMeaurePass::default() + .run(&mut hugr, &EMPTY_REG) + .unwrap(); assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); } @@ -245,7 +276,9 @@ mod test { let mut hugr = builder.finish_hugr_with_outputs([qb], ®ISTRY).unwrap(); assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); - LazifyMeaurePass.run(&mut hugr).unwrap(); + LazifyMeaurePass::default() + .run(&mut hugr, &EMPTY_REG) + .unwrap(); assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); } } From 5d376adb46bdc938933a149985e8230266e3b778 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Mon, 15 Jul 2024 14:10:46 +0100 Subject: [PATCH 07/14] tickle ci --- tket2-hseries/src/lazify_measure.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tket2-hseries/src/lazify_measure.rs b/tket2-hseries/src/lazify_measure.rs index ea4fa5b6..b099c2ea 100644 --- a/tket2-hseries/src/lazify_measure.rs +++ b/tket2-hseries/src/lazify_measure.rs @@ -11,6 +11,7 @@ use hugr::{ extension::{ prelude::{BOOL_T, QB_T}, ExtensionRegistry, + }, hugr::{hugrmut::HugrMut, views::SiblingSubgraph, Rewrite}, types::FunctionType, From 04b2a248152917bb32c695d8c09698f50e64cb03 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Mon, 15 Jul 2024 14:11:10 +0100 Subject: [PATCH 08/14] Revert "tickle ci" This reverts commit 5d376adb46bdc938933a149985e8230266e3b778. --- tket2-hseries/src/lazify_measure.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tket2-hseries/src/lazify_measure.rs b/tket2-hseries/src/lazify_measure.rs index b099c2ea..ea4fa5b6 100644 --- a/tket2-hseries/src/lazify_measure.rs +++ b/tket2-hseries/src/lazify_measure.rs @@ -11,7 +11,6 @@ use hugr::{ extension::{ prelude::{BOOL_T, QB_T}, ExtensionRegistry, - }, hugr::{hugrmut::HugrMut, views::SiblingSubgraph, Rewrite}, types::FunctionType, From 80a12e30ad71326d59829475fa023f9aa3ba5c75 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Mon, 15 Jul 2024 14:12:48 +0100 Subject: [PATCH 09/14] add tket2-hseries to change-filters.yml --- .github/change-filters.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/change-filters.yml b/.github/change-filters.yml index a48bb004..f5127387 100644 --- a/.github/change-filters.yml +++ b/.github/change-filters.yml @@ -8,6 +8,7 @@ rust-core: &rust-core rust: - *rust-core + - "tket2-hseries/**" - "badger-optimiser/**" - "compile-rewriter/**" From 19113dcb2c73bbaac1c059f928d18f6fc9b4b4b0 Mon Sep 17 00:00:00 2001 From: Douglas Wilson <141026920+doug-q@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:44:43 +0100 Subject: [PATCH 10/14] Update tket2-hseries/src/lazify_measure.rs Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> --- tket2-hseries/src/lazify_measure.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tket2-hseries/src/lazify_measure.rs b/tket2-hseries/src/lazify_measure.rs index ea4fa5b6..120554e5 100644 --- a/tket2-hseries/src/lazify_measure.rs +++ b/tket2-hseries/src/lazify_measure.rs @@ -175,8 +175,8 @@ impl WorkItem { fn work(self, hugr: &mut impl HugrMut) -> Result, Error> { match self { Self::ReplaceMeasure(node) => { - // for now we read immeidately, but when we don't the first - // result are the linked inputs we must return + // for now we read immediately, but when we don't the first + // results are the linked inputs we must return let (_, replace) = simple_replace_measure(hugr, node); replace.apply(hugr)?; Ok(std::iter::empty()) From 54652b6b3774e7e4119e097b71c4744e441edfd6 Mon Sep 17 00:00:00 2001 From: Douglas Wilson <141026920+doug-q@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:46:19 +0100 Subject: [PATCH 11/14] Update tket2-hseries/src/lazify_measure.rs Co-authored-by: Mark Koch <48097969+mark-koch@users.noreply.github.com> --- tket2-hseries/src/lazify_measure.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tket2-hseries/src/lazify_measure.rs b/tket2-hseries/src/lazify_measure.rs index 120554e5..713a148d 100644 --- a/tket2-hseries/src/lazify_measure.rs +++ b/tket2-hseries/src/lazify_measure.rs @@ -29,7 +29,7 @@ use crate::extension::{ /// [quantum_lazy::LazyQuantumOp::Measure] nodes. To construct a `LazifyMeasurePass` use /// [Default::default]. #[derive(Default)] -pub struct LazifyMeaurePass(ValidationLevel); +pub struct LazifyMeasurePass(ValidationLevel); type Error = Box; From 4cb1259ec760204d364bf7e999aef9e84856b200 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Mon, 15 Jul 2024 16:04:56 +0100 Subject: [PATCH 12/14] fixes + merge main --- tket2-hseries/src/lazify_measure.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tket2-hseries/src/lazify_measure.rs b/tket2-hseries/src/lazify_measure.rs index 713a148d..7f0d93dc 100644 --- a/tket2-hseries/src/lazify_measure.rs +++ b/tket2-hseries/src/lazify_measure.rs @@ -33,7 +33,7 @@ pub struct LazifyMeasurePass(ValidationLevel); type Error = Box; -impl LazifyMeaurePass { +impl LazifyMeasurePass { /// Run `LazifyMeasurePass` on the given [HugrMut]. `registry` is used for /// validation, if enabled. pub fn run(&self, hugr: &mut impl HugrMut, registry: &ExtensionRegistry) -> Result<(), Error> { @@ -224,7 +224,7 @@ mod test { builder.finish_hugr_with_outputs(outs, ®ISTRY).unwrap() }; assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); - LazifyMeaurePass::default() + LazifyMeasurePass::default() .run(&mut hugr, &EMPTY_REG) .unwrap(); assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); @@ -259,7 +259,7 @@ mod test { .unwrap(); assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); - LazifyMeaurePass::default() + LazifyMeasurePass::default() .run(&mut hugr, &EMPTY_REG) .unwrap(); assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); @@ -276,7 +276,7 @@ mod test { let mut hugr = builder.finish_hugr_with_outputs([qb], ®ISTRY).unwrap(); assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); - LazifyMeaurePass::default() + LazifyMeasurePass::default() .run(&mut hugr, &EMPTY_REG) .unwrap(); assert!(hugr.validate_no_extensions(®ISTRY).is_ok()); From 7b804f61d04c81f0444951563af2701f52127643 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Tue, 16 Jul 2024 06:40:58 +0100 Subject: [PATCH 13/14] add match in signature --- tket2-hseries/src/extension/quantum_lazy.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tket2-hseries/src/extension/quantum_lazy.rs b/tket2-hseries/src/extension/quantum_lazy.rs index 8d495d3a..7fc2496f 100644 --- a/tket2-hseries/src/extension/quantum_lazy.rs +++ b/tket2-hseries/src/extension/quantum_lazy.rs @@ -65,7 +65,9 @@ pub enum LazyQuantumOp { impl MakeOpDef for LazyQuantumOp { fn signature(&self) -> SignatureFunc { - FunctionType::new(QB_T, vec![QB_T, future_type(BOOL_T)]).into() + match self { + Self::Measure => FunctionType::new(QB_T, vec![QB_T, future_type(BOOL_T)]).into() + } } fn from_def(op_def: &OpDef) -> Result { From b7fca2737f31c9699e00547d1e028047484e9945 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Tue, 16 Jul 2024 06:43:18 +0100 Subject: [PATCH 14/14] formatting --- tket2-hseries/src/extension/quantum_lazy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tket2-hseries/src/extension/quantum_lazy.rs b/tket2-hseries/src/extension/quantum_lazy.rs index 7fc2496f..99f930e4 100644 --- a/tket2-hseries/src/extension/quantum_lazy.rs +++ b/tket2-hseries/src/extension/quantum_lazy.rs @@ -66,7 +66,7 @@ pub enum LazyQuantumOp { impl MakeOpDef for LazyQuantumOp { fn signature(&self) -> SignatureFunc { match self { - Self::Measure => FunctionType::new(QB_T, vec![QB_T, future_type(BOOL_T)]).into() + Self::Measure => FunctionType::new(QB_T, vec![QB_T, future_type(BOOL_T)]).into(), } }