From f4ed8145a95b54d8ee89dda925458f0fcb7cafa4 Mon Sep 17 00:00:00 2001 From: Luca Mondada <72734770+lmondada@users.noreply.github.com> Date: Thu, 7 Sep 2023 16:45:58 +0200 Subject: [PATCH] feat!: Rewriter API + ECCRewriter (#93) - refactor!: Standardise ECC naming - feat: Add Subcircuit and move CircuitRewrite - refactor: CircuitMatch stores PatternID - feat: Add ECCRewriter BREAKING CHANGE: `CircuitMatch` is now `PatternMatch`, `CircuitMatcher` is now `PatternMatcher`. BREAKING CHANGE: RepCircSet is now EqCircClass. BREAKING CHANGE: rep_sets_from_path is now load_eccs_json_file. BREAKING CHANGE: CircuitMatch.pattern is now a PatternID instead of a reference and no longer has a 'p pattern lifetime. BREAKING CHANGE: CircuitRewrite was moved to ::rewrite. --- Cargo.toml | 1 + compile-matcher/src/main.rs | 14 +- pyrs/src/lib.rs | 4 +- pyrs/test/test_portmatching.py | 6 +- src/lib.rs | 1 + src/passes/taso.rs | 22 ++- src/portmatching.rs | 2 +- src/portmatching/matcher.rs | 185 +++++++++++++++--------- src/portmatching/pyo3.rs | 141 ++++++++++++++----- src/rewrite.rs | 59 ++++++++ src/rewrite/ecc_rewriter.rs | 247 +++++++++++++++++++++++++++++++++ test_files/small_eccs.json | 54 +++++++ 12 files changed, 614 insertions(+), 122 deletions(-) create mode 100644 src/rewrite.rs create mode 100644 src/rewrite/ecc_rewriter.rs create mode 100644 test_files/small_eccs.json diff --git a/Cargo.toml b/Cargo.toml index a2f695f5..0ffe4a7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ strum_macros = "0.25.2" strum = "0.25.0" fxhash = "0.2.1" rmp-serde = { version = "1.1.2", optional = true } +delegate = "0.10.0" [features] pyo3 = [ diff --git a/compile-matcher/src/main.rs b/compile-matcher/src/main.rs index 4a55c0d2..5c0f59dc 100644 --- a/compile-matcher/src/main.rs +++ b/compile-matcher/src/main.rs @@ -8,14 +8,14 @@ use hugr::HugrView; use itertools::Itertools; use tket2::json::load_tk1_json_file; -// Import the CircuitMatcher struct and its methods -use tket2::passes::taso::rep_sets_from_path; -use tket2::portmatching::{CircuitMatcher, CircuitPattern}; +// Import the PatternMatcher struct and its methods +use tket2::passes::taso::load_eccs_json_file; +use tket2::portmatching::{CircuitPattern, PatternMatcher}; -/// Program to precompile patterns from files into a CircuitMatcher stored as binary file. +/// 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 CircuitMatcher stored as binary file.")] +#[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 @@ -45,7 +45,7 @@ fn main() { let all_circs = if input_path.is_file() { // Input is an ECC file in JSON format - let eccs = rep_sets_from_path(input_path); + let eccs = load_eccs_json_file(input_path); eccs.into_iter() .flat_map(|ecc| ecc.into_circuits()) .collect_vec() @@ -78,7 +78,7 @@ fn main() { } else { output_path.to_path_buf() }; - let matcher = CircuitMatcher::from_patterns(patterns); + let matcher = PatternMatcher::from_patterns(patterns); matcher.save_binary(output_file.to_str().unwrap()).unwrap(); println!("Written matcher to {:?}", output_file); diff --git a/pyrs/src/lib.rs b/pyrs/src/lib.rs index 9688cb2d..085a1434 100644 --- a/pyrs/src/lib.rs +++ b/pyrs/src/lib.rs @@ -1,5 +1,5 @@ use pyo3::prelude::*; -use tket2::portmatching::{CircuitMatcher, CircuitPattern}; +use tket2::portmatching::{CircuitPattern, PatternMatcher}; /// The Python bindings to TKET2. #[pymodule] @@ -11,7 +11,7 @@ fn pyrs(py: Python, m: &PyModule) -> PyResult<()> { fn add_patterns_module(py: Python, parent: &PyModule) -> PyResult<()> { let m = PyModule::new(py, "patterns")?; m.add_class::()?; - m.add_class::()?; + m.add_class::()?; parent.add_submodule(m)?; Ok(()) } diff --git a/pyrs/test/test_portmatching.py b/pyrs/test/test_portmatching.py index 8d4ca17e..88acc851 100644 --- a/pyrs/test/test_portmatching.py +++ b/pyrs/test/test_portmatching.py @@ -10,7 +10,7 @@ def test_simple_matching(): p1 = patterns.CircuitPattern(Circuit(2).CX(0, 1).H(1)) p2 = patterns.CircuitPattern(Circuit(2).H(0).CX(1, 0)) - matcher = patterns.CircuitMatcher(iter([p1, p2])) + matcher = patterns.PatternMatcher(iter([p1, p2])) assert len(matcher.find_matches(c)) == 2 @@ -18,7 +18,7 @@ def test_simple_matching(): def test_non_convex_pattern(): """ two-qubit circuits can't match three-qb ones """ p1 = patterns.CircuitPattern(Circuit(3).CX(0, 1).CX(1, 2)) - matcher = patterns.CircuitMatcher(iter([p1])) + matcher = patterns.PatternMatcher(iter([p1])) c = Circuit(2).CX(0, 1).CX(1, 0) assert len(matcher.find_matches(c)) == 0 @@ -39,6 +39,6 @@ def test_larger_matching(): p3 = patterns.CircuitPattern(Circuit(2).CX(0, 1).CX(1, 0)) p4 = patterns.CircuitPattern(Circuit(3).CX(0, 1).CX(1, 2)) - matcher = patterns.CircuitMatcher(iter([p1, p2, p3, p4])) + matcher = patterns.PatternMatcher(iter([p1, p2, p3, p4])) assert len(matcher.find_matches(c)) == 6 \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index bce5b59b..6c35b015 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ pub mod extension; pub mod json; pub(crate) mod ops; pub mod passes; +pub mod rewrite; pub use ops::{symbolic_constant_op, Pauli, T2Op}; #[cfg(feature = "portmatching")] diff --git a/src/passes/taso.rs b/src/passes/taso.rs index 3c390b0d..64494832 100644 --- a/src/passes/taso.rs +++ b/src/passes/taso.rs @@ -12,14 +12,18 @@ use hugr::Hugr; mod qtz_circuit; +/// An equivalent circuit class (ECC), with a canonical representative. #[derive(Clone)] -#[allow(unused)] // TODO -pub struct RepCircSet { +pub struct EqCircClass { rep_circ: Hugr, others: Vec, } -impl RepCircSet { +impl EqCircClass { + pub fn new(rep_circ: Hugr, others: Vec) -> Self { + Self { rep_circ, others } + } + /// The representative circuit of the equivalence class. pub fn rep_circ(&self) -> &Hugr { &self.rep_circ @@ -39,11 +43,19 @@ impl RepCircSet { pub fn into_circuits(self) -> impl Iterator { std::iter::once(self.rep_circ).chain(self.others) } + + /// The number of circuits in the equivalence class. + /// + /// An ECC always has a representative circuit, so this method will always + /// return an integer strictly greater than 0. + pub fn n_circuits(&self) -> usize { + self.others.len() + 1 + } } // TODO refactor so both implementations share more code -pub fn rep_sets_from_path(path: impl AsRef) -> Vec { +pub fn load_eccs_json_file(path: impl AsRef) -> Vec { let all_circs = qtz_circuit::load_ecc_set(path); all_circs @@ -52,7 +64,7 @@ pub fn rep_sets_from_path(path: impl AsRef) -> Vec { // TODO is the rep circ always the first?? let rep_circ = all.remove(0); - RepCircSet { + EqCircClass { rep_circ, others: all, } diff --git a/src/portmatching.rs b/src/portmatching.rs index a93e3736..7875ae8d 100644 --- a/src/portmatching.rs +++ b/src/portmatching.rs @@ -5,7 +5,7 @@ pub mod pattern; #[cfg(feature = "pyo3")] mod pyo3; -pub use matcher::{CircuitMatch, CircuitMatcher, CircuitRewrite}; +pub use matcher::{PatternMatch, PatternMatcher}; pub use pattern::CircuitPattern; use hugr::Port; diff --git a/src/portmatching/matcher.rs b/src/portmatching/matcher.rs index e084dd5b..a666451b 100644 --- a/src/portmatching/matcher.rs +++ b/src/portmatching/matcher.rs @@ -8,7 +8,6 @@ use std::{ }; use super::{CircuitPattern, PEdge, PNode}; -use derive_more::{From, Into}; use hugr::{ hugr::views::{ sibling::{ @@ -18,7 +17,7 @@ use hugr::{ SiblingSubgraph, }, ops::OpType, - Hugr, Node, Port, SimpleReplacement, + Hugr, Node, Port, }; use itertools::Itertools; use portmatching::{ @@ -30,7 +29,12 @@ use thiserror::Error; #[cfg(feature = "pyo3")] use pyo3::prelude::*; -use crate::{circuit::Circuit, ops::NotT2Op, T2Op}; +use crate::{ + circuit::Circuit, + ops::NotT2Op, + rewrite::{CircuitRewrite, Subcircuit}, + T2Op, +}; /// Matchable operations in a circuit. /// @@ -64,26 +68,33 @@ impl TryFrom for MatchOp { } /// A convex pattern match in a circuit. +/// +/// The pattern is identified by a [`PatternID`] that can be used to retrieve the +/// pattern from the matcher. #[derive(Clone)] -pub struct CircuitMatch<'a, 'p, C> { - subgraph: SiblingSubgraph<'a, C>, - #[allow(dead_code)] - pub(super) pattern: &'p CircuitPattern, +pub struct PatternMatch<'a, C> { + position: Subcircuit<'a, C>, + pattern: PatternID, /// The root of the pattern in the circuit. /// - /// This is redundant with the subgraph attribute, but is a more concise - /// representation of the match useful for `PyCircuitMatch` or serialisation. + /// This is redundant with the position attribute, but is a more concise + /// representation of the match useful for `PyPatternMatch` or serialisation. pub(super) root: Node, } -impl<'a, 'p, C: Circuit<'a>> CircuitMatch<'a, 'p, C> { +impl<'a, C: Circuit<'a>> PatternMatch<'a, C> { + /// The matcher's pattern ID of the match. + pub fn pattern_id(&self) -> PatternID { + self.pattern + } + /// Create a pattern match from the image of a pattern root. /// /// This checks at construction time that the match is convex. This will /// have runtime linear in the size of the circuit. /// /// For repeated convexity checking on the same circuit, use - /// [`CircuitMatch::try_from_root_match_with_checker`] instead. + /// [`PatternMatch::try_from_root_match_with_checker`] instead. /// /// Returns an error if /// - the match is not convex @@ -92,42 +103,88 @@ impl<'a, 'p, C: Circuit<'a>> CircuitMatch<'a, 'p, C> { /// - the subcircuit obtained is not a valid circuit region pub fn try_from_root_match( root: Node, - pattern: &'p CircuitPattern, + pattern: PatternID, circ: &'a C, - ) -> Result { + matcher: &PatternMatcher, + ) -> Result { let mut checker = ConvexChecker::new(circ); - Self::try_from_root_match_with_checker(root, pattern, circ, &mut checker) + Self::try_from_root_match_with_checker(root, pattern, circ, matcher, &mut checker) } /// Create a pattern match from the image of a pattern root with a checker. /// - /// This is the same as [`CircuitMatch::try_from_root_match`] but takes a + /// This is the same as [`PatternMatch::try_from_root_match`] but takes a /// checker object to speed up convexity checking. /// - /// See [`CircuitMatch::try_from_root_match`] for more details. + /// See [`PatternMatch::try_from_root_match`] for more details. pub fn try_from_root_match_with_checker( root: Node, - pattern: &'p CircuitPattern, + pattern: PatternID, circ: &'a C, + matcher: &PatternMatcher, checker: &mut ConvexChecker<'a, C>, - ) -> Result { - let map = pattern + ) -> Result { + let pattern_ref = matcher + .get_pattern(pattern) + .ok_or(InvalidPatternMatch::MatchNotFound)?; + let map = pattern_ref .get_match_map(root, circ) - .ok_or(InvalidCircuitMatch::MatchNotFound)?; - let inputs = pattern + .ok_or(InvalidPatternMatch::MatchNotFound)?; + let inputs = pattern_ref .inputs .iter() .map(|p| p.iter().map(|(n, p)| (map[n], *p)).collect_vec()) .collect_vec(); - let outputs = pattern + let outputs = pattern_ref .outputs .iter() .map(|(n, p)| (map[n], *p)) .collect_vec(); + Self::try_from_io_with_checker(root, pattern, circ, inputs, outputs, checker) + } + + /// Create a pattern match from the subcircuit boundaries. + /// + /// The position of the match is given by a list of incoming boundary + /// ports and outgoing boundary ports. See [`SiblingSubgraph`] for more + /// details. + /// + /// This checks at construction time that the match is convex. This will + /// have runtime linear in the size of the circuit. + /// + /// For repeated convexity checking on the same circuit, use + /// [`PatternMatch::try_from_io_with_checker`] instead. + pub fn try_from_io( + root: Node, + pattern: PatternID, + circ: &'a C, + inputs: Vec>, + outputs: Vec<(Node, Port)>, + ) -> Result { + let mut checker = ConvexChecker::new(circ); + Self::try_from_io_with_checker(root, pattern, circ, inputs, outputs, &mut checker) + } + + /// Create a pattern match from the subcircuit boundaries. + /// + /// The position of the match is given by a list of incoming boundary + /// ports and outgoing boundary ports. See [`SiblingSubgraph`] for more + /// details. + /// + /// This checks at construction time that the match is convex. This will + /// have runtime linear in the size of the circuit. + pub fn try_from_io_with_checker( + root: Node, + pattern: PatternID, + circ: &'a C, + inputs: Vec>, + outputs: Vec<(Node, Port)>, + checker: &mut ConvexChecker<'a, C>, + ) -> Result { let subgraph = SiblingSubgraph::try_from_boundary_ports_with_checker(circ, inputs, outputs, checker)?; Ok(Self { - subgraph, + position: subgraph.into(), pattern, root, }) @@ -135,46 +192,39 @@ impl<'a, 'p, C: Circuit<'a>> CircuitMatch<'a, 'p, C> { /// Construct a rewrite to replace `self` with `repl`. pub fn to_rewrite(&self, repl: Hugr) -> Result { - self.subgraph - .create_simple_replacement(repl) - .map(Into::into) + CircuitRewrite::try_new(&self.position, repl) } } -impl<'a, 'p, C: Circuit<'a>> Debug for CircuitMatch<'a, 'p, C> { +impl<'a, C: Circuit<'a>> Debug for PatternMatch<'a, C> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CircuitMatch") + f.debug_struct("PatternMatch") .field("root", &self.root) - .field("nodes", &self.subgraph.nodes()) + .field("nodes", &self.position.subgraph.nodes()) .finish() } } -/// A rewrite object for circuit matching. -#[cfg_attr(feature = "pyo3", pyclass)] -#[derive(Debug, Clone, From, Into)] -pub struct CircuitRewrite(SimpleReplacement); - /// A matcher object for fast pattern matching on circuits. /// /// This uses a state automaton internally to match against a set of patterns /// simultaneously. #[cfg_attr(feature = "pyo3", pyclass)] #[derive(Clone, serde::Serialize, serde::Deserialize)] -pub struct CircuitMatcher { +pub struct PatternMatcher { automaton: ScopeAutomaton, patterns: Vec, } -impl Debug for CircuitMatcher { +impl Debug for PatternMatcher { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CircuitMatcher") + f.debug_struct("PatternMatcher") .field("patterns", &self.patterns) .finish() } } -impl CircuitMatcher { +impl PatternMatcher { /// Construct a matcher from a set of patterns pub fn from_patterns(patterns: impl Into>) -> Self { let patterns = patterns.into(); @@ -196,10 +246,7 @@ impl CircuitMatcher { } /// Find all convex pattern matches in a circuit. - pub fn find_matches<'a, 'm, C: Circuit<'a>>( - &'m self, - circuit: &'a C, - ) -> Vec> { + pub fn find_matches<'a, C: Circuit<'a>>(&self, circuit: &'a C) -> Vec> { let mut checker = ConvexChecker::new(circuit); circuit .commands() @@ -208,12 +255,12 @@ impl CircuitMatcher { } /// Find all convex pattern matches in a circuit rooted at a given node. - fn find_rooted_matches<'a, 'm, C: Circuit<'a>>( - &'m self, + fn find_rooted_matches<'a, C: Circuit<'a>>( + &self, circ: &'a C, root: Node, checker: &mut ConvexChecker<'a, C>, - ) -> Vec> { + ) -> Vec> { self.automaton .run( root, @@ -222,10 +269,11 @@ impl CircuitMatcher { // Check edge exist validate_unweighted_edge(circ), ) - .filter_map(|m| { - let p = &self.patterns[m.0]; + .filter_map(|pattern_id| { handle_match_error( - CircuitMatch::try_from_root_match_with_checker(root, p, circ, checker), + PatternMatch::try_from_root_match_with_checker( + root, pattern_id, circ, self, checker, + ), root, ) }) @@ -237,10 +285,15 @@ impl CircuitMatcher { self.patterns.get(id.0) } + /// Get the number of patterns in the matcher. + pub fn n_patterns(&self) -> usize { + self.patterns.len() + } + /// Serialise a matcher into an IO stream. /// /// Precomputed matchers can be serialised as binary and then loaded - /// later using [`CircuitMatcher::load_binary_io`]. + /// later using [`PatternMatcher::load_binary_io`]. pub fn save_binary_io( &self, writer: &mut W, @@ -251,7 +304,7 @@ impl CircuitMatcher { /// Loads a matcher from an IO stream. /// - /// Loads streams as created by [`CircuitMatcher::save_binary_io`]. + /// Loads streams as created by [`PatternMatcher::save_binary_io`]. pub fn load_binary_io(reader: &mut R) -> Result { let matcher: Self = rmp_serde::decode::from_read(reader)?; Ok(matcher) @@ -260,7 +313,7 @@ impl CircuitMatcher { /// Save a matcher as a binary file. /// /// Precomputed matchers can be saved as binary files and then loaded - /// later using [`CircuitMatcher::load_binary`]. + /// later using [`PatternMatcher::load_binary`]. /// /// The extension of the file name will always be set or amended to be /// `.bin`. @@ -277,7 +330,7 @@ impl CircuitMatcher { Ok(file_name) } - /// Loads a matcher saved using [`CircuitMatcher::save_binary`]. + /// Loads a matcher saved using [`PatternMatcher::save_binary`]. pub fn load_binary(name: impl AsRef) -> Result { let file = File::open(name)?; let mut reader = std::io::BufReader::new(file); @@ -287,7 +340,7 @@ impl CircuitMatcher { /// Errors that can occur when constructing matches. #[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum InvalidCircuitMatch { +pub enum InvalidPatternMatch { /// The match is not convex. #[error("match is not convex")] NotConvex, @@ -321,13 +374,13 @@ pub enum MatcherSerialisationError { Serialisation(#[from] rmp_serde::encode::Error), } -impl From for InvalidCircuitMatch { +impl From for InvalidPatternMatch { fn from(value: InvalidSubgraph) -> Self { match value { - InvalidSubgraph::NotConvex => InvalidCircuitMatch::NotConvex, - InvalidSubgraph::EmptySubgraph => InvalidCircuitMatch::EmptyMatch, + InvalidSubgraph::NotConvex => InvalidPatternMatch::NotConvex, + InvalidSubgraph::EmptySubgraph => InvalidPatternMatch::EmptyMatch, InvalidSubgraph::NoSharedParent | InvalidSubgraph::InvalidBoundary => { - InvalidCircuitMatch::InvalidSubcircuit + InvalidPatternMatch::InvalidSubcircuit } } } @@ -363,13 +416,13 @@ pub(crate) fn validate_weighted_node<'circ>( /// /// Benign errors are non-convex matches, which are expected to occur. /// Other errors are considered logic errors and should never occur. -fn handle_match_error(match_res: Result, root: Node) -> Option { +fn handle_match_error(match_res: Result, root: Node) -> Option { match_res .map_err(|err| match err { - InvalidCircuitMatch::NotConvex => InvalidCircuitMatch::NotConvex, - InvalidCircuitMatch::MatchNotFound - | InvalidCircuitMatch::InvalidSubcircuit - | InvalidCircuitMatch::EmptyMatch => { + InvalidPatternMatch::NotConvex => InvalidPatternMatch::NotConvex, + InvalidPatternMatch::MatchNotFound + | InvalidPatternMatch::InvalidSubcircuit + | InvalidPatternMatch::EmptyMatch => { panic!("invalid match at root node {root:?}") } }) @@ -388,7 +441,7 @@ mod tests { use crate::utils::build_simple_circuit; use crate::T2Op; - use super::{CircuitMatcher, CircuitPattern}; + use super::{CircuitPattern, PatternMatcher}; static H_CX: OnceLock = OnceLock::new(); static CX_CX: OnceLock = OnceLock::new(); @@ -422,7 +475,7 @@ mod tests { let circ = h_cx(); let p = CircuitPattern::try_from_circuit(&circ).unwrap(); - let m = CircuitMatcher::from_patterns(vec![p]); + let m = PatternMatcher::from_patterns(vec![p]); let matches = m.find_matches(&circ); assert_eq!(matches.len(), 1); @@ -438,10 +491,10 @@ mod tests { // Estimate the size of the buffer based on the number of patterns and the size of each pattern let mut buf = Vec::with_capacity(patterns[0].n_edges() + patterns[1].n_edges()); - let m = CircuitMatcher::from_patterns(patterns); + let m = PatternMatcher::from_patterns(patterns); m.save_binary_io(&mut buf).unwrap(); - let m2 = CircuitMatcher::load_binary_io(&mut buf.as_slice()).unwrap(); + let m2 = PatternMatcher::load_binary_io(&mut buf.as_slice()).unwrap(); let mut buf2 = Vec::with_capacity(buf.len()); m2.save_binary_io(&mut buf2).unwrap(); diff --git a/src/portmatching/pyo3.rs b/src/portmatching/pyo3.rs index 72cfba70..ce146856 100644 --- a/src/portmatching/pyo3.rs +++ b/src/portmatching/pyo3.rs @@ -5,12 +5,16 @@ use std::fmt; use derive_more::{From, Into}; use hugr::hugr::views::{DescendantsGraph, HierarchyView}; use hugr::ops::handle::DfgID; -use hugr::{Hugr, HugrView}; +use hugr::{Hugr, HugrView, Port}; +use itertools::Itertools; +use portmatching::{HashMap, PatternID}; use pyo3::{create_exception, exceptions::PyException, prelude::*, types::PyIterator}; use tket_json_rs::circuit_json::SerialCircuit; -use super::{CircuitMatch, CircuitMatcher, CircuitPattern, CircuitRewrite}; +use super::{CircuitPattern, PatternMatch, PatternMatcher}; +use crate::circuit::Circuit; use crate::json::TKETDecode; +use crate::rewrite::CircuitRewrite; create_exception!(pyrs, PyValidateError, PyException); create_exception!(pyrs, PyInvalidReplacement, PyException); @@ -34,11 +38,11 @@ impl CircuitPattern { } #[pymethods] -impl CircuitMatcher { +impl PatternMatcher { /// Construct a matcher from a list of patterns. #[new] pub fn py_from_patterns(patterns: &PyIterator) -> PyResult { - Ok(CircuitMatcher::from_patterns( + Ok(PatternMatcher::from_patterns( patterns .iter()? .map(|p| p?.extract::()) @@ -52,14 +56,21 @@ impl CircuitMatcher { /// Find all convex matches in a circuit. #[pyo3(name = "find_matches")] - pub fn py_find_matches(&self, circ: PyObject) -> PyResult> { + pub fn py_find_matches(&self, circ: PyObject) -> PyResult> { let hugr = pyobj_as_hugr(circ)?; let circ = hugr_as_view(&hugr); - Ok(self - .find_matches(&circ) + self.find_matches(&circ) .into_iter() - .map(|m| PyCircuitMatch::new(m.pattern.clone(), hugr.clone(), Node(m.root))) - .collect()) + .map(|m| { + let pattern_id = m.pattern_id(); + PyPatternMatch::try_from_rust(m, &circ, self).map_err(|e| { + PyInvalidReplacement::new_err(format!( + "Invalid match for pattern {:?}: {}", + pattern_id, e + )) + }) + }) + .collect() } } @@ -67,52 +78,106 @@ impl CircuitMatcher { /// /// A convex pattern match in a circuit, available from Python. /// -/// This object removes the lifetime constraints of its Rust counterpart by -/// cloning the pattern and circuit data. It is provided for convenience and -/// not recommended when performance is a key concern. +/// This object is semantically equivalent to Rust's [`PatternMatch`] but +/// stores data differently, and in particular removes the lifetime-bound +/// references of Rust. +/// +/// The data is stored in a way that favours a nice user-facing representation +/// over efficiency. It is provided for convenience and not recommended when +/// performance is a key concern. /// /// TODO: can this be a wrapper for a [`CircuitMatch`] instead? #[pyclass] #[derive(Debug, Clone)] -pub struct PyCircuitMatch { - /// The pattern that was matched. - pub pattern: CircuitPattern, - /// The circuit that contains the match. - pub circuit: Hugr, +pub struct PyPatternMatch { + /// The ID of the pattern in the matcher. + pub pattern_id: usize, /// The root of the pattern within the circuit. pub root: Node, + /// The input ports of the subcircuit. + /// + /// This is the incoming boundary of a [`hugr::hugr::views::SiblingSubgraph`]. + /// The input ports are grouped together if they are connected to the same + /// source. + pub inputs: Vec>, + /// The output ports of the subcircuit. + /// + /// This is the outgoing boundary of a [`hugr::hugr::views::SiblingSubgraph`]. + pub outputs: Vec<(Node, Port)>, + /// The node map from pattern to circuit. + pub node_map: HashMap, } #[pymethods] -impl PyCircuitMatch { +impl PyPatternMatch { /// A string representation of the pattern. pub fn __repr__(&self) -> String { - let circ = hugr_as_view(&self.circuit); - format!( - "CircuitMatch {:?}", - self.pattern - .get_match_map(self.root.0, &circ) - .expect("Invalid PyCircuitMatch object") - ) + format!("CircuitMatch {:?}", self.node_map) } } -impl PyCircuitMatch { - pub fn new(pattern: CircuitPattern, circuit: Hugr, root: Node) -> Self { - Self { - pattern, - circuit, +impl PyPatternMatch { + /// Construct a [`PyCircuitMatch`] from a [`PatternMatch`]. + /// + /// Requires references to the circuit and pattern to resolve indices + /// into these objects. + pub fn try_from_rust<'circ, C: Circuit<'circ>>( + m: PatternMatch<'circ, C>, + circ: &C, + matcher: &PatternMatcher, + ) -> PyResult { + let pattern_id = m.pattern_id(); + let pattern = matcher.get_pattern(pattern_id).unwrap(); + let root = Node(m.root); + + let node_map: HashMap = pattern + .get_match_map(root.0, circ) + .ok_or_else(|| PyInvalidReplacement::new_err("Invalid match"))? + .into_iter() + .map(|(p, c)| (Node(p), Node(c))) + .collect(); + let inputs = pattern + .inputs + .iter() + .map(|p| { + p.iter() + .map(|&(n, p)| (node_map[&Node(n)], p)) + .collect_vec() + }) + .collect_vec(); + let outputs = pattern + .outputs + .iter() + .map(|&(n, p)| (node_map[&Node(n)], p)) + .collect_vec(); + Ok(Self { + pattern_id: pattern_id.0, + inputs, + outputs, + node_map, root, - } + }) } - /// Obtain as a [`CircuitMatch`] object. - pub fn to_rewrite(&self, replacement: PyObject) -> PyResult { - let circ = hugr_as_view(&self.circuit); - CircuitMatch::try_from_root_match(self.root.0, &self.pattern, &circ) - .expect("Invalid PyCircuitMatch object") - .to_rewrite(pyobj_as_hugr(replacement)?) - .map_err(|e| PyInvalidReplacement::new_err(e.to_string())) + pub fn to_rewrite(&self, circ: PyObject, replacement: PyObject) -> PyResult { + let hugr = pyobj_as_hugr(circ)?; + let circ = hugr_as_view(&hugr); + let inputs = self + .inputs + .iter() + .map(|p| p.iter().map(|&(n, p)| (n.0, p)).collect()) + .collect(); + let outputs = self.outputs.iter().map(|&(n, p)| (n.0, p)).collect(); + PatternMatch::try_from_io( + self.root.0, + PatternID(self.pattern_id), + &circ, + inputs, + outputs, + ) + .expect("Invalid PyCircuitMatch object") + .to_rewrite(pyobj_as_hugr(replacement)?) + .map_err(|e| PyInvalidReplacement::new_err(e.to_string())) } } diff --git a/src/rewrite.rs b/src/rewrite.rs new file mode 100644 index 00000000..5947a339 --- /dev/null +++ b/src/rewrite.rs @@ -0,0 +1,59 @@ +//! Transform circuits using rewrite rules. + +#[cfg(feature = "portmatching")] +pub mod ecc_rewriter; +#[cfg(feature = "portmatching")] +pub use ecc_rewriter::ECCRewriter; + +use delegate::delegate; +use derive_more::{From, Into}; +use hugr::{ + hugr::{ + hugrmut::HugrMut, + views::{sibling::InvalidReplacement, SiblingSubgraph}, + Rewrite, SimpleReplacementError, + }, + Hugr, HugrView, SimpleReplacement, +}; + +#[cfg(feature = "pyo3")] +use pyo3::prelude::*; + +use crate::circuit::Circuit; + +/// A subcircuit of a circuit. +#[derive(Debug, Clone, From, Into)] +pub struct Subcircuit<'a, C> { + pub(crate) subgraph: SiblingSubgraph<'a, C>, +} + +/// A rewrite rule for circuits. +#[cfg_attr(feature = "pyo3", pyclass)] +#[derive(Debug, Clone, From, Into)] +pub struct CircuitRewrite(SimpleReplacement); + +impl CircuitRewrite { + /// Create a new rewrite rule. + pub fn try_new( + source_position: &Subcircuit<'_, C>, + target: Hugr, + ) -> Result { + source_position + .subgraph + .create_simple_replacement(target) + .map(Self) + } + + delegate! { + to self.0 { + /// Apply the rewrite rule to a circuit. + pub fn apply(self, circ: &mut impl HugrMut) -> Result<(), SimpleReplacementError>; + } + } +} + +/// Generate rewrite rules for circuits. +pub trait Rewriter { + /// Get the rewrite rules for a circuit. + fn get_rewrites<'a, C: Circuit<'a>>(&'a self, circ: &'a C) -> Vec; +} diff --git a/src/rewrite/ecc_rewriter.rs b/src/rewrite/ecc_rewriter.rs new file mode 100644 index 00000000..1bad0157 --- /dev/null +++ b/src/rewrite/ecc_rewriter.rs @@ -0,0 +1,247 @@ +//! A rewriter based on circuit equivalence classes. +//! +//! Circuits are clustered in equivalence classes based on whether they +//! represent the same unitary. +//! +//! This rewriter uses the [`PatternMatcher`] to find known subcircuits and +//! generates rewrites to replace them with other circuits within the same +//! equivalence class. +//! +//! Equivalence classes are generated using Quartz +//! (). The simplest way +//! to generate such a file is to use the `gen_ecc_set.sh` script at the root +//! of the Quartz repository. + +use derive_more::{From, Into}; +use itertools::Itertools; +use portmatching::PatternID; +use std::path::Path; + +use hugr::{ + hugr::views::{HierarchyView, SiblingGraph}, + ops::handle::DfgID, + Hugr, HugrView, +}; + +use crate::{ + circuit::Circuit, + passes::taso::{load_eccs_json_file, EqCircClass}, + portmatching::{CircuitPattern, PatternMatcher}, +}; + +use super::{CircuitRewrite, Rewriter}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, From, Into)] +struct TargetID(usize); + +/// A rewriter based on circuit equivalence classes. +/// +/// In every equivalence class, one circuit is chosen as the representative. +/// Valid rewrites turn a non-representative circuit into its representative, +/// or a representative circuit into any of the equivalent non-representative +/// circuits. +pub struct ECCRewriter { + /// Matcher for finding patterns. + matcher: PatternMatcher, + /// Targets of some rewrite rules. + targets: Vec, + /// Rewrites, stored as a map from the source PatternID to possibly multiple + /// target TargetIDs. The usize index of PatternID is used to index into + /// the outer vector. + rewrite_rules: Vec>, +} + +impl ECCRewriter { + /// Create a new rewriter from equivalent circuit classes in JSON file. + /// + /// This uses the Quartz JSON file format to store equivalent circuit classes. + /// Generate such a file using the `gen_ecc_set.sh` script at the root of + /// the Quartz repository. + /// + /// Quartz: . + pub fn from_eccs_json_file(path: impl AsRef) -> Self { + let eccs = load_eccs_json_file(path); + Self::from_eccs(eccs) + } + + /// Create a new rewriter from a list of equivalent circuit classes. + /// + /// Equivalence classes are represented as [`EqCircClass`]s, lists of + /// HUGRs where one of the elements is chosen as the representative. + pub fn from_eccs(eccs: impl Into>) -> Self { + let eccs = eccs.into(); + let rewrite_rules = get_rewrite_rules(&eccs); + let patterns = get_patterns(&eccs); + // Remove failed patterns + let (patterns, rewrite_rules): (Vec<_>, Vec<_>) = patterns + .into_iter() + .zip(rewrite_rules) + .filter_map(|(p, r)| Some((p?, r))) + .unzip(); + let targets = into_targets(eccs); + let matcher = PatternMatcher::from_patterns(patterns); + Self { + matcher, + targets, + rewrite_rules, + } + } + + /// Get all targets of rewrite rules given a source pattern. + fn get_targets(&self, pattern: PatternID) -> impl Iterator { + self.rewrite_rules[pattern.0] + .iter() + .map(|id| &self.targets[id.0]) + } +} + +impl Rewriter for ECCRewriter { + fn get_rewrites<'a, C: Circuit<'a>>(&'a self, circ: &'a C) -> Vec { + let matches = self.matcher.find_matches(circ); + matches + .into_iter() + .flat_map(|m| { + let pattern_id = m.pattern_id(); + self.get_targets(pattern_id) + .map(move |repl| m.to_rewrite(repl.clone()).expect("invalid replacement")) + }) + .collect() + } +} + +fn into_targets(rep_sets: Vec) -> Vec { + rep_sets + .into_iter() + .flat_map(|rs| rs.into_circuits()) + .collect() +} + +fn get_rewrite_rules(rep_sets: &[EqCircClass]) -> Vec> { + let n_circs = rep_sets.iter().map(|rs| rs.n_circuits()).sum::(); + let mut rewrite_rules = vec![Default::default(); n_circs]; + let mut curr_target = 0; + for rep_set in rep_sets { + let rep_ind = curr_target; + let other_inds = (curr_target + 1)..(curr_target + rep_set.n_circuits()); + // Rewrite rules for representative circuit + rewrite_rules[rep_ind] = other_inds.clone().map_into().collect(); + // Rewrite rules for other circuits + for i in other_inds { + rewrite_rules[i] = vec![rep_ind.into()]; + } + curr_target += rep_set.n_circuits(); + } + rewrite_rules +} + +fn get_patterns(rep_sets: &[EqCircClass]) -> Vec> { + let all_hugrs = rep_sets.iter().flat_map(|rs| rs.circuits()); + let all_circs = all_hugrs + .map(|hugr| SiblingGraph::::new(hugr, hugr.root())) + // TODO: resolve lifetime issues to avoid collecting to vec + .collect_vec(); + all_circs + .iter() + .map(|circ| CircuitPattern::try_from_circuit(circ).ok()) + .collect() +} + +#[cfg(test)] +mod tests { + use crate::{utils::build_simple_circuit, T2Op}; + + use super::*; + + fn empty() -> Hugr { + build_simple_circuit(2, |_| Ok(())).unwrap() + } + + fn h_h() -> Hugr { + build_simple_circuit(2, |circ| { + circ.append(T2Op::H, [0]).unwrap(); + circ.append(T2Op::H, [0]).unwrap(); + Ok(()) + }) + .unwrap() + } + + fn cx_cx() -> Hugr { + build_simple_circuit(2, |circ| { + circ.append(T2Op::CX, [0, 1]).unwrap(); + circ.append(T2Op::CX, [0, 1]).unwrap(); + Ok(()) + }) + .unwrap() + } + + fn cx_x() -> Hugr { + build_simple_circuit(2, |circ| { + circ.append(T2Op::CX, [0, 1]).unwrap(); + circ.append(T2Op::X, [1]).unwrap(); + Ok(()) + }) + .unwrap() + } + + fn x_cx() -> Hugr { + build_simple_circuit(2, |circ| { + circ.append(T2Op::X, [1]).unwrap(); + circ.append(T2Op::CX, [0, 1]).unwrap(); + Ok(()) + }) + .unwrap() + } + + #[test] + fn small_ecc_rewriter() { + let ecc1 = EqCircClass::new(h_h(), vec![empty(), cx_cx()]); + let ecc2 = EqCircClass::new(cx_x(), vec![x_cx()]); + let rewriter = ECCRewriter::from_eccs(vec![ecc1, ecc2]); + assert_eq!(rewriter.targets.len(), 5); + assert_eq!( + rewriter.rewrite_rules, + [ + vec![TargetID(1), TargetID(2)], + vec![TargetID(0)], + vec![TargetID(4)], + vec![TargetID(3)], + ] + ); + assert_eq!(rewriter.get_targets(PatternID(1)).collect_vec(), [&h_h()]); + } + + #[test] + fn ecc_rewriter_from_file() { + // In this example, all circuits are valid patterns, thus + // PatternID == TargetID. + let test_file = "test_files/small_eccs.json"; + let rewriter = ECCRewriter::from_eccs_json_file(test_file); + assert_eq!(rewriter.rewrite_rules.len(), rewriter.matcher.n_patterns()); + assert_eq!(rewriter.targets.len(), 5 * 4 + 4 * 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]] + // where k is the index of a representative circuit and t+1 is the size + // of the ECC. + let mut n_eccs_of_len = [0; 4]; + let mut next_k_are_1 = 0; + let mut curr_repr = TargetID(0); + for (i, rws) in rewriter.rewrite_rules.into_iter().enumerate() { + n_eccs_of_len[rws.len()] += 1; + if rws.len() == 1 { + assert!(next_k_are_1 > 0); + assert_eq!(rws, vec![curr_repr]); + next_k_are_1 -= 1; + } else { + assert_eq!(next_k_are_1, 0); + let exp_rws: Vec<_> = (i + 1..=i + rws.len()).map(TargetID).collect(); + assert_eq!(rws, exp_rws); + next_k_are_1 = rws.len(); + 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]; + assert_eq!(n_eccs_of_len, exp_n_eccs_of_len); + } +} diff --git a/test_files/small_eccs.json b/test_files/small_eccs.json new file mode 100644 index 00000000..787d10a6 --- /dev/null +++ b/test_files/small_eccs.json @@ -0,0 +1,54 @@ +[[], +{ +"0_2": [ +[[1,0,0,2,["bd6ef7f96a3f"],[-9.75927075462199589e-02,-4.04975393353440460e-01]],[["t", ["Q0"],["Q0"]],["x", ["Q0"],["Q0"]]]] +,[[1,0,0,2,["bd6ef7f96a3e"],[-3.55369312154194761e-01,-2.17352381553619523e-01]],[["x", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]]]] +,[[1,0,0,2,["bd6ef7f96a3e"],[-3.55369312154194761e-01,-2.17352381553619523e-01]],[["x", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]]]] +] +,"1_2": [ +[[1,0,0,2,["9f82b360087d"],[1.27673353248785815e-01,3.26706732953894963e-01]],[["tdg", ["Q0"],["Q0"]],["x", ["Q0"],["Q0"]]]] +,[[1,0,0,2,["9f82b360087d"],[-1.40737852471959524e-01,3.21295240190043629e-01]],[["x", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]]]] +,[[1,0,0,2,["9f82b360087d"],[-1.40737852471959524e-01,3.21295240190043629e-01]],[["x", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]]]] +] +,"2_2": [ +[[1,0,0,3,["1584725ea81f9"],[1.61102697541570028e-01,7.39735570981055468e-01]],[["h", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]]]] +,[[1,0,0,4,["1584725ea81f9"],[1.61102697541570028e-01,7.39735570981055468e-01]],[["x", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]]]] +,[[1,0,0,4,["1584725ea81f9"],[1.61102697541570028e-01,7.39735570981055468e-01]],[["x", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]]]] +] +,"2140_3": [ +[[1,0,0,3,["90150669058f"],[1.75375079708356807e-01,2.63877124678975283e-01]],[["h", ["Q0"],["Q0"]],["x", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]]]] +,[[1,0,0,4,["90150669058f"],[1.75375079708356668e-01,2.63877124678975117e-01]],[["t", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]]]] +,[[1,0,0,4,["90150669058f"],[1.75375079708356668e-01,2.63877124678975117e-01]],[["tdg", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]]]] +,[[1,0,0,4,["90150669058f"],[1.75375079708356668e-01,2.63877124678975117e-01]],[["tdg", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]]]] +] +,"2141_3": [ +[[1,0,0,3,["c0d01cdf17f9"],[-3.22589602981046553e-02,4.22771189170472572e-01]],[["t", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]]]] +,[[1,0,0,4,["c0d01cdf17f9"],[-3.22589602981047108e-02,4.22771189170472406e-01]],[["tdg", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["x", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]]]] +,[[1,0,0,4,["c0d01cdf17f9"],[-3.22589602981044887e-02,4.22771189170472628e-01]],[["h", ["Q0"],["Q0"]],["x", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]]]] +,[[1,0,0,4,["c0d01cdf17f9"],[-3.22589602981044887e-02,4.22771189170472628e-01]],[["h", ["Q0"],["Q0"]],["x", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]]]] +] +,"2142_3": [ +[[1,0,0,3,["efb33e265fdf"],[4.34549587894314859e-01,2.98341491880472431e-01]],[["tdg", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]]]] +,[[1,0,0,4,["efb33e265fdf"],[4.34549587894314748e-01,2.98341491880472431e-01]],[["t", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["x", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]]]] +,[[1,0,0,4,["efb33e265fdf"],[4.34549587894314859e-01,2.98341491880472542e-01]],[["h", ["Q0"],["Q0"]],["x", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]]]] +,[[1,0,0,4,["efb33e265fdf"],[4.34549587894314859e-01,2.98341491880472542e-01]],[["h", ["Q0"],["Q0"]],["x", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]]]] +] +,"2143_3": [ +[[1,0,0,4,["11a467734c250"],[5.48949386000248341e-01,2.89758731510870227e-01]],[["t", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]]]] +,[[1,0,0,5,["11a467734c250"],[5.93056197328315604e-01,-1.83275469409618696e-01]],[["tdg", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]],["x", ["Q0"],["Q0"]]]] +,[[1,0,0,5,["11a467734c250"],[5.93056197328315715e-01,-1.83275469409618641e-01]],[["h", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]]]] +,[[1,0,0,5,["11a467734c250"],[5.93056197328315715e-01,-1.83275469409618641e-01]],[["h", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]]]] +] +,"2144_3": [ +[[1,0,0,4,["164fc31d45d4d"],[2.92364596255271447e-01,7.28544554548003376e-01]],[["tdg", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["tdg", ["Q0"],["Q0"]]]] +,[[1,0,0,5,["164fc31d45d4d"],[-3.08425806326456275e-01,7.21891783508395335e-01]],[["t", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]],["x", ["Q0"],["Q0"]]]] +,[[1,0,0,5,["164fc31d45d4d"],[-3.08425806326456164e-01,7.21891783508395224e-01]],[["h", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]]]] +,[[1,0,0,5,["164fc31d45d4d"],[-3.08425806326456164e-01,7.21891783508395224e-01]],[["h", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]],["h", ["Q0"],["Q0"]],["t", ["Q0"],["Q0"]]]] +] +,"2145_3": [ +[[2,0,0,3,["116baec0e00cd"],[5.53564910528592469e-01,2.63161770391809990e-01]],[["cx", ["Q0", "Q1"],["Q0", "Q1"]],["x", ["Q0"],["Q0"]],["cx", ["Q0", "Q1"],["Q0", "Q1"]]]] +,[[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"]]]] +] +} +] \ No newline at end of file