From 573868d6a4dc54ae6b18ee9ab178c89ec49bcbb5 Mon Sep 17 00:00:00 2001 From: Luca Mondada <72734770+lmondada@users.noreply.github.com> Date: Fri, 29 Sep 2023 13:06:36 +0200 Subject: [PATCH 01/10] feat: Serialisation for ECCRewriter (#141) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Agustín Borgna <121866228+aborgna-q@users.noreply.github.com> --- Cargo.toml | 2 +- compile-matcher/src/main.rs | 93 ------------------ .../Cargo.toml | 0 .../README.md | 0 .../matcher.bin | Bin compile-rewriter/src/main.rs | 75 ++++++++++++++ src/rewrite/ecc_rewriter.rs | 70 ++++++++++++- 7 files changed, 143 insertions(+), 97 deletions(-) delete mode 100644 compile-matcher/src/main.rs rename {compile-matcher => compile-rewriter}/Cargo.toml (100%) rename {compile-matcher => compile-rewriter}/README.md (100%) rename {compile-matcher => compile-rewriter}/matcher.bin (100%) create mode 100644 compile-rewriter/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 4e19f896..85c4770f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ harness = false [workspace] -members = ["pyrs", "compile-matcher", "taso-optimiser"] +members = ["pyrs", "compile-rewriter", "taso-optimiser"] [workspace.dependencies] diff --git a/compile-matcher/src/main.rs b/compile-matcher/src/main.rs deleted file mode 100644 index 64f3c6d5..00000000 --- a/compile-matcher/src/main.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::fs; -use std::path::Path; -use std::process::exit; - -use clap::Parser; -use itertools::Itertools; - -use tket2::json::load_tk1_json_file; -// Import the PatternMatcher struct and its methods -use tket2::optimiser::taso::load_eccs_json_file; -use tket2::portmatching::{CircuitPattern, PatternMatcher}; - -/// Program to precompile patterns from files into a PatternMatcher stored as binary file. -#[derive(Parser, Debug)] -#[clap(version = "1.0", long_about = None)] -#[clap(about = "Precompiles patterns from files into a PatternMatcher stored as binary file.")] -struct CmdLineArgs { - // TODO: Differentiate between TK1 input and ECC input - /// Name of input file/folder - #[arg( - short, - long, - value_name = "FILE", - help = "Sets the input file or folder to use. It is either a JSON file of Quartz-generated ECCs or a folder with TK1 circuits in JSON format." - )] - input: String, - /// Name of output file/folder - #[arg( - short, - long, - value_name = "FILE", - default_value = ".", - help = "Sets the output file or folder to use. Defaults to \"matcher.bin\" if no file name is provided." - )] - output: String, -} - -fn main() { - let opts = CmdLineArgs::parse(); - - let input_path = Path::new(&opts.input); - let output_path = Path::new(&opts.output); - - let all_circs = if input_path.is_file() { - // Input is an ECC file in JSON format - let Ok(eccs) = load_eccs_json_file(input_path) else { - eprintln!( - "Unable to load ECC file {:?}. Is it a JSON file of Quartz-generated ECCs?", - input_path - ); - exit(1); - }; - eccs.into_iter() - .flat_map(|ecc| ecc.into_circuits()) - .collect_vec() - } else if input_path.is_dir() { - // Input is a folder with TK1 circuits in JSON format - fs::read_dir(input_path) - .unwrap() - .map(|file| { - let path = file.unwrap().path(); - load_tk1_json_file(path).unwrap() - }) - .collect_vec() - } else { - panic!("Input must be a file or a directory"); - }; - - let patterns = all_circs - .iter() - .filter_map(|circ| { - // Fail silently on empty or disconnected patterns - CircuitPattern::try_from_circuit(circ).ok() - }) - .collect_vec(); - println!("Loaded {} patterns.", patterns.len()); - - println!("Building matcher..."); - let output_file = if output_path.is_dir() { - output_path.join("matcher.bin") - } else { - output_path.to_path_buf() - }; - let matcher = PatternMatcher::from_patterns(patterns); - matcher.save_binary(output_file.to_str().unwrap()).unwrap(); - println!("Written matcher to {:?}", output_file); - - // Print the file size of output_file in megabytes - if let Ok(metadata) = fs::metadata(&output_file) { - let file_size = metadata.len() as f64 / (1024.0 * 1024.0); - println!("File size: {:.2} MB", file_size); - } -} diff --git a/compile-matcher/Cargo.toml b/compile-rewriter/Cargo.toml similarity index 100% rename from compile-matcher/Cargo.toml rename to compile-rewriter/Cargo.toml diff --git a/compile-matcher/README.md b/compile-rewriter/README.md similarity index 100% rename from compile-matcher/README.md rename to compile-rewriter/README.md diff --git a/compile-matcher/matcher.bin b/compile-rewriter/matcher.bin similarity index 100% rename from compile-matcher/matcher.bin rename to compile-rewriter/matcher.bin diff --git a/compile-rewriter/src/main.rs b/compile-rewriter/src/main.rs new file mode 100644 index 00000000..77384f03 --- /dev/null +++ b/compile-rewriter/src/main.rs @@ -0,0 +1,75 @@ +use std::fs; +use std::path::Path; +use std::process::exit; +use std::time::Instant; + +use clap::Parser; + +use tket2::rewrite::ECCRewriter; + +/// Program to precompile patterns from files into a PatternMatcher stored as binary file. +#[derive(Parser, Debug)] +#[clap(version = "1.0", long_about = None)] +#[clap( + about = "Precompiles ECC sets into a TKET2 Rewriter. The resulting binary files can be loaded into TKET2 for circuit optimisation." +)] +struct CmdLineArgs { + // TODO: Differentiate between TK1 input and ECC input + /// Name of input file/folder + #[arg( + short, + long, + value_name = "FILE", + help = "Sets the input file to use. It must be a JSON file of ECC sets in the Quartz format." + )] + input: String, + /// Name of output file/folder + #[arg( + short, + long, + value_name = "FILE", + default_value = ".", + help = "Sets the output file or folder. Defaults to \"matcher.rwr\" if no file name is provided. The extension of the file name will always be set or amended to be `.rwr`." + )] + output: String, +} + +fn main() { + let opts = CmdLineArgs::parse(); + + let input_path = Path::new(&opts.input); + let output_path = Path::new(&opts.output); + + if !input_path.is_file() || input_path.extension().unwrap() != "json" { + panic!("Input must be a JSON file"); + }; + let start_time = Instant::now(); + println!("Compiling rewriter..."); + let Ok(rewriter) = ECCRewriter::try_from_eccs_json_file(input_path) else { + eprintln!( + "Unable to load ECC file {:?}. Is it a JSON file of Quartz-generated ECCs?", + input_path + ); + exit(1); + }; + println!("Saving to file..."); + let output_file = if output_path.is_dir() { + output_path.join("matcher.rwr") + } else { + output_path.to_path_buf() + }; + let output_file = rewriter.save_binary(output_file).unwrap(); + println!("Written rewriter to {:?}", output_file); + + // Print the file size of output_file in megabytes + if let Ok(metadata) = fs::metadata(&output_file) { + let file_size = metadata.len() as f64 / (1024.0 * 1024.0); + println!("File size: {:.2} MB", file_size); + } + let elapsed = start_time.elapsed(); + println!( + "Done in {}.{:03} seconds", + elapsed.as_secs(), + elapsed.subsec_millis() + ); +} diff --git a/src/rewrite/ecc_rewriter.rs b/src/rewrite/ecc_rewriter.rs index afd4ff10..8f0a5a8d 100644 --- a/src/rewrite/ecc_rewriter.rs +++ b/src/rewrite/ecc_rewriter.rs @@ -15,8 +15,10 @@ use derive_more::{From, Into}; use itertools::Itertools; use portmatching::PatternID; -use std::io; +use std::fs::File; use std::path::Path; +use std::{io, path::PathBuf}; +use thiserror::Error; use hugr::Hugr; @@ -28,7 +30,7 @@ use crate::{ use super::{CircuitRewrite, Rewriter}; -#[derive(Debug, Clone, Copy, PartialEq, Eq, From, Into)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, From, Into, serde::Serialize, serde::Deserialize)] struct TargetID(usize); /// A rewriter based on circuit equivalence classes. @@ -37,7 +39,7 @@ struct TargetID(usize); /// Valid rewrites turn a non-representative circuit into its representative, /// or a representative circuit into any of the equivalent non-representative /// circuits. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct ECCRewriter { /// Matcher for finding patterns. matcher: PatternMatcher, @@ -91,6 +93,54 @@ impl ECCRewriter { .iter() .map(|id| &self.targets[id.0]) } + + /// Serialise a rewriter to an IO stream. + /// + /// Precomputed rewriters can be serialised as binary and then loaded + /// later using [`ECCRewriter::load_binary_io`]. + pub fn save_binary_io( + &self, + writer: &mut W, + ) -> Result<(), RewriterSerialisationError> { + rmp_serde::encode::write(writer, &self)?; + Ok(()) + } + + /// Load a rewriter from an IO stream. + /// + /// Loads streams as created by [`ECCRewriter::save_binary_io`]. + pub fn load_binary_io(reader: &mut R) -> Result { + let matcher: Self = rmp_serde::decode::from_read(reader)?; + Ok(matcher) + } + + /// Save a rewriter as a binary file. + /// + /// Precomputed rewriters can be saved as binary files and then loaded + /// later using [`ECCRewriter::load_binary`]. + /// + /// The extension of the file name will always be set or amended to be + /// `.rwr`. + /// + /// If successful, returns the path to the newly created file. + pub fn save_binary( + &self, + name: impl AsRef, + ) -> Result { + let mut file_name = PathBuf::from(name.as_ref()); + file_name.set_extension("rwr"); + let file = File::create(&file_name)?; + let mut file = io::BufWriter::new(file); + self.save_binary_io(&mut file)?; + Ok(file_name) + } + + /// Loads a rewriter saved using [`ECCRewriter::save_binary`]. + pub fn load_binary(name: impl AsRef) -> Result { + let file = File::open(name)?; + let mut reader = std::io::BufReader::new(file); + Self::load_binary_io(&mut reader) + } } impl Rewriter for ECCRewriter { @@ -109,6 +159,20 @@ impl Rewriter for ECCRewriter { } } +/// Errors that can occur when (de)serialising an [`ECCRewriter`]. +#[derive(Debug, Error)] +pub enum RewriterSerialisationError { + /// An IO error occured + #[error("IO error: {0}")] + Io(#[from] io::Error), + /// An error occured during deserialisation + #[error("Deserialisation error: {0}")] + Deserialisation(#[from] rmp_serde::decode::Error), + /// An error occured during serialisation + #[error("Serialisation error: {0}")] + Serialisation(#[from] rmp_serde::encode::Error), +} + fn into_targets(rep_sets: Vec) -> Vec { rep_sets .into_iter() From b4700393d44af82f2b98c1f396278662f0da1e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Borgna?= <121866228+aborgna-q@users.noreply.github.com> Date: Fri, 29 Sep 2023 13:12:15 +0200 Subject: [PATCH 02/10] fix: buffer csv writer in taso (#147) quickfix; add a buffer layer to the csv writer that logs new best circuits. This mainly helps short runs find new results frequently. --- taso-optimiser/src/main.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/taso-optimiser/src/main.rs b/taso-optimiser/src/main.rs index af8b1a5d..c31afb0d 100644 --- a/taso-optimiser/src/main.rs +++ b/taso-optimiser/src/main.rs @@ -2,11 +2,12 @@ mod tracing; use crate::tracing::Tracer; +use std::fs::File; use std::io::BufWriter; use std::num::NonZeroUsize; +use std::path::Path; use std::path::PathBuf; use std::process::exit; -use std::{fs, path::Path}; use clap::Parser; use hugr::Hugr; @@ -82,7 +83,7 @@ struct CmdLineArgs { } fn save_tk1_json_file(path: impl AsRef, circ: &Hugr) -> Result<(), std::io::Error> { - let file = fs::File::create(path)?; + let file = File::create(path)?; let writer = BufWriter::new(file); let serial_circ = SerialCircuit::encode(circ).unwrap(); serde_json::to_writer_pretty(writer, &serial_circ)?; @@ -102,7 +103,7 @@ fn main() -> Result<(), Box> { let ecc_path = Path::new(&opts.eccs); // TODO: Remove this from the Logger, and use tracing events instead. - let circ_candidates_csv = fs::File::create("best_circs.csv")?; + let circ_candidates_csv = BufWriter::new(File::create("best_circs.csv")?); let taso_logger = TasoLogger::new(circ_candidates_csv); From ae8b281469956c58157ade8926a87c57bd34ef63 Mon Sep 17 00:00:00 2001 From: Luca Mondada <72734770+lmondada@users.noreply.github.com> Date: Fri, 29 Sep 2023 14:22:41 +0200 Subject: [PATCH 03/10] feat: Add remove_empty_wires fn (#138) --- src/circuit.rs | 193 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 189 insertions(+), 4 deletions(-) diff --git a/src/circuit.rs b/src/circuit.rs index 8f9e9a22..5bf0d1ff 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -9,10 +9,18 @@ pub use hash::CircuitHash; use hugr::HugrView; +use derive_more::From; +use hugr::hugr::hugrmut::HugrMut; +use hugr::hugr::{NodeType, PortIndex}; +use hugr::ops::dataflow::IOTrait; pub use hugr::ops::OpType; +use hugr::ops::{Input, Output, DFG}; use hugr::types::FunctionType; pub use hugr::types::{EdgeKind, Signature, Type, TypeRow}; pub use hugr::{Node, Port, Wire}; +use itertools::Itertools; +use portgraph::Direction; +use thiserror::Error; use self::units::{filter, FilteredUnits, Units}; @@ -120,18 +128,164 @@ pub trait Circuit: HugrView { } } +/// Remove an empty wire in a dataflow HUGR. +/// +/// The wire to be removed is identified by the index of the outgoing port +/// at the circuit input node. +/// +/// This will change the circuit signature and will shift all ports after +/// the removed wire by -1. If the wire is connected to the output node, +/// this will also change the signature output and shift the ports after +/// the removed wire by -1. +/// +/// This will return an error if the wire is not empty or if a HugrError +/// occurs. +#[allow(dead_code)] +pub(crate) fn remove_empty_wire( + circ: &mut impl HugrMut, + input_port: usize, +) -> Result<(), CircuitMutError> { + let [inp, out] = circ.get_io(circ.root()).expect("no IO nodes found at root"); + if input_port >= circ.num_outputs(inp) { + return Err(CircuitMutError::InvalidPortOffset(input_port)); + } + let input_port = Port::new_outgoing(input_port); + let link = circ + .linked_ports(inp, input_port) + .at_most_one() + .map_err(|_| CircuitMutError::DeleteNonEmptyWire(input_port.index()))?; + if link.is_some() && link.unwrap().0 != out { + return Err(CircuitMutError::DeleteNonEmptyWire(input_port.index())); + } + if link.is_some() { + circ.disconnect(inp, input_port)?; + } + + // Shift ports at input + shift_ports(circ, inp, input_port, circ.num_outputs(inp))?; + // Shift ports at output + if let Some((out, output_port)) = link { + shift_ports(circ, out, output_port, circ.num_inputs(out))?; + } + // Update input node, output node (if necessary) and root signatures. + update_signature(circ, input_port.index(), link.map(|(_, p)| p.index())); + // Resize ports at input/output node + circ.set_num_ports(inp, 0, circ.num_outputs(inp) - 1); + if let Some((out, _)) = link { + circ.set_num_ports(out, circ.num_inputs(out) - 1, 0); + } + Ok(()) +} + +/// Errors that can occur when mutating a circuit. +#[derive(Debug, Clone, Error, PartialEq, Eq, From)] +pub enum CircuitMutError { + /// A Hugr error occurred. + #[error("Hugr error: {0:?}")] + HugrError(hugr::hugr::HugrError), + /// The wire to be deleted is not empty. + #[from(ignore)] + #[error("Wire {0} cannot be deleted: not empty")] + DeleteNonEmptyWire(usize), + /// The wire does not exist. + #[from(ignore)] + #[error("Wire {0} does not exist")] + InvalidPortOffset(usize), +} + +/// Shift ports in range (free_port + 1 .. max_ind) by -1. +fn shift_ports( + circ: &mut C, + node: Node, + mut free_port: Port, + max_ind: usize, +) -> Result { + let dir = free_port.direction(); + let port_range = (free_port.index() + 1..max_ind).map(|p| Port::new(dir, p)); + for port in port_range { + let links = circ.linked_ports(node, port).collect_vec(); + if !links.is_empty() { + circ.disconnect(node, port)?; + } + for (other_n, other_p) in links { + // TODO: simplify when CQCL-DEV/hugr#565 is resolved + match dir { + Direction::Incoming => circ.connect(other_n, other_p, node, free_port), + Direction::Outgoing => circ.connect(node, free_port, other_n, other_p), + }?; + } + free_port = port; + } + Ok(free_port) +} + +// Update the signature of circ when removing the in_index-th input wire and +// the out_index-th output wire. +fn update_signature( + circ: &mut C, + in_index: usize, + out_index: Option, +) { + let inp = circ.input(); + // Update input node + let inp_types: TypeRow = { + let OpType::Input(Input { types }) = circ.get_optype(inp).clone() else { + panic!("invalid circuit") + }; + let mut types = types.into_owned(); + types.remove(in_index); + types.into() + }; + let new_inp_op = Input::new(inp_types.clone()); + let inp_exts = circ.get_nodetype(inp).input_extensions().cloned(); + circ.replace_op(inp, NodeType::new(new_inp_op, inp_exts)); + + // Update output node if necessary. + let out_types = out_index.map(|out_index| { + let out = circ.output(); + let out_types: TypeRow = { + let OpType::Output(Output { types }) = circ.get_optype(out).clone() else { + panic!("invalid circuit") + }; + let mut types = types.into_owned(); + types.remove(out_index); + types.into() + }; + let new_out_op = Output::new(out_types.clone()); + let inp_exts = circ.get_nodetype(out).input_extensions().cloned(); + circ.replace_op(out, NodeType::new(new_out_op, inp_exts)); + out_types + }); + + // Update root + let OpType::DFG(DFG { mut signature, .. }) = circ.get_optype(circ.root()).clone() else { + panic!("invalid circuit") + }; + signature.input = inp_types; + if let Some(out_types) = out_types { + signature.output = out_types; + } + let new_dfg_op = DFG { signature }; + let inp_exts = circ.get_nodetype(circ.root()).input_extensions().cloned(); + circ.replace_op(circ.root(), NodeType::new(new_dfg_op, inp_exts)); +} + impl Circuit for T where T: HugrView {} #[cfg(test)] mod tests { - use hugr::Hugr; + use hugr::{ + builder::{DFGBuilder, DataflowHugr}, + extension::{prelude::BOOL_T, PRELUDE_REGISTRY}, + Hugr, + }; - use crate::{circuit::Circuit, json::load_tk1_json_str}; + use super::*; + use crate::{json::load_tk1_json_str, utils::build_simple_circuit, T2Op}; fn test_circuit() -> Hugr { load_tk1_json_str( - r#"{ - "phase": "0", + r#"{ "phase": "0", "bits": [["c", [0]]], "qubits": [["q", [0]], ["q", [1]]], "commands": [ @@ -160,4 +314,35 @@ mod tests { assert_eq!(circ.linear_units().count(), 3); assert_eq!(circ.qubits().count(), 2); } + + #[test] + fn remove_qubit() { + let mut circ = build_simple_circuit(2, |circ| { + circ.append(T2Op::X, [0])?; + Ok(()) + }) + .unwrap(); + + assert_eq!(circ.qubit_count(), 2); + assert!(remove_empty_wire(&mut circ, 1).is_ok()); + assert_eq!(circ.qubit_count(), 1); + assert_eq!( + remove_empty_wire(&mut circ, 0).unwrap_err(), + CircuitMutError::DeleteNonEmptyWire(0) + ); + } + + #[test] + fn remove_bit() { + let h = DFGBuilder::new(FunctionType::new(vec![BOOL_T], vec![])).unwrap(); + let mut circ = h.finish_hugr_with_outputs([], &PRELUDE_REGISTRY).unwrap(); + + assert_eq!(circ.units().count(), 1); + assert!(remove_empty_wire(&mut circ, 0).is_ok()); + assert_eq!(circ.units().count(), 0); + assert_eq!( + remove_empty_wire(&mut circ, 2).unwrap_err(), + CircuitMutError::InvalidPortOffset(2) + ); + } } From ca2d3df9821ea3168c5b22b2682889bf63475352 Mon Sep 17 00:00:00 2001 From: Luca Mondada <72734770+lmondada@users.noreply.github.com> Date: Fri, 29 Sep 2023 15:29:09 +0200 Subject: [PATCH 04/10] feat: Python bindings for TASO (#142) --- pyrs/src/lib.rs | 3 ++ pyrs/src/optimiser.rs | 67 +++++++++++++++++++++++++++++++++++++++++++ src/optimiser.rs | 2 +- src/optimiser/taso.rs | 22 ++++++++------ 4 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 pyrs/src/optimiser.rs diff --git a/pyrs/src/lib.rs b/pyrs/src/lib.rs index 062b4ba4..d845f2c9 100644 --- a/pyrs/src/lib.rs +++ b/pyrs/src/lib.rs @@ -1,11 +1,13 @@ //! Python bindings for TKET2. #![warn(missing_docs)] use circuit::{add_circuit_module, try_with_hugr}; +use optimiser::add_optimiser_module; use pyo3::prelude::*; use tket2::{json::TKETDecode, passes::apply_greedy_commutation}; use tket_json_rs::circuit_json::SerialCircuit; mod circuit; +mod optimiser; #[pyfunction] fn greedy_depth_reduce(py_c: PyObject) -> PyResult<(PyObject, u32)> { @@ -22,6 +24,7 @@ fn pyrs(py: Python, m: &PyModule) -> PyResult<()> { add_circuit_module(py, m)?; add_pattern_module(py, m)?; add_pass_module(py, m)?; + add_optimiser_module(py, m)?; Ok(()) } diff --git a/pyrs/src/optimiser.rs b/pyrs/src/optimiser.rs new file mode 100644 index 00000000..bd7b6a4c --- /dev/null +++ b/pyrs/src/optimiser.rs @@ -0,0 +1,67 @@ +//! PyO3 wrapper for the TASO circuit optimiser. + +use std::{fs, num::NonZeroUsize, path::PathBuf}; + +use pyo3::prelude::*; +use tket2::optimiser::{DefaultTasoOptimiser, TasoLogger}; + +use crate::circuit::update_hugr; + +/// The circuit optimisation module. +pub fn add_optimiser_module(py: Python, parent: &PyModule) -> PyResult<()> { + let m = PyModule::new(py, "optimiser")?; + m.add_class::()?; + + parent.add_submodule(m) +} + +/// Wrapped [`DefaultTasoOptimiser`]. +/// +/// Currently only exposes loading from an ECC file using the constructor +/// and optimising using default logging settings. +#[pyclass(name = "TasoOptimiser")] +pub struct PyDefaultTasoOptimiser(DefaultTasoOptimiser); + +#[pymethods] +impl PyDefaultTasoOptimiser { + /// Create a new [`PyDefaultTasoOptimiser`] from a precompiled rewriter. + #[staticmethod] + pub fn load_precompiled(path: PathBuf) -> Self { + Self(DefaultTasoOptimiser::default_with_rewriter_binary(path).unwrap()) + } + + /// Create a new [`PyDefaultTasoOptimiser`] from ECC sets. + /// + /// This will compile the rewriter from the provided ECC JSON file. + #[staticmethod] + pub fn compile_eccs(path: &str) -> Self { + Self(DefaultTasoOptimiser::default_with_eccs_json_file(path).unwrap()) + } + + /// Run the optimiser on a circuit. + /// + /// Returns an optimised circuit and optionally log the progress to a CSV + /// file. + pub fn optimise( + &self, + circ: PyObject, + timeout: Option, + n_threads: Option, + log_progress: Option, + ) -> PyResult { + let taso_logger = log_progress + .map(|file_name| { + let log_file = fs::File::create(file_name).unwrap(); + TasoLogger::new(log_file) + }) + .unwrap_or_default(); + update_hugr(circ, |circ| { + self.0.optimise_with_log( + &circ, + taso_logger, + timeout, + n_threads.unwrap_or(NonZeroUsize::new(1).unwrap()), + ) + }) + } +} diff --git a/src/optimiser.rs b/src/optimiser.rs index 0760d0d3..4656d0be 100644 --- a/src/optimiser.rs +++ b/src/optimiser.rs @@ -6,4 +6,4 @@ pub mod taso; #[cfg(feature = "portmatching")] pub use taso::DefaultTasoOptimiser; -pub use taso::TasoOptimiser; +pub use taso::{TasoLogger, TasoOptimiser}; diff --git a/src/optimiser/taso.rs b/src/optimiser/taso.rs index 2c7f4982..a231aaf8 100644 --- a/src/optimiser/taso.rs +++ b/src/optimiser/taso.rs @@ -20,6 +20,7 @@ mod worker; use crossbeam_channel::select; pub use eq_circ_class::{load_eccs_json_file, EqCircClass}; +pub use log::TasoLogger; use std::num::NonZeroUsize; use std::time::{Duration, Instant}; @@ -34,11 +35,6 @@ use crate::optimiser::taso::worker::TasoWorker; use crate::rewrite::strategy::RewriteStrategy; use crate::rewrite::Rewriter; -use self::log::TasoLogger; - -#[cfg(feature = "portmatching")] -use std::io; - /// The TASO optimiser. /// /// Adapted from [Quartz][], and originally [TASO][]. @@ -295,8 +291,11 @@ where mod taso_default { use hugr::ops::OpType; use hugr::HugrView; + use std::io; + use std::path::Path; use crate::ops::op_matches; + use crate::rewrite::ecc_rewriter::RewriterSerialisationError; use crate::rewrite::strategy::ExhaustiveRewriteStrategy; use crate::rewrite::ECCRewriter; use crate::T2Op; @@ -312,13 +311,20 @@ mod taso_default { impl DefaultTasoOptimiser { /// A sane default optimiser using the given ECC sets. - pub fn default_with_eccs_json_file( - eccs_path: impl AsRef, - ) -> io::Result { + pub fn default_with_eccs_json_file(eccs_path: impl AsRef) -> io::Result { let rewriter = ECCRewriter::try_from_eccs_json_file(eccs_path)?; let strategy = ExhaustiveRewriteStrategy::exhaustive_cx(); Ok(TasoOptimiser::new(rewriter, strategy, num_cx_gates)) } + + /// A sane default optimiser using a precompiled binary rewriter. + pub fn default_with_rewriter_binary( + rewriter_path: impl AsRef, + ) -> Result { + let rewriter = ECCRewriter::load_binary(rewriter_path)?; + let strategy = ExhaustiveRewriteStrategy::exhaustive_cx(); + Ok(TasoOptimiser::new(rewriter, strategy, num_cx_gates)) + } } fn num_cx_gates(circ: &Hugr) -> usize { From 0e9540b96554d8ab5dc4e622c50fe14a83c0e3f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Borgna?= <121866228+aborgna-q@users.noreply.github.com> Date: Fri, 29 Sep 2023 18:05:26 +0200 Subject: [PATCH 05/10] fix: don't leak memory (#150) Turns out the hugr priority queue never dropped the old circuits in `truncate`. Now the memory usage remains ~constant once the queue reaches its maximum size. --- src/optimiser/taso/hugr_pqueue.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/optimiser/taso/hugr_pqueue.rs b/src/optimiser/taso/hugr_pqueue.rs index eba000e0..f362b5dc 100644 --- a/src/optimiser/taso/hugr_pqueue.rs +++ b/src/optimiser/taso/hugr_pqueue.rs @@ -81,7 +81,8 @@ impl HugrPQ { /// Only keep up to `max_size` elements. pub(super) fn truncate(&mut self, max_size: usize) { while self.queue.len() > max_size { - self.queue.pop_max(); + let (hash, _) = self.queue.pop_max().unwrap(); + self.hash_lookup.remove(&hash); } } From d9cc153b851f534bf14bbff6ecc9fdbbe637c332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Borgna?= <121866228+aborgna-q@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:54:12 +0200 Subject: [PATCH 06/10] feat: Delay yielding const/loadConst commands (#153) Only output them immediately before they are consumed by an operation. This ensures that constant parameters are closer to their consumers on the traversal. That's specially useful for `CircuitChunks`, so we can avoid loading a const in one chunk that's only used another one. (Before this change, the first chunk always had all the `LoadConst` nodes). --- src/circuit/command.rs | 83 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/src/circuit/command.rs b/src/circuit/command.rs index 9bad28ac..b1cb390e 100644 --- a/src/circuit/command.rs +++ b/src/circuit/command.rs @@ -2,6 +2,7 @@ //! //! A [`Command`] is an operation applied to an specific wires, possibly identified by their index in the circuit's input vector. +use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; use std::iter::FusedIterator; @@ -242,6 +243,24 @@ pub struct CommandIterator<'circ, Circ> { wire_unit: HashMap, /// Remaining commands, not counting I/O nodes. remaining: usize, + /// Delayed output of constant and load const nodes. Contains nodes that + /// haven't been yielded yet. + /// + /// We only yield them as Commands when their consumers require them. + delayed_consts: HashSet, + /// Nodes with delayed predecessors. + /// + /// Each node is associated with the number of predecessors that are present + /// in `delayed_consts`. + /// + /// This map is used for performance, to avoid checking the neighbours vs + /// the `delayed_consts` set for each processed node. + delayed_consumers: HashMap, + /// The next node to be processed. + /// + /// This node was produced by the last call to `nodes.next()`, but we had to + /// yield some delayed const nodes before it. + delayed_node: Option, } impl<'circ, Circ> CommandIterator<'circ, Circ> @@ -266,6 +285,67 @@ where wire_unit, // Ignore the input and output nodes, and the root. remaining: circ.node_count() - 3, + delayed_consts: HashSet::new(), + delayed_consumers: HashMap::new(), + delayed_node: None, + } + } + + /// Returns the next node to be processed. + /// + /// If the next node in the topological order is a constant or load const node, + /// delay it until its consumers are processed. + fn next_node(&mut self) -> Option { + let node = self + .delayed_node + .take() + .or_else(|| self.nodes.next(&self.circ.as_petgraph()))?; + + // If this node is a constant or load const node, delay it. + let tag = self.circ.get_optype(node).tag(); + if tag == OpTag::Const || tag == OpTag::LoadConst { + self.delayed_consts.insert(node); + for consumer in self.circ.output_neighbours(node) { + *self.delayed_consumers.entry(consumer).or_default() += 1; + } + return self.next_node(); + } + + // Check if we have any delayed const nodes that are consumed by this node. + match self.delayed_consumers.contains_key(&node) { + true => { + let delayed = self.next_delayed_node(node); + self.delayed_consts.remove(&delayed); + for consumer in self.circ.output_neighbours(delayed) { + let Entry::Occupied(mut entry) = self.delayed_consumers.entry(consumer) else { + panic!("Delayed node consumer was not in delayed_consumers. Delayed node: {delayed:?}, consumer: {consumer:?}."); + }; + *entry.get_mut() -= 1; + if *entry.get() == 0 { + entry.remove(); + } + } + self.delayed_node = Some(node); + Some(delayed) + } + false => Some(node), + } + } + + /// Given a node with delayed predecessors, returns one of those predecessors. + fn next_delayed_node(&mut self, consumer: Node) -> Node { + let Some(delayed_pred) = self + .circ + .input_neighbours(consumer) + .find(|k| self.delayed_consts.contains(k)) + else { + panic!("Could not find a delayed predecessor for node {consumer:?}."); + }; + + // Only output this node if it doesn't require any other delayed predecessors. + match self.delayed_consumers.contains_key(&delayed_pred) { + true => self.next_delayed_node(delayed_pred), + false => delayed_pred, } } @@ -347,7 +427,8 @@ where #[inline] fn next(&mut self) -> Option { loop { - let node = self.nodes.next(&self.circ.as_petgraph())?; + let node = self.next_node()?; + // Process the node, returning a command if it's not an input or output. if let Some(linear_units) = self.process_node(node) { self.remaining -= 1; return Some(Command { From d4f1cc6fa844e4358c8efb809c9d0ab279ec55a0 Mon Sep 17 00:00:00 2001 From: Luca Mondada <72734770+lmondada@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:38:40 +0200 Subject: [PATCH 07/10] fix: Remove empty wires in patterns (#140) --- src/rewrite/ecc_rewriter.rs | 93 ++++++++++++++++++++++++++++++++----- test_files/cx_cx_eccs.json | 8 ++++ 2 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 test_files/cx_cx_eccs.json diff --git a/src/rewrite/ecc_rewriter.rs b/src/rewrite/ecc_rewriter.rs index 8f0a5a8d..c8e95f0f 100644 --- a/src/rewrite/ecc_rewriter.rs +++ b/src/rewrite/ecc_rewriter.rs @@ -13,17 +13,22 @@ //! of the Quartz repository. use derive_more::{From, Into}; +use hugr::hugr::PortIndex; +use hugr::ops::OpTrait; use itertools::Itertools; use portmatching::PatternID; -use std::fs::File; -use std::path::Path; -use std::{io, path::PathBuf}; +use std::{ + collections::HashSet, + fs::File, + io, + path::{Path, PathBuf}, +}; use thiserror::Error; use hugr::Hugr; use crate::{ - circuit::Circuit, + circuit::{remove_empty_wire, Circuit}, optimiser::taso::{load_eccs_json_file, EqCircClass}, portmatching::{CircuitPattern, PatternMatcher}, }; @@ -49,6 +54,9 @@ pub struct ECCRewriter { /// target TargetIDs. The usize index of PatternID is used to index into /// the outer vector. rewrite_rules: Vec>, + /// Wires that have been removed in the pattern circuit -- to be removed + /// in the target circuit as well when generating a rewrite. + empty_wires: Vec>, } impl ECCRewriter { @@ -72,18 +80,34 @@ impl ECCRewriter { let eccs = eccs.into(); let rewrite_rules = get_rewrite_rules(&eccs); let patterns = get_patterns(&eccs); + let targets = into_targets(eccs); // Remove failed patterns - let (patterns, rewrite_rules): (Vec<_>, Vec<_>) = patterns + let (patterns, empty_wires, rewrite_rules): (Vec<_>, Vec<_>, Vec<_>) = patterns .into_iter() .zip(rewrite_rules) - .filter_map(|(p, r)| Some((p?, r))) - .unzip(); - let targets = into_targets(eccs); + .filter_map(|(p, r)| { + // Filter out target IDs where empty wires are not empty + let (pattern, pattern_empty_wires) = p?; + let targets = r + .into_iter() + .filter(|&id| { + let circ = &targets[id.0]; + let target_empty_wires: HashSet<_> = + empty_wires(&circ).into_iter().collect(); + pattern_empty_wires + .iter() + .all(|&w| target_empty_wires.contains(&w)) + }) + .collect(); + Some((pattern, pattern_empty_wires, targets)) + }) + .multiunzip(); let matcher = PatternMatcher::from_patterns(patterns); Self { matcher, targets, rewrite_rules, + empty_wires, } } @@ -151,7 +175,11 @@ impl Rewriter for ECCRewriter { .flat_map(|m| { let pattern_id = m.pattern_id(); self.get_targets(pattern_id).map(move |repl| { - m.to_rewrite(circ.base_hugr(), repl.clone()) + let mut repl = repl.clone(); + for &empty_qb in self.empty_wires[pattern_id.0].iter().rev() { + remove_empty_wire(&mut repl, empty_qb).unwrap(); + } + m.to_rewrite(circ.base_hugr(), repl) .expect("invalid replacement") }) }) @@ -198,11 +226,43 @@ fn get_rewrite_rules(rep_sets: &[EqCircClass]) -> Vec> { rewrite_rules } -fn get_patterns(rep_sets: &[EqCircClass]) -> Vec> { +/// For an equivalence class, return all valid patterns together with the +/// indices of the wires that have been removed in the pattern circuit. +fn get_patterns(rep_sets: &[EqCircClass]) -> Vec)>> { rep_sets .iter() .flat_map(|rs| rs.circuits()) - .map(|circ| CircuitPattern::try_from_circuit(circ).ok()) + .map(|circ| { + let empty_qbs = empty_wires(circ); + let mut circ = circ.clone(); + for &qb in empty_qbs.iter().rev() { + remove_empty_wire(&mut circ, qb).unwrap(); + } + CircuitPattern::try_from_circuit(&circ) + .ok() + .map(|circ| (circ, empty_qbs)) + }) + .collect() +} + +/// The port offsets of wires that are empty. +fn empty_wires(circ: &impl Circuit) -> Vec { + let inp = circ.input(); + circ.node_outputs(inp) + // Only consider dataflow edges + .filter(|&p| circ.get_optype(inp).signature().get(p).is_some()) + // Only consider ports linked to at most one other port + .filter_map(|p| Some((p, circ.linked_ports(inp, p).at_most_one().ok()?))) + // Ports are either connected to output or nothing + .filter_map(|(from, to)| { + if let Some((n, _)) = to { + // Wires connected to output + (n == circ.output()).then_some(from.index()) + } else { + // Wires connected to nothing + Some(from.index()) + } + }) .collect() } @@ -305,4 +365,15 @@ mod tests { let exp_n_eccs_of_len = [0, 4 * 2 + 5 * 3, 4, 5]; assert_eq!(n_eccs_of_len, exp_n_eccs_of_len); } + + /// Some inputs are left untouched: these parameters should be removed to + /// obtain convex patterns + #[test] + fn ecc_rewriter_empty_params() { + let test_file = "test_files/cx_cx_eccs.json"; + let rewriter = ECCRewriter::try_from_eccs_json_file(test_file).unwrap(); + + let cx_cx = cx_cx(); + assert_eq!(rewriter.get_rewrites(&cx_cx).len(), 1); + } } diff --git a/test_files/cx_cx_eccs.json b/test_files/cx_cx_eccs.json new file mode 100644 index 00000000..8cf933f3 --- /dev/null +++ b/test_files/cx_cx_eccs.json @@ -0,0 +1,8 @@ +[[], +{ +"7779_2": [ +[[3,0,0,0,["2acba3946000"],[7.23975983031379111e-02,-6.01244148396314765e-02]],[]] +,[[3,0,0,2,["2acba3946000"],[7.23975983031379111e-02,-6.01244148396314765e-02]],[["cx", ["Q0", "Q1"],["Q0", "Q1"]],["cx", ["Q0", "Q1"],["Q0", "Q1"]]]] +] +} +] \ No newline at end of file From 5257dcb75a45359f18e3f86c8f528ed8192e1319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Borgna?= <121866228+aborgna-q@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:55:19 +0200 Subject: [PATCH 08/10] fix: Preserve the tket1 register names and order when decoding/encoding (#157) --- src/json.rs | 6 + src/json/decoder.rs | 3 + src/json/encoder.rs | 103 +- src/json/tests.rs | 20 +- src/passes/chunks.rs | 2 + test_files/barenco_tof_10.json | 7576 ++++++++++++++++++++++++++++++++ 6 files changed, 7665 insertions(+), 45 deletions(-) create mode 100755 test_files/barenco_tof_10.json diff --git a/src/json.rs b/src/json.rs index 37ddbb59..c08afba5 100644 --- a/src/json.rs +++ b/src/json.rs @@ -30,8 +30,14 @@ use self::encoder::JsonEncoder; /// Prefix used for storing metadata in the hugr nodes. pub const METADATA_PREFIX: &str = "TKET1_JSON"; +/// The global phase specified as metadata. const METADATA_PHASE: &str = "TKET1_JSON.phase"; +/// The implicit permutation of qubits. const METADATA_IMPLICIT_PERM: &str = "TKET1_JSON.implicit_permutation"; +/// Explicit names for the input qubit registers. +const METADATA_Q_REGISTERS: &str = "TKET1_JSON.qubit_registers"; +/// Explicit names for the input bit registers. +const METADATA_B_REGISTERS: &str = "TKET1_JSON.bit_registers"; /// A JSON-serialized circuit that can be converted to a [`Hugr`]. pub trait TKETDecode: Sized { diff --git a/src/json/decoder.rs b/src/json/decoder.rs index 19c4cd0b..38c712ba 100644 --- a/src/json/decoder.rs +++ b/src/json/decoder.rs @@ -22,6 +22,7 @@ use tket_json_rs::circuit_json::SerialCircuit; use super::op::JsonOp; use super::{try_param_to_constant, METADATA_IMPLICIT_PERM, METADATA_PHASE}; use crate::extension::{LINEAR_BIT, REGISTRY}; +use crate::json::{METADATA_B_REGISTERS, METADATA_Q_REGISTERS}; use crate::symbolic_constant_op; /// The state of an in-progress [`DFGBuilder`] being built from a [`SerialCircuit`]. @@ -76,6 +77,8 @@ impl JsonDecoder { "name": serialcirc.name, METADATA_PHASE: serialcirc.phase, METADATA_IMPLICIT_PERM: serialcirc.implicit_permutation, + METADATA_Q_REGISTERS: serialcirc.qubits, + METADATA_B_REGISTERS: serialcirc.bits, }); dfg.set_metadata(metadata); diff --git a/src/json/encoder.rs b/src/json/encoder.rs index ca7fe187..cd026005 100644 --- a/src/json/encoder.rs +++ b/src/json/encoder.rs @@ -16,7 +16,10 @@ use crate::extension::LINEAR_BIT; use crate::ops::match_symb_const_op; use super::op::JsonOp; -use super::{OpConvertError, METADATA_IMPLICIT_PERM, METADATA_PHASE}; +use super::{ + OpConvertError, METADATA_B_REGISTERS, METADATA_IMPLICIT_PERM, METADATA_PHASE, + METADATA_Q_REGISTERS, +}; /// The state of an in-progress [`SerialCircuit`] being built from a [`Circuit`]. #[derive(Debug, PartialEq)] @@ -30,9 +33,13 @@ pub(super) struct JsonEncoder { /// The current commands commands: Vec, /// The TKET1 qubit registers associated to each qubit unit of the circuit. - qubit_regs: HashMap, + qubit_to_reg: HashMap, /// The TKET1 bit registers associated to each linear bit unit of the circuit. - bit_regs: HashMap, + bit_to_reg: HashMap, + /// The ordered TKET1 names for the input qubit registers. + qubit_registers: Vec, + /// The ordered TKET1 names for the input bit registers. + bit_registers: Vec, /// A register of wires with constant values, used to recover TKET1 /// parameters. parameters: HashMap, @@ -43,48 +50,63 @@ impl JsonEncoder { pub fn new(circ: &impl Circuit) -> Self { let name = circ.name().map(str::to_string); - // Compute the linear qubit and bit registers. Each one have independent - // indices starting from zero. - // - // TODO Throw an error on non-recognized unit types, or just ignore? - let mut bit_units = HashMap::new(); - let mut qubit_units = HashMap::new(); + let mut qubit_registers = vec![]; + let mut bit_registers = vec![]; + let mut phase = "0".to_string(); + let mut implicit_permutation = vec![]; + + // Recover other parameters stored in the metadata + if let Some(meta) = circ.get_metadata(circ.root()).as_object() { + if let Some(p) = meta.get(METADATA_PHASE) { + // TODO: Check for invalid encoded metadata + phase = p.as_str().unwrap().to_string(); + } + if let Some(perm) = meta.get(METADATA_IMPLICIT_PERM) { + // TODO: Check for invalid encoded metadata + implicit_permutation = serde_json::from_value(perm.clone()).unwrap(); + } + if let Some(q_regs) = meta.get(METADATA_Q_REGISTERS) { + qubit_registers = serde_json::from_value(q_regs.clone()).unwrap(); + } + if let Some(b_regs) = meta.get(METADATA_B_REGISTERS) { + bit_registers = serde_json::from_value(b_regs.clone()).unwrap(); + } + } + + // Map the Hugr units to tket1 register names. + // Uses the names from the metadata if available, or initializes new sequentially-numbered registers. + let mut bit_to_reg = HashMap::new(); + let mut qubit_to_reg = HashMap::new(); + let get_register = |registers: &mut Vec, prefix: &str, index| { + registers.get(index).cloned().unwrap_or_else(|| { + let r = Register(prefix.to_string(), vec![index as i64]); + registers.push(r.clone()); + r + }) + }; for (unit, _, ty) in circ.units() { if ty == QB_T { - let index = vec![qubit_units.len() as i64]; - let reg = circuit_json::Register("q".to_string(), index); - qubit_units.insert(unit, reg); + let index = qubit_to_reg.len(); + let reg = get_register(&mut qubit_registers, "q", index); + qubit_to_reg.insert(unit, reg); } else if ty == *LINEAR_BIT { - let index = vec![bit_units.len() as i64]; - let reg = circuit_json::Register("c".to_string(), index); - bit_units.insert(unit, reg); + let index = bit_to_reg.len(); + let reg = get_register(&mut bit_registers, "b", index); + bit_to_reg.insert(unit, reg.clone()); } } - let mut encoder = Self { + Self { name, - phase: "0".to_string(), - implicit_permutation: vec![], + phase, + implicit_permutation, commands: vec![], - qubit_regs: qubit_units, - bit_regs: bit_units, + qubit_to_reg, + bit_to_reg, + qubit_registers, + bit_registers, parameters: HashMap::new(), - }; - - // Encode other parameters stored in the metadata - if let Some(meta) = circ.get_metadata(circ.root()).as_object() { - if let Some(phase) = meta.get(METADATA_PHASE) { - // TODO: Check for invalid encoded metadata - encoder.phase = phase.as_str().unwrap().to_string(); - } - if let Some(implicit_perm) = meta.get(METADATA_IMPLICIT_PERM) { - // TODO: Check for invalid encoded metadata - encoder.implicit_permutation = - serde_json::from_value(implicit_perm.clone()).unwrap(); - } } - - encoder } /// Add a circuit command to the serialization. @@ -139,8 +161,8 @@ impl JsonEncoder { name: self.name, phase: self.phase, commands: self.commands, - qubits: self.qubit_regs.into_values().collect_vec(), - bits: self.bit_regs.into_values().collect_vec(), + qubits: self.qubit_registers, + bits: self.bit_registers, implicit_permutation: self.implicit_permutation, } } @@ -208,10 +230,11 @@ impl JsonEncoder { true } - fn unit_to_register(&self, unit: CircuitUnit) -> Option { - self.qubit_regs + /// Translate a linear [`CircuitUnit`] into a [`Register`], if possible. + fn unit_to_register(&self, unit: CircuitUnit) -> Option { + self.qubit_to_reg .get(&unit) - .or_else(|| self.bit_regs.get(&unit)) + .or_else(|| self.bit_to_reg.get(&unit)) .cloned() } } diff --git a/src/json/tests.rs b/src/json/tests.rs index 78c8cc3c..98b066be 100644 --- a/src/json/tests.rs +++ b/src/json/tests.rs @@ -1,6 +1,6 @@ //! General tests. -use std::collections::HashSet; +use std::io::BufReader; use hugr::Hugr; use rstest::rstest; @@ -64,16 +64,26 @@ fn json_roundtrip(#[case] circ_s: &str, #[case] num_commands: usize, #[case] num compare_serial_circs(&ser, &reser); } +#[rstest] +#[case::barenco_tof_10("test_files/barenco_tof_10.json")] +fn json_file_roundtrip(#[case] circ: impl AsRef) { + let reader = BufReader::new(std::fs::File::open(circ).unwrap()); + let ser: circuit_json::SerialCircuit = serde_json::from_reader(reader).unwrap(); + let circ: Hugr = ser.clone().decode().unwrap(); + let reser: SerialCircuit = SerialCircuit::encode(&circ).unwrap(); + compare_serial_circs(&ser, &reser); +} + fn compare_serial_circs(a: &SerialCircuit, b: &SerialCircuit) { assert_eq!(a.name, b.name); assert_eq!(a.phase, b.phase); - let qubits_a: HashSet<_> = a.qubits.iter().collect(); - let qubits_b: HashSet<_> = b.qubits.iter().collect(); + let qubits_a: Vec<_> = a.qubits.iter().collect(); + let qubits_b: Vec<_> = b.qubits.iter().collect(); assert_eq!(qubits_a, qubits_b); - let bits_a: HashSet<_> = a.bits.iter().collect(); - let bits_b: HashSet<_> = b.bits.iter().collect(); + let bits_a: Vec<_> = a.bits.iter().collect(); + let bits_b: Vec<_> = b.bits.iter().collect(); assert_eq!(bits_a, bits_b); assert_eq!(a.implicit_permutation, b.implicit_permutation); diff --git a/src/passes/chunks.rs b/src/passes/chunks.rs index ff10d3b7..2f436cdf 100644 --- a/src/passes/chunks.rs +++ b/src/passes/chunks.rs @@ -263,6 +263,8 @@ impl CircuitChunks { } } + reassembled.set_metadata(root, self.root_meta)?; + Ok(reassembled) } diff --git a/test_files/barenco_tof_10.json b/test_files/barenco_tof_10.json new file mode 100755 index 00000000..ebb40575 --- /dev/null +++ b/test_files/barenco_tof_10.json @@ -0,0 +1,7576 @@ +{ + "phase": "0.0", + "commands": [ + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 18 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ], + [ + "q", + [ + 18 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 18 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 9 + ] + ], + [ + "q", + [ + 18 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 18 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ], + [ + "q", + [ + 18 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 9 + ] + ], + [ + "q", + [ + 18 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 9 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 9 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 1 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 0 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 1 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 0 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ], + [ + "q", + [ + 18 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 18 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 9 + ] + ], + [ + "q", + [ + 18 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 18 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ], + [ + "q", + [ + 18 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 9 + ] + ], + [ + "q", + [ + 18 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 18 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 9 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 9 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 0 + ] + ], + [ + "q", + [ + 1 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 0 + ] + ], + [ + "q", + [ + 1 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 1 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 0 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 1 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 0 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "H", + "n_qb": 1, + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 17 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 16 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 15 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 14 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 13 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 12 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 11 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "Rz", + "n_qb": 1, + "params": [ + "-0.25" + ], + "signature": [ + "Q" + ] + }, + "args": [ + [ + "q", + [ + 10 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 0 + ] + ], + [ + "q", + [ + 1 + ] + ] + ] + }, + { + "op": { + "type": "CX", + "n_qb": 2, + "signature": [ + "Q", + "Q" + ] + }, + "args": [ + [ + "q", + [ + 0 + ] + ], + [ + "q", + [ + 1 + ] + ] + ] + } + ], + "qubits": [ + [ + "q", + [ + 1 + ] + ], + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 0 + ] + ], + [ + "q", + [ + 13 + ] + ], + [ + "q", + [ + 16 + ] + ], + [ + "q", + [ + 10 + ] + ], + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 18 + ] + ], + [ + "q", + [ + 12 + ] + ], + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 14 + ] + ], + [ + "q", + [ + 9 + ] + ], + [ + "q", + [ + 15 + ] + ], + [ + "q", + [ + 17 + ] + ], + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 11 + ] + ] + ], + "bits": [], + "implicit_permutation": [ + [ + [ + "q", + [ + 0 + ] + ], + [ + "q", + [ + 0 + ] + ] + ], + [ + [ + "q", + [ + 1 + ] + ], + [ + "q", + [ + 1 + ] + ] + ], + [ + [ + "q", + [ + 2 + ] + ], + [ + "q", + [ + 2 + ] + ] + ], + [ + [ + "q", + [ + 3 + ] + ], + [ + "q", + [ + 3 + ] + ] + ], + [ + [ + "q", + [ + 4 + ] + ], + [ + "q", + [ + 4 + ] + ] + ], + [ + [ + "q", + [ + 5 + ] + ], + [ + "q", + [ + 5 + ] + ] + ], + [ + [ + "q", + [ + 6 + ] + ], + [ + "q", + [ + 6 + ] + ] + ], + [ + [ + "q", + [ + 7 + ] + ], + [ + "q", + [ + 7 + ] + ] + ], + [ + [ + "q", + [ + 8 + ] + ], + [ + "q", + [ + 8 + ] + ] + ], + [ + [ + "q", + [ + 9 + ] + ], + [ + "q", + [ + 9 + ] + ] + ], + [ + [ + "q", + [ + 10 + ] + ], + [ + "q", + [ + 10 + ] + ] + ], + [ + [ + "q", + [ + 11 + ] + ], + [ + "q", + [ + 11 + ] + ] + ], + [ + [ + "q", + [ + 12 + ] + ], + [ + "q", + [ + 12 + ] + ] + ], + [ + [ + "q", + [ + 13 + ] + ], + [ + "q", + [ + 13 + ] + ] + ], + [ + [ + "q", + [ + 14 + ] + ], + [ + "q", + [ + 14 + ] + ] + ], + [ + [ + "q", + [ + 15 + ] + ], + [ + "q", + [ + 15 + ] + ] + ], + [ + [ + "q", + [ + 16 + ] + ], + [ + "q", + [ + 16 + ] + ] + ], + [ + [ + "q", + [ + 17 + ] + ], + [ + "q", + [ + 17 + ] + ] + ], + [ + [ + "q", + [ + 18 + ] + ], + [ + "q", + [ + 18 + ] + ] + ] + ] +} \ No newline at end of file From 742e64cc2676edc163ceb8f9c07f39665828f6d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Borgna?= <121866228+aborgna-q@users.noreply.github.com> Date: Tue, 3 Oct 2023 15:23:59 +0200 Subject: [PATCH 09/10] fix: Don't run miri on tests that open files (#159) Fixes the failing CI test in `HEAD`. https://github.com/CQCL-DEV/tket2/actions/runs/6393267172/job/17352161422 --- src/json/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/json/tests.rs b/src/json/tests.rs index 98b066be..90fb3253 100644 --- a/src/json/tests.rs +++ b/src/json/tests.rs @@ -65,6 +65,7 @@ fn json_roundtrip(#[case] circ_s: &str, #[case] num_commands: usize, #[case] num } #[rstest] +#[cfg_attr(miri, ignore)] // Opening files is not supported in (isolated) miri #[case::barenco_tof_10("test_files/barenco_tof_10.json")] fn json_file_roundtrip(#[case] circ: impl AsRef) { let reader = BufReader::new(std::fs::File::open(circ).unwrap()); From 0dcf5e00d7346158f1e9f1df9787eb5a7c49e8e3 Mon Sep 17 00:00:00 2001 From: Luca Mondada <72734770+lmondada@users.noreply.github.com> Date: Tue, 3 Oct 2023 15:33:30 +0200 Subject: [PATCH 10/10] feat: Improve TASO cost function and rewrite strategies (#154) --- src/ops.rs | 10 ++ src/optimiser/taso.rs | 136 +++++++++++----- src/optimiser/taso/log.rs | 22 +-- src/rewrite/ecc_rewriter.rs | 6 +- src/rewrite/strategy.rs | 308 +++++++++++++++++++++++++++++------- test_files/small_eccs.json | 5 + 6 files changed, 377 insertions(+), 110 deletions(-) diff --git a/src/ops.rs b/src/ops.rs index 1c1cb69a..1ac5c230 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -200,6 +200,16 @@ impl T2Op { _ => vec![], } } + + /// Check if this op is a quantum op. + pub fn is_quantum(&self) -> bool { + use T2Op::*; + match self { + H | CX | T | S | X | Y | Z | Tdg | Sdg | ZZMax | RzF64 | RxF64 | PhasedX | ZZPhase + | CZ | TK1 => true, + AngleAdd | Measure => false, + } + } } /// Initialize a new custom symbolic expression constant op from a string. diff --git a/src/optimiser/taso.rs b/src/optimiser/taso.rs index a231aaf8..7fc4c9b0 100644 --- a/src/optimiser/taso.rs +++ b/src/optimiser/taso.rs @@ -22,6 +22,7 @@ use crossbeam_channel::select; pub use eq_circ_class::{load_eccs_json_file, EqCircClass}; pub use log::TasoLogger; +use std::fmt; use std::num::NonZeroUsize; use std::time::{Duration, Instant}; @@ -52,28 +53,30 @@ use crate::rewrite::Rewriter; /// [Quartz]: https://arxiv.org/abs/2204.09033 /// [TASO]: https://dl.acm.org/doi/10.1145/3341301.3359630 #[derive(Clone, Debug)] -pub struct TasoOptimiser { +pub struct TasoOptimiser { rewriter: R, strategy: S, - cost: C, } -impl TasoOptimiser { +impl TasoOptimiser { /// Create a new TASO optimiser. - pub fn new(rewriter: R, strategy: S, cost: C) -> Self { - Self { - rewriter, - strategy, - cost, - } + pub fn new(rewriter: R, strategy: S) -> Self { + Self { rewriter, strategy } + } + + fn cost(&self, circ: &Hugr) -> S::Cost + where + S: RewriteStrategy, + { + self.strategy.circuit_cost(circ) } } -impl TasoOptimiser +impl TasoOptimiser where R: Rewriter + Send + Clone + 'static, - S: RewriteStrategy + Send + Clone + 'static, - C: Fn(&Hugr) -> usize + Send + Sync + Clone + 'static, + S: RewriteStrategy + Send + Sync + Clone + 'static, + S::Cost: fmt::Debug + serde::Serialize, { /// Run the TASO optimiser on a circuit. /// @@ -103,15 +106,19 @@ where let start_time = Instant::now(); let mut best_circ = circ.clone(); - let mut best_circ_cost = (self.cost)(circ); - logger.log_best(best_circ_cost); + let mut best_circ_cost = self.cost(circ); + logger.log_best(&best_circ_cost); // Hash of seen circuits. Dot not store circuits as this map gets huge let mut seen_hashes: FxHashSet<_> = FromIterator::from_iter([(circ.circuit_hash())]); // The priority queue of circuits to be processed (this should not get big) const PRIORITY_QUEUE_CAPACITY: usize = 10_000; - let mut pq = HugrPQ::with_capacity(&self.cost, PRIORITY_QUEUE_CAPACITY); + let cost_fn = { + let strategy = self.strategy.clone(); + move |circ: &'_ Hugr| strategy.circuit_cost(circ) + }; + let mut pq = HugrPQ::with_capacity(cost_fn, PRIORITY_QUEUE_CAPACITY); pq.push(circ.clone()); let mut circ_cnt = 1; @@ -120,7 +127,7 @@ where if cost < best_circ_cost { best_circ = circ.clone(); best_circ_cost = cost; - logger.log_best(best_circ_cost); + logger.log_best(&best_circ_cost); } let rewrites = self.rewriter.get_rewrites(&circ); @@ -168,15 +175,19 @@ where const PRIORITY_QUEUE_CAPACITY: usize = 10_000; // multi-consumer priority channel for queuing circuits to be processed by the workers + let cost_fn = { + let strategy = self.strategy.clone(); + move |circ: &'_ Hugr| strategy.circuit_cost(circ) + }; let (tx_work, rx_work) = - HugrPriorityChannel::init((self.cost).clone(), PRIORITY_QUEUE_CAPACITY * n_threads); + HugrPriorityChannel::init(cost_fn, PRIORITY_QUEUE_CAPACITY * n_threads); // channel for sending circuits from threads back to main let (tx_result, rx_result) = crossbeam_channel::unbounded(); let initial_circ_hash = circ.circuit_hash(); let mut best_circ = circ.clone(); - let mut best_circ_cost = (self.cost)(&best_circ); - logger.log_best(best_circ_cost); + let mut best_circ_cost = self.cost(&best_circ); + logger.log_best(&best_circ_cost); // Hash of seen circuits. Dot not store circuits as this map gets huge let mut seen_hashes: FxHashSet<_> = FromIterator::from_iter([(initial_circ_hash)]); @@ -239,13 +250,13 @@ where } seen_hashes.insert(*circ_hash); - let cost = (self.cost)(circ); + let cost = self.cost(circ); // Check if we got a new best circuit if cost < best_circ_cost { best_circ = circ.clone(); best_circ_cost = cost; - logger.log_best(best_circ_cost); + logger.log_best(&best_circ_cost); } jobs_sent += 1; } @@ -289,32 +300,29 @@ where #[cfg(feature = "portmatching")] mod taso_default { - use hugr::ops::OpType; - use hugr::HugrView; use std::io; use std::path::Path; - use crate::ops::op_matches; + use hugr::ops::OpType; + use crate::rewrite::ecc_rewriter::RewriterSerialisationError; - use crate::rewrite::strategy::ExhaustiveRewriteStrategy; + use crate::rewrite::strategy::NonIncreasingGateCountStrategy; use crate::rewrite::ECCRewriter; - use crate::T2Op; use super::*; /// The default TASO optimiser using ECC sets. pub type DefaultTasoOptimiser = TasoOptimiser< ECCRewriter, - ExhaustiveRewriteStrategy bool>, - fn(&Hugr) -> usize, + NonIncreasingGateCountStrategy usize, fn(&OpType) -> usize>, >; impl DefaultTasoOptimiser { /// A sane default optimiser using the given ECC sets. pub fn default_with_eccs_json_file(eccs_path: impl AsRef) -> io::Result { let rewriter = ECCRewriter::try_from_eccs_json_file(eccs_path)?; - let strategy = ExhaustiveRewriteStrategy::exhaustive_cx(); - Ok(TasoOptimiser::new(rewriter, strategy, num_cx_gates)) + let strategy = NonIncreasingGateCountStrategy::default_cx(); + Ok(TasoOptimiser::new(rewriter, strategy)) } /// A sane default optimiser using a precompiled binary rewriter. @@ -322,16 +330,68 @@ mod taso_default { rewriter_path: impl AsRef, ) -> Result { let rewriter = ECCRewriter::load_binary(rewriter_path)?; - let strategy = ExhaustiveRewriteStrategy::exhaustive_cx(); - Ok(TasoOptimiser::new(rewriter, strategy, num_cx_gates)) + let strategy = NonIncreasingGateCountStrategy::default_cx(); + Ok(TasoOptimiser::new(rewriter, strategy)) } } - - fn num_cx_gates(circ: &Hugr) -> usize { - circ.nodes() - .filter(|&n| op_matches(circ.get_optype(n), T2Op::CX)) - .count() - } } #[cfg(feature = "portmatching")] pub use taso_default::DefaultTasoOptimiser; + +#[cfg(test)] +#[cfg(feature = "portmatching")] +mod tests { + use hugr::{ + builder::{DFGBuilder, Dataflow, DataflowHugr}, + extension::prelude::QB_T, + std_extensions::arithmetic::float_types::FLOAT64_TYPE, + types::FunctionType, + Hugr, + }; + use rstest::{fixture, rstest}; + + use crate::{extension::REGISTRY, Circuit, T2Op}; + + use super::{DefaultTasoOptimiser, TasoOptimiser}; + + #[fixture] + fn rz_rz() -> Hugr { + let input_t = vec![QB_T, FLOAT64_TYPE, FLOAT64_TYPE]; + let output_t = vec![QB_T]; + let mut h = DFGBuilder::new(FunctionType::new(input_t, output_t)).unwrap(); + + let mut inps = h.input_wires(); + let qb = inps.next().unwrap(); + let f1 = inps.next().unwrap(); + let f2 = inps.next().unwrap(); + + let res = h.add_dataflow_op(T2Op::RzF64, [qb, f1]).unwrap(); + let qb = res.outputs().next().unwrap(); + let res = h.add_dataflow_op(T2Op::RzF64, [qb, f2]).unwrap(); + let qb = res.outputs().next().unwrap(); + + h.finish_hugr_with_outputs([qb], ®ISTRY).unwrap() + } + + #[fixture] + fn taso_opt() -> DefaultTasoOptimiser { + TasoOptimiser::default_with_eccs_json_file("test_files/small_eccs.json").unwrap() + } + + #[rstest] + fn rz_rz_cancellation(rz_rz: Hugr, taso_opt: DefaultTasoOptimiser) { + let opt_rz = taso_opt.optimise(&rz_rz, None, 1.try_into().unwrap()); + let cmds = opt_rz + .commands() + .map(|cmd| { + ( + cmd.optype().try_into().unwrap(), + cmd.inputs().count(), + cmd.outputs().count(), + ) + }) + .collect::>(); + let exp_cmds = vec![(T2Op::AngleAdd, 2, 1), (T2Op::RzF64, 2, 1)]; + assert_eq!(cmds, exp_cmds); + } +} diff --git a/src/optimiser/taso/log.rs b/src/optimiser/taso/log.rs index 39969ee6..9a44f717 100644 --- a/src/optimiser/taso/log.rs +++ b/src/optimiser/taso/log.rs @@ -1,6 +1,6 @@ //! Logging utilities for the TASO optimiser. -use std::io; +use std::{fmt::Debug, io}; /// Logging configuration for the TASO optimiser. #[derive(Default)] @@ -35,8 +35,8 @@ impl<'w> TasoLogger<'w> { /// Log a new best candidate #[inline] - pub fn log_best(&mut self, best_cost: usize) { - self.log(format!("new best of size {}", best_cost)); + pub fn log_best(&mut self, best_cost: C) { + self.log(format!("new best of size {:?}", best_cost)); if let Some(csv_writer) = self.circ_candidates_csv.as_mut() { csv_writer.serialize(BestCircSer::new(best_cost)).unwrap(); csv_writer.flush().unwrap(); @@ -45,10 +45,10 @@ impl<'w> TasoLogger<'w> { /// Log the final optimised circuit #[inline] - pub fn log_processing_end( + pub fn log_processing_end( &self, circuit_count: usize, - best_cost: usize, + best_cost: C, needs_joining: bool, timeout: bool, ) { @@ -57,7 +57,7 @@ impl<'w> TasoLogger<'w> { } self.log("Optimisation finished"); self.log(format!("Tried {circuit_count} circuits")); - self.log(format!("END RESULT: {}", best_cost)); + self.log(format!("END RESULT: {:?}", best_cost)); if needs_joining { self.log("Joining worker threads"); } @@ -98,14 +98,14 @@ impl<'w> TasoLogger<'w> { // // TODO: Replace this fixed logging. Report back intermediate results. #[derive(serde::Serialize, Clone, Debug)] -struct BestCircSer { - circ_len: usize, +struct BestCircSer { + circ_cost: C, time: String, } -impl BestCircSer { - fn new(circ_len: usize) -> Self { +impl BestCircSer { + fn new(circ_cost: C) -> Self { let time = chrono::Local::now().to_rfc3339(); - Self { circ_len, time } + Self { circ_cost, time } } } diff --git a/src/rewrite/ecc_rewriter.rs b/src/rewrite/ecc_rewriter.rs index c8e95f0f..6343837c 100644 --- a/src/rewrite/ecc_rewriter.rs +++ b/src/rewrite/ecc_rewriter.rs @@ -338,7 +338,7 @@ mod tests { let test_file = "test_files/small_eccs.json"; let rewriter = ECCRewriter::try_from_eccs_json_file(test_file).unwrap(); assert_eq!(rewriter.rewrite_rules.len(), rewriter.matcher.n_patterns()); - assert_eq!(rewriter.targets.len(), 5 * 4 + 4 * 3); + assert_eq!(rewriter.targets.len(), 5 * 4 + 5 * 3); // Assert that the rewrite rules are correct, i.e that the rewrite // rules in the slice (k..=k+t) is given by [[k+1, ..., k+t], [k], ..., [k]] @@ -361,8 +361,8 @@ mod tests { curr_repr = TargetID(i); } } - // There should be 4x ECCs of size 3 and 5x ECCs of size 4 - let exp_n_eccs_of_len = [0, 4 * 2 + 5 * 3, 4, 5]; + // There should be 5x ECCs of size 3 and 5x ECCs of size 4 + let exp_n_eccs_of_len = [0, 5 * 2 + 5 * 3, 5, 5]; assert_eq!(n_eccs_of_len, exp_n_eccs_of_len); } diff --git a/src/rewrite/strategy.rs b/src/rewrite/strategy.rs index 3df7ceeb..38a63e3f 100644 --- a/src/rewrite/strategy.rs +++ b/src/rewrite/strategy.rs @@ -2,18 +2,23 @@ //! //! This module contains the [`RewriteStrategy`] trait, which is currently //! implemented by -//! - [`GreedyRewriteStrategy`], which applies as many rewrites as possible +//! - [`GreedyRewriteStrategy`], which applies as many rewrites as possible //! on one circuit, and -//! - [`ExhaustiveRewriteStrategy`], which clones the original circuit as many -//! times as there are possible rewrites and applies a different rewrite -//! to every circuit. +//! - exhaustive strategies, which clone the original circuit and explore every +//! possible rewrite (with some pruning strategy): +//! - [`NonIncreasingGateCountStrategy`], which only considers rewrites that +//! do not increase some cost function (e.g. cx gate count, implemented as +//! [`NonIncreasingGateCountStrategy::default_cx`]), and +//! - [`ExhaustiveGammaStrategy`], which ignores rewrites that increase the +//! cost function beyond a threshold given by a f64 parameter gamma. -use std::collections::HashSet; +use std::{collections::HashSet, fmt::Debug, iter::Sum}; +use derive_more::From; use hugr::{ops::OpType, Hugr, HugrView, Node}; use itertools::Itertools; -use crate::{ops::op_matches, T2Op}; +use crate::{ops::op_matches, Circuit, T2Op}; use super::CircuitRewrite; @@ -23,13 +28,22 @@ use super::CircuitRewrite; /// to a circuit according to a strategy. It returns a list of new circuits, /// each obtained by applying one or several non-overlapping rewrites to the /// original circuit. +/// +/// It also assign every circuit a totally ordered cost that can be used when +/// using rewrites for circuit optimisation. pub trait RewriteStrategy { + /// The circuit cost to be minised. + type Cost: Ord; + /// Apply a set of rewrites to a circuit. fn apply_rewrites( &self, rewrites: impl IntoIterator, circ: &Hugr, ) -> Vec; + + /// The cost of a circuit. + fn circuit_cost(&self, circ: &Hugr) -> Self::Cost; } /// A rewrite strategy applying as many non-overlapping rewrites as possible. @@ -45,6 +59,8 @@ pub trait RewriteStrategy { pub struct GreedyRewriteStrategy; impl RewriteStrategy for GreedyRewriteStrategy { + type Cost = usize; + #[tracing::instrument(skip_all)] fn apply_rewrites( &self, @@ -73,87 +89,224 @@ impl RewriteStrategy for GreedyRewriteStrategy { } vec![circ] } + + fn circuit_cost(&self, circ: &Hugr) -> Self::Cost { + circ.num_gates() + } +} + +/// Exhaustive strategies based on cost functions and thresholds. +/// +/// Every possible rewrite is applied to a copy of the input circuit. Thus for +/// one circuit, up to `n` rewritten circuits will be returned, each obtained +/// by applying one of the `n` rewrites to the original circuit. +/// +/// Whether a rewrite is allowed or not is determined by a cost function and a +/// threshold function: if the cost of the target of the rewrite is below the +/// threshold given by the cost of the original circuit, the rewrite is +/// performed. +/// +/// The cost function must return a value of type `Self::OpCost`. All op costs +/// are summed up to obtain a total cost that is then compared using the +/// threshold function. +pub trait ExhaustiveThresholdStrategy { + /// The cost of a single operation. + type OpCost; + /// The sum of the cost of all operations in a circuit. + type SumOpCost; + + /// Whether the rewrite is allowed or not, based on the cost of the pattern and target. + fn threshold(&self, pattern_cost: &Self::SumOpCost, target_cost: &Self::SumOpCost) -> bool; + + /// The cost of a single operation. + fn op_cost(&self, op: &OpType) -> Self::OpCost; +} + +impl RewriteStrategy for T +where + T::SumOpCost: Sum + Ord, +{ + type Cost = T::SumOpCost; + + #[tracing::instrument(skip_all)] + fn apply_rewrites( + &self, + rewrites: impl IntoIterator, + circ: &Hugr, + ) -> Vec { + rewrites + .into_iter() + .filter(|rw| { + let pattern_cost = pre_rewrite_cost(rw, circ, |op| self.op_cost(op)); + let target_cost = post_rewrite_cost(rw, circ, |op| self.op_cost(op)); + self.threshold(&pattern_cost, &target_cost) + }) + .map(|rw| { + let mut circ = circ.clone(); + rw.apply(&mut circ).expect("invalid pattern match"); + circ + }) + .collect() + } + + fn circuit_cost(&self, circ: &Hugr) -> Self::Cost { + cost(circ.nodes(), circ, |op| self.op_cost(op)) + } +} + +/// Exhaustive rewrite strategy allowing smaller or equal cost rewrites. +/// +/// Rewrites are permitted based on a cost function called the major cost: if +/// the major cost of the target of the rewrite is smaller or equal to the major +/// cost of the pattern, the rewrite is allowed. +/// +/// A second cost function, the minor cost, is used as a tie breaker: within +/// circuits with the same major cost, the circuit ordering prioritises circuits +/// with a smaller minor cost. +/// +/// An example would be to use the number of CX gates as major cost and the +/// total number of gates as minor cost. Compared to a [`ExhaustiveGammaStrategy`], +/// that would only order circuits based on the number of CX gates, this creates +/// a less flat optimisation landscape. +#[derive(Debug, Clone)] +pub struct NonIncreasingGateCountStrategy { + major_cost: C1, + minor_cost: C2, +} + +impl ExhaustiveThresholdStrategy for NonIncreasingGateCountStrategy +where + C1: Fn(&OpType) -> usize, + C2: Fn(&OpType) -> usize, +{ + type OpCost = MajorMinorCost; + type SumOpCost = MajorMinorCost; + + fn threshold(&self, pattern_cost: &Self::SumOpCost, target_cost: &Self::SumOpCost) -> bool { + target_cost.major <= pattern_cost.major + } + + fn op_cost(&self, op: &OpType) -> Self::OpCost { + ((self.major_cost)(op), (self.minor_cost)(op)).into() + } +} + +impl NonIncreasingGateCountStrategy usize, fn(&OpType) -> usize> { + /// Non-increasing rewrite strategy based on CX count. + /// + /// The minor cost to break ties between equal CX counts is the number of + /// quantum gates. + /// + /// This is probably a good default for NISQ-y circuit optimisation. + pub fn default_cx() -> Self { + Self { + major_cost: |op| is_cx(op) as usize, + minor_cost: |op| is_quantum(op) as usize, + } + } } -/// A rewrite strategy that explores applying each rewrite to copies of the -/// circuit. +/// Exhaustive rewrite strategy allowing rewrites with bounded cost increase. /// /// The parameter gamma controls how greedy the algorithm should be. It allows /// a rewrite C1 -> C2 if C2 has at most gamma times the cost of C1: /// /// $cost(C2) < gamma * cost(C1)$ /// -/// The cost function is given by the number of operations in the circuit that -/// satisfy a given Op predicate. This allows for instance to use the total -/// number of gates (true predicate) or the number of CX gates as cost function. +/// The cost function is given by the sum of the cost of each operation in the +/// circuit. This allows for instance to use of the total number of gates (true +/// predicate), the number of CX gates or a weighted sum of gate types as cost +/// functions. /// /// gamma = 1 is the greedy strategy where a rewrite is only allowed if it /// strictly reduces the gate count. The default is gamma = 1.0001 (as set in /// the Quartz paper) and the number of CX gates. This essentially allows /// rewrites that improve or leave the number of CX unchanged. #[derive(Debug, Clone)] -pub struct ExhaustiveRewriteStrategy

{ +pub struct ExhaustiveGammaStrategy { /// The gamma parameter. pub gamma: f64, - /// Ops to count for cost function. - pub op_predicate: P, + /// A cost function for each operation. + pub op_cost: C, } -impl

ExhaustiveRewriteStrategy

{ +impl usize> ExhaustiveThresholdStrategy for ExhaustiveGammaStrategy { + type OpCost = usize; + type SumOpCost = usize; + + fn threshold(&self, &pattern_cost: &Self::SumOpCost, &target_cost: &Self::SumOpCost) -> bool { + (target_cost as f64) < self.gamma * (pattern_cost as f64) + } + + fn op_cost(&self, op: &OpType) -> Self::OpCost { + (self.op_cost)(op) + } +} + +impl ExhaustiveGammaStrategy { /// New exhaustive rewrite strategy with provided predicate. /// /// The gamma parameter is set to the default 1.0001. - pub fn with_predicate(op_predicate: P) -> Self { + pub fn with_cost(op_cost: C) -> Self { Self { gamma: 1.0001, - op_predicate, + op_cost, } } /// New exhaustive rewrite strategy with provided gamma and predicate. - pub fn new(gamma: f64, op_predicate: P) -> Self { - Self { - gamma, - op_predicate, - } + pub fn new(gamma: f64, op_cost: C) -> Self { + Self { gamma, op_cost } } } -impl ExhaustiveRewriteStrategy bool> { +impl ExhaustiveGammaStrategy usize> { /// Exhaustive rewrite strategy with CX count cost function. /// /// The gamma parameter is set to the default 1.0001. This is a good default /// choice for NISQ-y circuits, where CX gates are the most expensive. pub fn exhaustive_cx() -> Self { - ExhaustiveRewriteStrategy::with_predicate(is_cx) + ExhaustiveGammaStrategy::with_cost(|op| is_cx(op) as usize) } /// Exhaustive rewrite strategy with CX count cost function and provided gamma. pub fn exhaustive_cx_with_gamma(gamma: f64) -> Self { - ExhaustiveRewriteStrategy::new(gamma, is_cx) + ExhaustiveGammaStrategy::new(gamma, |op| is_cx(op) as usize) } } -impl bool> RewriteStrategy for ExhaustiveRewriteStrategy

{ - #[tracing::instrument(skip_all)] - fn apply_rewrites( - &self, - rewrites: impl IntoIterator, - circ: &Hugr, - ) -> Vec { - rewrites - .into_iter() - .filter(|rw| { - let old_count = pre_rewrite_cost(rw, circ, &self.op_predicate) as f64; - let new_count = post_rewrite_cost(rw, circ, &self.op_predicate) as f64; - new_count < old_count * self.gamma - }) - .map(|rw| { - let mut circ = circ.clone(); - rw.apply(&mut circ).expect("invalid pattern match"); - circ - }) - .collect() +/// A pair of major and minor cost. +/// +/// This is used to order circuits based on major cost first, then minor cost. +/// A typical example would be CX count as major cost and total gate count as +/// minor cost. +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, From)] +pub struct MajorMinorCost { + major: usize, + minor: usize, +} + +// Serialise as string so that it is easy to write to CSV +impl serde::Serialize for MajorMinorCost { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&format!("{:?}", self)) + } +} + +impl Debug for MajorMinorCost { + // TODO: A nicer print for the logs + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "(major={}, minor={})", self.major, self.minor) + } +} + +impl Sum for MajorMinorCost { + fn sum>(iter: I) -> Self { + iter.reduce(|a, b| (a.major + b.major, a.minor + b.minor).into()) + .unwrap_or_default() } } @@ -161,25 +314,40 @@ fn is_cx(op: &OpType) -> bool { op_matches(op, T2Op::CX) } -fn cost( - nodes: impl IntoIterator, - circ: &Hugr, - pred: impl Fn(&OpType) -> bool, -) -> usize { +fn is_quantum(op: &OpType) -> bool { + let Ok(op): Result = op.try_into() else { + return false; + }; + op.is_quantum() +} + +fn cost(nodes: impl IntoIterator, circ: &Hugr, op_cost: C) -> S +where + C: Fn(&OpType) -> T, + S: Sum, +{ nodes .into_iter() - .filter(|n| { - let op = circ.get_optype(*n); - pred(op) + .map(|n| { + let op = circ.get_optype(n); + op_cost(op) }) - .count() + .sum() } -fn pre_rewrite_cost(rw: &CircuitRewrite, circ: &Hugr, pred: impl Fn(&OpType) -> bool) -> usize { +fn pre_rewrite_cost(rw: &CircuitRewrite, circ: &Hugr, pred: C) -> S +where + C: Fn(&OpType) -> T, + S: Sum, +{ cost(rw.subcircuit().nodes().iter().copied(), circ, pred) } -fn post_rewrite_cost(rw: &CircuitRewrite, circ: &Hugr, pred: impl Fn(&OpType) -> bool) -> usize { +fn post_rewrite_cost(rw: &CircuitRewrite, circ: &Hugr, pred: C) -> S +where + C: Fn(&OpType) -> T, + S: Sum, +{ cost(rw.replacement().nodes(), circ, pred) } @@ -258,7 +426,7 @@ mod tests { rw_to_empty(&circ, cx_gates[9..10].to_vec()), ]; - let strategy = ExhaustiveRewriteStrategy::exhaustive_cx(); + let strategy = ExhaustiveGammaStrategy::exhaustive_cx(); let rewritten = strategy.apply_rewrites(rws, &circ); let exp_circ_lens = HashSet::from_iter([8, 6, 9]); let circ_lens: HashSet<_> = rewritten.iter().map(|c| c.num_gates()).collect(); @@ -280,10 +448,34 @@ mod tests { rw_to_empty(&circ, cx_gates[9..10].to_vec()), ]; - let strategy = ExhaustiveRewriteStrategy::exhaustive_cx_with_gamma(10.); + let strategy = ExhaustiveGammaStrategy::exhaustive_cx_with_gamma(10.); let rewritten = strategy.apply_rewrites(rws, &circ); let exp_circ_lens = HashSet::from_iter([8, 17, 6, 9]); let circ_lens: HashSet<_> = rewritten.iter().map(|c| c.num_gates()).collect(); assert_eq!(circ_lens, exp_circ_lens); } + + #[test] + fn test_exhaustive_default_cx_cost() { + let strat = NonIncreasingGateCountStrategy::default_cx(); + let circ = n_cx(3); + assert_eq!(strat.circuit_cost(&circ), (3, 3).into()); + let circ = build_simple_circuit(2, |circ| { + circ.append(T2Op::CX, [0, 1])?; + circ.append(T2Op::X, [0])?; + circ.append(T2Op::X, [1])?; + Ok(()) + }) + .unwrap(); + assert_eq!(strat.circuit_cost(&circ), (1, 3).into()); + } + + #[test] + fn test_exhaustive_default_cx_threshold() { + let strat = NonIncreasingGateCountStrategy::default_cx(); + assert!(strat.threshold(&(3, 0).into(), &(3, 0).into())); + assert!(strat.threshold(&(3, 0).into(), &(3, 5).into())); + assert!(!strat.threshold(&(3, 10).into(), &(4, 0).into())); + assert!(strat.threshold(&(3, 0).into(), &(1, 5).into())); + } } diff --git a/test_files/small_eccs.json b/test_files/small_eccs.json index 787d10a6..06e29e54 100644 --- a/test_files/small_eccs.json +++ b/test_files/small_eccs.json @@ -50,5 +50,10 @@ ,[[2,0,0,3,["116baec0e00cd"],[5.53564910528592469e-01,2.63161770391809990e-01]],[["cx", ["Q1", "Q0"],["Q1", "Q0"]],["x", ["Q1"],["Q1"]],["cx", ["Q1", "Q0"],["Q1", "Q0"]]]] ,[[2,0,0,3,["116baec0e00cd"],[5.53564910528592469e-01,2.63161770391809990e-01]],[["cx", ["Q1", "Q0"],["Q1", "Q0"]],["x", ["Q1"],["Q1"]],["cx", ["Q1", "Q0"],["Q1", "Q0"]]]] ] +,"6701_3": [ +[[1,2,3,2,["a720832fadf2"],[2.22710267824423158e-01,2.92349563045663841e-01]],[["add", ["P2"],["P0", "P1"]],["rz", ["Q0"],["Q0", "P2"]]]] +,[[1,2,2,2,["a720832fadf2"],[2.22710267824423158e-01,2.92349563045663841e-01]],[["rz", ["Q0"],["Q0", "P0"]],["rz", ["Q0"],["Q0", "P1"]]]] +,[[1,2,2,2,["a720832fadf2"],[2.22710267824423103e-01,2.92349563045663619e-01]],[["rz", ["Q0"],["Q0", "P1"]],["rz", ["Q0"],["Q0", "P0"]]]] +] } ] \ No newline at end of file