From 7be2094537b0a2b0a087739584acfbc803e28a66 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 6 Feb 2023 13:30:59 -0800 Subject: [PATCH 001/118] Initial ToLaTeX commit. --- Cargo.toml | 1 + src/program/latex.rs | 74 +++++++++++++++++++ src/program/mod.rs | 3 + ...rogram__latex__tests__controlled_gate.snap | 15 ++++ 4 files changed, 93 insertions(+) create mode 100644 src/program/latex.rs create mode 100644 src/program/snapshots/quil_rs__program__latex__tests__controlled_gate.snap diff --git a/Cargo.toml b/Cargo.toml index 3e534ae6..16a6c6cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ rstest = "0.15.0" [features] graphviz-dot = ["dot-writer"] +latex = [] [[bench]] name = "parser" diff --git a/src/program/latex.rs b/src/program/latex.rs new file mode 100644 index 00000000..553043e6 --- /dev/null +++ b/src/program/latex.rs @@ -0,0 +1,74 @@ +use crate::Program; + +#[derive(Debug)] +/// Settings to control the layout and rendering of circuits. +pub struct DiagramSettings { + /// Convert numerical constants, such as pi, to LaTeX form. + texify_numerical_constants: bool, + + /// Include qubits with indices between those explicitly referenced in the Quil program. + /// For example, if true, the diagram for `CNOT 0 2` would have three qubit lines: 0, 1, 2. + impute_missing_qubits: bool, + + /// Label qubit lines. + label_qubit_lines: bool, + + /// Write controlled rotations in a compact form. + /// For example, `RX(pi)` as `X_{\\pi}`, instead of the longer `R_X(\\pi)` + abbreviate_controlled_rotations: bool, + + /// The length by which qubit lines should be extended with open wires at the right of the diagram. + /// The default of 1 is the natural choice. The main reason for including this option + /// is that it may be appropriate for this to be 0 in subdiagrams. + qubit_line_open_wire_length: u32, + + /// Align measurement operations which appear at the end of the program. + right_align_terminal_measurements: bool, +} + +impl Default for DiagramSettings { + fn default() -> Self { + Self { + texify_numerical_constants: true, + impute_missing_qubits: false, + label_qubit_lines: true, + abbreviate_controlled_rotations: false, + qubit_line_open_wire_length: 1, + right_align_terminal_measurements: true, + } + } +} + +#[derive(thiserror::Error, Debug)] +pub enum LatexGenError { + // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. + #[error("This is an error on {qubit_index}.")] + SomeError{qubit_index: u32}, +} + +pub trait ToLatex { + fn to_latex(self, diagram_settings: DiagramSettings) -> Result; +} + +impl ToLatex for Program { + fn to_latex(self, diagram_settings: DiagramSettings) -> Result { + // TODO: Generate the Program LaTeX. + let latex = ""; + + Ok(latex.to_string()) + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + use crate::Program; + use super::{ToLatex, DiagramSettings}; + + #[test] + fn test_controlled_gate() { + let program = Program::from_str("CONTROLLED H 3 2").expect("Program should be returned."); + let latex = program.to_latex(DiagramSettings::default()).expect("LaTeX should generate without error."); + insta::assert_snapshot!(latex); + } +} \ No newline at end of file diff --git a/src/program/mod.rs b/src/program/mod.rs index db044dec..a609cf2f 100644 --- a/src/program/mod.rs +++ b/src/program/mod.rs @@ -39,6 +39,9 @@ pub type Result = std::result::Result>; #[cfg(feature = "graphviz-dot")] pub mod graphviz_dot; +#[cfg(feature = "latex")] +pub mod latex; + /// A Quil Program instance describes a quantum program with metadata used in execution. /// /// This contains not only instructions which are executed in turn on the quantum processor, but diff --git a/src/program/snapshots/quil_rs__program__latex__tests__controlled_gate.snap b/src/program/snapshots/quil_rs__program__latex__tests__controlled_gate.snap new file mode 100644 index 00000000..14a6b082 --- /dev/null +++ b/src/program/snapshots/quil_rs__program__latex__tests__controlled_gate.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex.rs +assertion_line: 104 +expression: latex +--- +\[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{2}}} & \gate{H} & \qw \\ +\lstick{\ket{q_{3}}} & \ctrl{-1} & \qw +\end{tikzcd} +\end{document} From 479974dd7abfe953fcc2fc8c2487b0de893000c1 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 6 Feb 2023 17:03:45 -0800 Subject: [PATCH 002/118] Add X and Y gate tests with helper function. --- src/program/latex.rs | 20 ++++++++++++++++--- ...uil_rs__program__latex__tests__x_gate.snap | 14 +++++++++++++ ...uil_rs__program__latex__tests__y_gate.snap | 15 ++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 src/program/snapshots/quil_rs__program__latex__tests__x_gate.snap create mode 100644 src/program/snapshots/quil_rs__program__latex__tests__y_gate.snap diff --git a/src/program/latex.rs b/src/program/latex.rs index 553043e6..f6d9996d 100644 --- a/src/program/latex.rs +++ b/src/program/latex.rs @@ -65,10 +65,24 @@ mod tests { use crate::Program; use super::{ToLatex, DiagramSettings}; + /// Take an instruction and return the LaTeX using the to_latex method. + fn get_latex(instructions: &str) -> String { + let program = Program::from_str(instructions).expect("Program should be returned."); + program.to_latex(DiagramSettings::default()).expect("LaTeX should generate without error.") + } + + #[test] + fn test_x_gate() { + insta::assert_snapshot!(get_latex("X 0")); + } + + #[test] + fn test_y_gate() { + insta::assert_snapshot!(get_latex("Y 1")); + } + #[test] fn test_controlled_gate() { - let program = Program::from_str("CONTROLLED H 3 2").expect("Program should be returned."); - let latex = program.to_latex(DiagramSettings::default()).expect("LaTeX should generate without error."); - insta::assert_snapshot!(latex); + insta::assert_snapshot!(get_latex("CONTROLLED H 3 2")); } } \ No newline at end of file diff --git a/src/program/snapshots/quil_rs__program__latex__tests__x_gate.snap b/src/program/snapshots/quil_rs__program__latex__tests__x_gate.snap new file mode 100644 index 00000000..d0f3d928 --- /dev/null +++ b/src/program/snapshots/quil_rs__program__latex__tests__x_gate.snap @@ -0,0 +1,14 @@ +--- +source: src/program/latex.rs +assertion_line: 77 +expression: "r\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{X} & \\qw\n\\end{tikzcd}\n\\end{document}\"" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \gate{X} & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/snapshots/quil_rs__program__latex__tests__y_gate.snap b/src/program/snapshots/quil_rs__program__latex__tests__y_gate.snap new file mode 100644 index 00000000..ac88ea92 --- /dev/null +++ b/src/program/snapshots/quil_rs__program__latex__tests__y_gate.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex.rs +assertion_line: 82 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{1}}} & \\gate{Y} & \\qw\n\\end{tikzcd}\n\\end{document}\n\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{1}}} & \gate{Y} & \qw +\end{tikzcd} +\end{document} + From 8fb30e59759519fb2fe8629657c37baf6daca9a4 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 7 Feb 2023 15:46:03 -0800 Subject: [PATCH 003/118] Move gate tests to tests submodule gates. --- src/program/latex.rs | 36 ++++++++++++++----- ...latex__tests__gates__gate_controlled.snap} | 0 ...program__latex__tests__gates__gate_x.snap} | 0 ...program__latex__tests__gates__gate_y.snap} | 0 4 files changed, 27 insertions(+), 9 deletions(-) rename src/program/snapshots/{quil_rs__program__latex__tests__controlled_gate.snap => quil_rs__program__latex__tests__gates__gate_controlled.snap} (100%) rename src/program/snapshots/{quil_rs__program__latex__tests__x_gate.snap => quil_rs__program__latex__tests__gates__gate_x.snap} (100%) rename src/program/snapshots/{quil_rs__program__latex__tests__y_gate.snap => quil_rs__program__latex__tests__gates__gate_y.snap} (100%) diff --git a/src/program/latex.rs b/src/program/latex.rs index f6d9996d..53492ae7 100644 --- a/src/program/latex.rs +++ b/src/program/latex.rs @@ -61,24 +61,42 @@ impl ToLatex for Program { #[cfg(test)] mod tests { - use std::str::FromStr; + use super::{DiagramSettings, ToLatex}; use crate::Program; - use super::{ToLatex, DiagramSettings}; + use std::str::FromStr; /// Take an instruction and return the LaTeX using the to_latex method. - fn get_latex(instructions: &str) -> String { + pub fn get_latex(instructions: &str) -> String { let program = Program::from_str(instructions).expect("Program should be returned."); - program.to_latex(DiagramSettings::default()).expect("LaTeX should generate without error.") + program + .to_latex(DiagramSettings::default()) + .expect("LaTeX should generate without error.") } #[test] - fn test_x_gate() { - insta::assert_snapshot!(get_latex("X 0")); + /// Test functionality of to_latex using default settings. + fn test_to_latex() { + let program = Program::from_str("").expect(""); + program.to_latex(DiagramSettings::default()).expect(""); } - #[test] - fn test_y_gate() { - insta::assert_snapshot!(get_latex("Y 1")); + mod gates { + use crate::program::latex::tests::get_latex; + + #[test] + fn test_gate_x() { + insta::assert_snapshot!(get_latex("X 0")); + } + + #[test] + fn test_gate_y() { + insta::assert_snapshot!(get_latex("Y 1")); + } + + #[test] + fn test_gate_controlled() { + insta::assert_snapshot!(get_latex("CONTROLLED H 3 2")); + } } #[test] diff --git a/src/program/snapshots/quil_rs__program__latex__tests__controlled_gate.snap b/src/program/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__controlled_gate.snap rename to src/program/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap diff --git a/src/program/snapshots/quil_rs__program__latex__tests__x_gate.snap b/src/program/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__x_gate.snap rename to src/program/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap diff --git a/src/program/snapshots/quil_rs__program__latex__tests__y_gate.snap b/src/program/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__y_gate.snap rename to src/program/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap From cb02ef8c837b3ca39639ea94abf3fc0639a6a0ac Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 7 Feb 2023 15:53:15 -0800 Subject: [PATCH 004/118] Add TikZ operators. --- src/program/latex.rs | 77 ++++++++++++++++++- ...sts__tikz_operators__tikz_cnot_target.snap | 6 ++ ...__tests__tikz_operators__tikz_control.snap | 6 ++ ...s__tikz_operators__tikz_cphase_target.snap | 6 ++ ..._tests__tikz_operators__tikz_left_ket.snap | 6 ++ ...__tests__tikz_operators__tikz_measure.snap | 6 ++ ...atex__tests__tikz_operators__tikz_nop.snap | 6 ++ ...tex__tests__tikz_operators__tikz_swap.snap | 6 ++ ...sts__tikz_operators__tikz_swap_target.snap | 6 ++ 9 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap create mode 100644 src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap create mode 100644 src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap create mode 100644 src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap create mode 100644 src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap create mode 100644 src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap create mode 100644 src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap create mode 100644 src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap diff --git a/src/program/latex.rs b/src/program/latex.rs index 53492ae7..a2d02e70 100644 --- a/src/program/latex.rs +++ b/src/program/latex.rs @@ -1,3 +1,5 @@ +use std::fmt::format; + use crate::Program; #[derive(Debug)] @@ -39,6 +41,34 @@ impl Default for DiagramSettings { } } +pub enum TikzOperators { + tikz_left_ket(u32), + tikz_control(i32), + tikz_cnot_target, + tikz_cphase_target, + tikz_swap(i32), + tikz_swap_target, + tikz_nop, + tikz_measure, +} + +impl TikzOperators { + fn get_tikz_operator(tikz_operator: TikzOperators) -> String { + match tikz_operator { + TikzOperators::tikz_left_ket(qubit) => { + format(format_args!(r#"\lstick{{\ket{{q_{{{qubit}}}}}}}"#)) + } // \lstick{\ket{q_{qubit}}} + TikzOperators::tikz_control(offset) => format(format_args!(r#"\ctrl{{{offset}}}"#)), // \ctrl{offset} + TikzOperators::tikz_cnot_target => r"\targ{}".to_string(), // \targ{} + TikzOperators::tikz_cphase_target => r"\control{}".to_string(), // \control{} + TikzOperators::tikz_swap(offset) => format(format_args!(r"\swap{{{offset}}}")), // \swap{offset} + TikzOperators::tikz_swap_target => r"\targX{}".to_string(), // \targX{} + TikzOperators::tikz_nop => r"\qw".to_string(), // \qw + TikzOperators::tikz_measure => r"\meter{}".to_string(), // \meter{} + } + } +} + #[derive(thiserror::Error, Debug)] pub enum LatexGenError { // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. @@ -99,8 +129,47 @@ mod tests { } } - #[test] - fn test_controlled_gate() { - insta::assert_snapshot!(get_latex("CONTROLLED H 3 2")); + mod tikz_operators { + use crate::program::latex::TikzOperators; + + #[test] + fn test_tikz_left_ket() { + insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_left_ket(0))); + } + + #[test] + fn test_tikz_control() { + insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_control(2))); + } + + #[test] + fn test_tikz_cnot_target() { + insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_cnot_target)); + } + + #[test] + fn test_tikz_cphase_target() { + insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_cphase_target)); + } + + #[test] + fn test_tikz_swap() { + insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_swap(4))); + } + + #[test] + fn test_tikz_swap_target() { + insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_swap_target)); + } + + #[test] + fn test_tikz_nop() { + insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_nop)); + } + + #[test] + fn test_tikz_measure() { + insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_measure)); + } } -} \ No newline at end of file +} diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap new file mode 100644 index 00000000..899f6ee6 --- /dev/null +++ b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 157 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_cnot_target)" +--- +\targ{} diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap new file mode 100644 index 00000000..e5f43424 --- /dev/null +++ b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 150 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_control(2))" +--- +\ctrl{2} diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap new file mode 100644 index 00000000..07bd40b2 --- /dev/null +++ b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 164 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_cphase_target)" +--- +\control{} diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap new file mode 100644 index 00000000..f1cce67e --- /dev/null +++ b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 143 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_left_ket(0))" +--- +\lstick{\ket{q_{0}}} diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap new file mode 100644 index 00000000..8daec78b --- /dev/null +++ b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 190 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_measure)" +--- +\meter{} diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap new file mode 100644 index 00000000..ebc5c10c --- /dev/null +++ b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 185 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_nop)" +--- +\qw diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap new file mode 100644 index 00000000..06a9f7fe --- /dev/null +++ b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 171 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_swap(4))" +--- +\swap{4} diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap new file mode 100644 index 00000000..faf86045 --- /dev/null +++ b/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 178 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_swap_target)" +--- +\targX{} From 5cb096d564114368726dfff2ff8beff5c88ff657 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 7 Feb 2023 16:03:02 -0800 Subject: [PATCH 005/118] Add latex dependency as feature. --- Cargo.lock | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 3 +- 2 files changed, 120 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90d94d8f..93947a46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "anes" version = "0.1.6" @@ -25,6 +40,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -70,6 +100,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + [[package]] name = "cfg-if" version = "1.0.0" @@ -234,6 +270,28 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2 1.0.47", + "quote 1.0.21", + "syn 1.0.103", + "synstructure", +] + [[package]] name = "fastrand" version = "1.8.0" @@ -361,6 +419,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec" + [[package]] name = "half" version = "1.8.2" @@ -444,6 +508,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "latex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41cf333d23534df4dbd7513d5cbf52513b6b4741b9e642efa3e835c984ad241" +dependencies = [ + "failure", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -565,6 +638,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + [[package]] name = "nom" version = "7.1.1" @@ -614,6 +696,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.30.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.15.0" @@ -694,7 +785,7 @@ version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" dependencies = [ - "unicode-xid", + "unicode-xid 0.1.0", ] [[package]] @@ -757,6 +848,7 @@ dependencies = [ "dot-writer", "indexmap", "insta", + "latex", "lexical", "nom", "nom_locate", @@ -909,6 +1001,12 @@ dependencies = [ "syn 1.0.103", ] +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + [[package]] name = "rustc_version" version = "0.4.0" @@ -1045,7 +1143,7 @@ checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", - "unicode-xid", + "unicode-xid 0.1.0", ] [[package]] @@ -1059,6 +1157,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2 1.0.47", + "quote 1.0.21", + "syn 1.0.103", + "unicode-xid 0.2.4", +] + [[package]] name = "tempfile" version = "3.3.0" @@ -1131,6 +1241,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 16a6c6cc..b96981a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ categories = ["parser-implementations", "science", "compilers", "emulators"] [dependencies] dot-writer = { version = "0.1.2", optional = true } +latex = { version = "0.3.1", optional = true } indexmap = "1.6.1" lexical = "6.1.1" nom = "7.1.1" @@ -29,7 +30,7 @@ rstest = "0.15.0" [features] graphviz-dot = ["dot-writer"] -latex = [] +to-latex = ["latex"] [[bench]] name = "parser" From 029d33718785f766baa8ad7323e9c06eec76e996 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 7 Feb 2023 16:56:43 -0800 Subject: [PATCH 006/118] Rename TikzOperators to TikzOperator and CamelCase variant names. --- src/program/latex.rs | 60 +++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/src/program/latex.rs b/src/program/latex.rs index a2d02e70..49cf0ee9 100644 --- a/src/program/latex.rs +++ b/src/program/latex.rs @@ -23,7 +23,7 @@ pub struct DiagramSettings { /// The default of 1 is the natural choice. The main reason for including this option /// is that it may be appropriate for this to be 0 in subdiagrams. qubit_line_open_wire_length: u32, - + /// Align measurement operations which appear at the end of the program. right_align_terminal_measurements: bool, } @@ -41,30 +41,28 @@ impl Default for DiagramSettings { } } -pub enum TikzOperators { - tikz_left_ket(u32), - tikz_control(i32), - tikz_cnot_target, - tikz_cphase_target, - tikz_swap(i32), - tikz_swap_target, - tikz_nop, - tikz_measure, +pub enum TikzOperator { + TikzLeftKet(u32), + TikzControl(i32), + TikzCnotTarget, + TikzCphaseTarget, + TikzSwap(i32), + TikzSwapTarget, + TikzNop, + TikzMeasure, } -impl TikzOperators { - fn get_tikz_operator(tikz_operator: TikzOperators) -> String { +impl TikzOperator { + fn get_tikz_operator(tikz_operator: Self) -> String { match tikz_operator { - TikzOperators::tikz_left_ket(qubit) => { - format(format_args!(r#"\lstick{{\ket{{q_{{{qubit}}}}}}}"#)) - } // \lstick{\ket{q_{qubit}}} - TikzOperators::tikz_control(offset) => format(format_args!(r#"\ctrl{{{offset}}}"#)), // \ctrl{offset} - TikzOperators::tikz_cnot_target => r"\targ{}".to_string(), // \targ{} - TikzOperators::tikz_cphase_target => r"\control{}".to_string(), // \control{} - TikzOperators::tikz_swap(offset) => format(format_args!(r"\swap{{{offset}}}")), // \swap{offset} - TikzOperators::tikz_swap_target => r"\targX{}".to_string(), // \targX{} - TikzOperators::tikz_nop => r"\qw".to_string(), // \qw - TikzOperators::tikz_measure => r"\meter{}".to_string(), // \meter{} + Self::TikzLeftKet(qubit) => format(format_args!(r#"\lstick{{\ket{{q_{{{qubit}}}}}}}"#)), // \lstick{\ket{q_{qubit}}} + Self::TikzControl(offset) => format(format_args!(r#"\ctrl{{{offset}}}"#)), // \ctrl{offset} + Self::TikzCnotTarget => r"\targ{}".to_string(), // \targ{} + Self::TikzCphaseTarget => r"\control{}".to_string(), // \control{} + Self::TikzSwap(offset) => format(format_args!(r"\swap{{{offset}}}")), // \swap{offset} + Self::TikzSwapTarget => r"\targX{}".to_string(), // \targX{} + Self::TikzNop => r"\qw".to_string(), // \qw + Self::TikzMeasure => r"\meter{}".to_string(), // \meter{} } } } @@ -130,46 +128,46 @@ mod tests { } mod tikz_operators { - use crate::program::latex::TikzOperators; + use crate::program::latex::TikzOperator; #[test] fn test_tikz_left_ket() { - insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_left_ket(0))); + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzLeftKet(0))); } #[test] fn test_tikz_control() { - insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_control(2))); + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzControl(2))); } #[test] fn test_tikz_cnot_target() { - insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_cnot_target)); + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzCnotTarget)); } #[test] fn test_tikz_cphase_target() { - insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_cphase_target)); + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzCphaseTarget)); } #[test] fn test_tikz_swap() { - insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_swap(4))); + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzSwap(4))); } #[test] fn test_tikz_swap_target() { - insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_swap_target)); + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzSwapTarget)); } #[test] fn test_tikz_nop() { - insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_nop)); + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzNop)); } #[test] fn test_tikz_measure() { - insta::assert_snapshot!(TikzOperators::get_tikz_operator(TikzOperators::tikz_measure)); + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzMeasure)); } } } From c9fc7e9628493f1e070f1bb496551e9c905b80a9 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 13 Feb 2023 10:34:41 -0800 Subject: [PATCH 007/118] Move latex.rs and snapshots into latex directory. --- src/program/{latex.rs => latex/mod.rs} | 0 ...l_rs__program__latex__tests__gates__gate_controlled.snap | 0 .../quil_rs__program__latex__tests__gates__gate_x.snap | 0 .../quil_rs__program__latex__tests__gates__gate_x.snap.new | 6 ++++++ .../quil_rs__program__latex__tests__gates__gate_y.snap | 0 ...ram__latex__tests__tikz_operators__tikz_cnot_target.snap | 0 ...program__latex__tests__tikz_operators__tikz_control.snap | 0 ...m__latex__tests__tikz_operators__tikz_cphase_target.snap | 0 ...rogram__latex__tests__tikz_operators__tikz_left_ket.snap | 0 ...program__latex__tests__tikz_operators__tikz_measure.snap | 0 ...rs__program__latex__tests__tikz_operators__tikz_nop.snap | 0 ...s__program__latex__tests__tikz_operators__tikz_swap.snap | 0 ...ram__latex__tests__tikz_operators__tikz_swap_target.snap | 0 13 files changed, 6 insertions(+) rename src/program/{latex.rs => latex/mod.rs} (100%) rename src/program/{ => latex}/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap (100%) rename src/program/{ => latex}/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap (100%) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new rename src/program/{ => latex}/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap (100%) rename src/program/{ => latex}/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap (100%) rename src/program/{ => latex}/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap (100%) rename src/program/{ => latex}/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap (100%) rename src/program/{ => latex}/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap (100%) rename src/program/{ => latex}/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap (100%) rename src/program/{ => latex}/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap (100%) rename src/program/{ => latex}/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap (100%) rename src/program/{ => latex}/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap (100%) diff --git a/src/program/latex.rs b/src/program/latex/mod.rs similarity index 100% rename from src/program/latex.rs rename to src/program/latex/mod.rs diff --git a/src/program/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap diff --git a/src/program/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new new file mode 100644 index 00000000..37015ec6 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 116 +expression: "get_latex(\"X 0\")" +--- + diff --git a/src/program/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap diff --git a/src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap From a25abb4acc1cc9c706ab60234eb6a6c17d85702e Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 16 Feb 2023 15:46:46 -0800 Subject: [PATCH 008/118] Initial commit for LaTeX abstract factory model. --- Cargo.lock | 120 +----------- Cargo.toml | 3 +- src/program/latex.rs | 118 ++++++++++++ src/program/latex/README.md | 119 ++++++++++++ src/program/latex/generation.rs | 7 + src/program/latex/generation/diagram.rs | 51 ++++++ ...sts__tikz_operators__tikz_cnot_target.snap | 6 + ...__tests__tikz_operators__tikz_control.snap | 6 + ...s__tikz_operators__tikz_cphase_target.snap | 6 + ..._tests__tikz_operators__tikz_left_ket.snap | 6 + ...__tests__tikz_operators__tikz_measure.snap | 6 + ...tikz__tests__tikz_operators__tikz_nop.snap | 6 + ...ikz__tests__tikz_operators__tikz_swap.snap | 6 + ...sts__tikz_operators__tikz_swap_target.snap | 6 + src/program/latex/generation/diagram/tikz.rs | 80 ++++++++ .../latex/generation/diagram/tikz/quantikz.rs | 10 + src/program/latex/mod.rs | 173 ------------------ ...gram__latex__tests__gates__gate_x.snap.new | 6 - ...sts__tikz_operators__tikz_cnot_target.snap | 6 - ...__tests__tikz_operators__tikz_control.snap | 6 - ...s__tikz_operators__tikz_cphase_target.snap | 6 - ..._tests__tikz_operators__tikz_left_ket.snap | 6 - ...__tests__tikz_operators__tikz_measure.snap | 6 - ...atex__tests__tikz_operators__tikz_nop.snap | 6 - ...tex__tests__tikz_operators__tikz_swap.snap | 6 - ...sts__tikz_operators__tikz_swap_target.snap | 6 - ...tikz_default_diagram_gate_controlled.snap} | 0 ...tes__quantikz_default_diagram_gate_x.snap} | 0 ...tes__quantikz_default_diagram_gate_y.snap} | 0 29 files changed, 436 insertions(+), 347 deletions(-) create mode 100644 src/program/latex.rs create mode 100644 src/program/latex/README.md create mode 100644 src/program/latex/generation.rs create mode 100644 src/program/latex/generation/diagram.rs create mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cnot_target.snap create mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_control.snap create mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cphase_target.snap create mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_left_ket.snap create mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_measure.snap create mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_nop.snap create mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap.snap create mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap_target.snap create mode 100644 src/program/latex/generation/diagram/tikz.rs create mode 100644 src/program/latex/generation/diagram/tikz/quantikz.rs delete mode 100644 src/program/latex/mod.rs delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap rename src/program/{latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap => snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_controlled.snap} (100%) rename src/program/{latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap => snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_x.snap} (100%) rename src/program/{latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap => snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_y.snap} (100%) diff --git a/Cargo.lock b/Cargo.lock index 93947a46..90d94d8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "anes" version = "0.1.6" @@ -40,21 +25,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "backtrace" -version = "0.3.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "bit-set" version = "0.5.3" @@ -100,12 +70,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - [[package]] name = "cfg-if" version = "1.0.0" @@ -270,28 +234,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" -[[package]] -name = "failure" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" -dependencies = [ - "backtrace", - "failure_derive", -] - -[[package]] -name = "failure_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" -dependencies = [ - "proc-macro2 1.0.47", - "quote 1.0.21", - "syn 1.0.103", - "synstructure", -] - [[package]] name = "fastrand" version = "1.8.0" @@ -419,12 +361,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "gimli" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec" - [[package]] name = "half" version = "1.8.2" @@ -508,15 +444,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "latex" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41cf333d23534df4dbd7513d5cbf52513b6b4741b9e642efa3e835c984ad241" -dependencies = [ - "failure", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -638,15 +565,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] - [[package]] name = "nom" version = "7.1.1" @@ -696,15 +614,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.30.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.15.0" @@ -785,7 +694,7 @@ version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" dependencies = [ - "unicode-xid 0.1.0", + "unicode-xid", ] [[package]] @@ -848,7 +757,6 @@ dependencies = [ "dot-writer", "indexmap", "insta", - "latex", "lexical", "nom", "nom_locate", @@ -1001,12 +909,6 @@ dependencies = [ "syn 1.0.103", ] -[[package]] -name = "rustc-demangle" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" - [[package]] name = "rustc_version" version = "0.4.0" @@ -1143,7 +1045,7 @@ checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", - "unicode-xid 0.1.0", + "unicode-xid", ] [[package]] @@ -1157,18 +1059,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2 1.0.47", - "quote 1.0.21", - "syn 1.0.103", - "unicode-xid 0.2.4", -] - [[package]] name = "tempfile" version = "3.3.0" @@ -1241,12 +1131,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index b96981a4..16a6c6cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ categories = ["parser-implementations", "science", "compilers", "emulators"] [dependencies] dot-writer = { version = "0.1.2", optional = true } -latex = { version = "0.3.1", optional = true } indexmap = "1.6.1" lexical = "6.1.1" nom = "7.1.1" @@ -30,7 +29,7 @@ rstest = "0.15.0" [features] graphviz-dot = ["dot-writer"] -to-latex = ["latex"] +latex = [] [[bench]] name = "parser" diff --git a/src/program/latex.rs b/src/program/latex.rs new file mode 100644 index 00000000..c5fc7977 --- /dev/null +++ b/src/program/latex.rs @@ -0,0 +1,118 @@ +use crate::Program; +use generation::{LatexFactory, diagram::DiagramFactory}; + +mod generation; + +pub struct Request { + document: Document, + settings: Option, +} + +impl Default for Request { + fn default() -> Self { + Self { document: Document::Diagram, settings: None} + } +} + +pub enum Document { + Diagram, + None, +} + +pub struct Settings; + +// TODO: Move inside of the ConcreteFactory +impl Default for Settings { + fn default() -> Self { + Self { } + } +} + +#[derive(thiserror::Error, Debug)] +pub enum LatexGenerationError { + // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. + #[error("This is an error on {qubit_index}.")] + SampleError{qubit_index: u32}, + #[error("Error NoRequest: A request is required to use this feature.")] + NoRequest, +} + +#[cfg(feature = "latex")] +pub trait Latex { + /// Returns a Result containing a string of LaTeX or a LatexGenerationError. + /// + /// # Arguments + /// * `option` - An Option containing a LaTeX Request. + /// * `Option::Some` - A valid Request that can be parsed. + /// * `Option::None` - An invalid Request that throws an error. + fn to_latex(self, request: Request) -> Result; +} + +impl Latex for Program { + fn to_latex(self, request: Request) -> Result { + match request.document { + Document::Diagram => { + let diagram_factory = Box::new(DiagramFactory {}); + let tikz_diagram_factory = diagram_factory.create_tikz_diagram(); + + tikz_diagram_factory.generate_default_quantikz_diagram(); + }, + Document::None => { + panic!("{}", LatexGenerationError::NoRequest); + }, + } + + // // TODO: Generate the Program LaTeX. + // let latex = ""; + + Ok("".to_string()) + } +} + +#[cfg(test)] +mod tests { + use super::{Program, Latex, Request, Document}; + use std::str::FromStr; + + /// Take an instruction and return the LaTeX using the to_latex method. + pub fn get_quantikz_diagram(instructions: &str) -> String { + let program = Program::from_str(instructions).expect("program should be returned"); + program.to_latex(Request::default()).expect("Quantikz diagram should generate without error") + } + + #[test] + #[should_panic(expected = "")] + /// Test functionality of to_latex using a request with a None document. + fn test_to_latex_should_panic_with_document_none() { + // Create an empty program as a string. + let program = Program::from_str("").expect("empty program should be created"); + program.to_latex(Request {document: Document::None, settings: None}).expect("function should panic with None document"); + } + + #[test] + /// Test functionality of to_latex using a default request of Document::Diagram and Settings::None. + fn test_to_latex_with_default_request() { + // Create an empty program as a string. + let program = Program::from_str("").expect("empty program should be created"); + program.to_latex(Request::default()).expect("function should work with default request"); + } + + mod gates { + use crate::program::latex::tests::get_quantikz_diagram; + + #[test] + fn test_quantikz_default_diagram_gate_x() { + insta::assert_snapshot!(get_quantikz_diagram("X 0")); + } + + #[test] + fn test_quantikz_default_diagram_gate_y() { + insta::assert_snapshot!(get_quantikz_diagram("Y 1")); + } + + #[test] + fn test_quantikz_default_diagram_gate_controlled() { + insta::assert_snapshot!(get_quantikz_diagram("CONTROLLED H 3 2")); + } + } +} diff --git a/src/program/latex/README.md b/src/program/latex/README.md new file mode 100644 index 00000000..bef64412 --- /dev/null +++ b/src/program/latex/README.md @@ -0,0 +1,119 @@ +# Quil LaTeX Generation using the AbstractFactory Design Pattern + +--- +## Context + +LaTeX is more than a circuit building utility, in general, it can be thought of as a powerful document generation tool with an expansive ecosystem of libraries each with their own unique packages. The abstract factory pattern used in this design opens LaTeX to quil-rs as more than a circuit building utility. It also narrows down LaTeX to documents that are explicitly made available (product branding opportunity) and specific to quantum programs which can be easily expanded upon, and encouraged to, using this design pattern. Provided the type of document is known, this feature can generate it in LaTeX. + + +--- +## User Compatibility + +If the user knows nothing about LaTeX but knows that the program can generate circuit diagrams that they can then copy and paste into a LaTeX renderer, they only need to call to_latex() using Python bindings. The default is to produce the same results with the same simplicity as pyQuil's to_latex feature, which is a Quantikz diagram of qubits and gates displayed on a circuit. + +With some knowledge of LaTeX, the user could also generate other documents made available, which this design encourages. For instance, if a user requests a graphical representation of a quil program that is made available, e.g. the probability density, a graph ConcreteFactory can be implemented that produces a pgfplots AbstractProduct which is defined stylistically in a, e.g. "quilplots", ConcreteProduct. + + +--- +## File Structure + +program/ +|-- latex.rs +|-- latex/ + |-- generation.rs + |-- generation/ + |-- diagram.rs + |-- diagram/ + |-- tikz.rs + |-- tikz/ + |-- quantikz.rs + +--- +## Directory and File Descriptions + +latex.rs +Defines the API to use the LaTeX generation feature. + +latex/ +Module containing all LaTeX generation code. + +generation.rs +Defines a LaTeX AbstractFactory that builds document-specific ConcreteFactories. + +generation/ +Module containing all LaTeX document-building ConcreteFactories and their products. + +diagram.rs +Defines a ConcreteFactory that builds AbstractDiagrams. + +diagram/ +Module containing the LaTeX AbstractDiagram and its ConcreteDiagrams. + +tikz.rs +Defines an AbstractDiagram TikZ that is an expansive diagram building library used to build ConcreteDiagrams. + +tikz/ +Module containing all ConcreteDiagrams that can be built using the TikZ library. + +quantikz.rs +Defines a ConcreteDiagram that is a Quantikz circuit from the TikZ library. + +**snapshots/ +Directories of snapshot unit tests. + + +--- +## Keywords + +LaTeX: a document generation tool. +TikZ: a LaTeX library used to generate a variety of LaTeX diagrams. +Quantikz: a TikZ package used to generate quantum circuits. +pgfplots: a LaTeX library used to visualize scientific/technical graphs. + + + +--- +## API Design + +quil-rs to_latex feature should be as flexible as Python's optional parameters. With Python bindings this can be as simple as calling to_latex(). The method signature for the same quil-rs function is + +`to_latex(self, request: Option) -> Result`. + +A Request is a struct with two attributes: + +document: Document, // An enum variant representing the type of document being requested. +settings: Settings, // Specific settings for how the document is rendered. + +Defaults +document: "diagram" // Specifically, a Quantikz diagram same as pyQuil. +settings: None // Default settings at the product level. + +Note on Settings + +- If settings are not specified then the default settings are defined in the ConcreteFactory if they can be generalized over all diagrams, or (inclusively) in the ConcreteProduct if they cannot be generalized but can only be applied on a specific type of diagram (e.q. Quantikz). Refer to the documentation for these specifications. + +- Without specifying them, settings are inferred depending on the type of document being created. More concretely, settings for diagrams are different from settings for plots. The program determines what type of settings are being used by matching it to the requested document type. This allows the document factory to apply the settings to the document being produced. + + +--- +## Program Flow + +Example generating a default (Quantikz) LaTeX diagram + +1. The Program makes a default request using the API defined in latex.rs. + +2. The request is parsed in latex.rs which is then passed to the create_diagram method on the AbstractFactory (latex/generation.rs) to build a diagram of a Quil Program with no settings. + +3. The AbstractFactory is then defined into a ConcreteFactory that builds diagrams (latex/generation/diagram.rs) with generalized settings. + +4. The ConcreteFactory then makes a request to generate a default diagram from the TikZ AbstractProduct (latex/generation/diagram/tikz.rs). + +5. The AbstractProduct is then defined defined into a ConcreteProduct that builds Quantikz diagrams (latex/generation/diagram/tikz/quantikz.rs). + +6. The Quantikz diagram is built at the ConcreteDiagram level and is returned to the Program + + +--- +## Conclusion + +The use of the AbstractFactory pattern opens several avenues within the LaTeX ecosystem for the rendering of any kind of document that might be useful in visualizing Quil programs or even generating reports (considering the many more uses for LaTeX beyond diagram rendering). The vision is that at some point, Quil Programs may want to be documented in another form that LaTeX will likely be able to provide. In this case, the LatexFactory can be expanded to implement a new factory that can construct the desired Quil document e.g. Pgfplots, a LaTeX package used to generate scientific/technical graphs. \ No newline at end of file diff --git a/src/program/latex/generation.rs b/src/program/latex/generation.rs new file mode 100644 index 00000000..5bfbdfea --- /dev/null +++ b/src/program/latex/generation.rs @@ -0,0 +1,7 @@ +use self::diagram::tikz::Tikz; + +pub mod diagram; + +pub trait LatexFactory { + fn create_tikz_diagram(&self) -> Box; +} \ No newline at end of file diff --git a/src/program/latex/generation/diagram.rs b/src/program/latex/generation/diagram.rs new file mode 100644 index 00000000..80f49bdd --- /dev/null +++ b/src/program/latex/generation/diagram.rs @@ -0,0 +1,51 @@ +use self::tikz::{Tikz, quantikz::Quantikz}; +use super::LatexFactory; + +pub mod tikz; + +pub struct DiagramFactory; + +impl LatexFactory for DiagramFactory { + fn create_tikz_diagram(&self) -> Box { + Box::new(Quantikz {}) + } +} + +#[derive(Debug)] +/// Settings to control the layout and rendering of circuits. +pub struct DiagramSettings { + /// Convert numerical constants, such as pi, to LaTeX form. + texify_numerical_constants: bool, + + /// Include qubits with indices between those explicitly referenced in the Quil program. + /// For example, if true, the diagram for `CNOT 0 2` would have three qubit lines: 0, 1, 2. + impute_missing_qubits: bool, + + /// Label qubit lines. + label_qubit_lines: bool, + + /// Write controlled rotations in a compact form. + /// For example, `RX(pi)` as `X_{\\pi}`, instead of the longer `R_X(\\pi)` + abbreviate_controlled_rotations: bool, + + /// The length by which qubit lines should be extended with open wires at the right of the diagram. + /// The default of 1 is the natural choice. The main reason for including this option + /// is that it may be appropriate for this to be 0 in subdiagrams. + qubit_line_open_wire_length: u32, + + /// Align measurement operations which appear at the end of the program. + right_align_terminal_measurements: bool, +} + +impl Default for DiagramSettings { + fn default() -> Self { + Self { + texify_numerical_constants: true, + impute_missing_qubits: false, + label_qubit_lines: true, + abbreviate_controlled_rotations: false, + qubit_line_open_wire_length: 1, + right_align_terminal_measurements: true, + } + } +} \ No newline at end of file diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cnot_target.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cnot_target.snap new file mode 100644 index 00000000..85acfa48 --- /dev/null +++ b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cnot_target.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/generation/diagram/tikz.rs +assertion_line: 52 +expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzCnotTarget)" +--- +\targ{} diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_control.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_control.snap new file mode 100644 index 00000000..3cae6051 --- /dev/null +++ b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_control.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/generation/diagram/tikz.rs +assertion_line: 47 +expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzControl(2))" +--- +\ctrl{2} diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cphase_target.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cphase_target.snap new file mode 100644 index 00000000..91fe2967 --- /dev/null +++ b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cphase_target.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/generation/diagram/tikz.rs +assertion_line: 57 +expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzCphaseTarget)" +--- +\control{} diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_left_ket.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_left_ket.snap new file mode 100644 index 00000000..350c39f3 --- /dev/null +++ b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_left_ket.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/generation/diagram/tikz.rs +assertion_line: 42 +expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzLeftKet(0))" +--- +\lstick{\ket{q_{0}}} diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_measure.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_measure.snap new file mode 100644 index 00000000..e6714223 --- /dev/null +++ b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_measure.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/generation/diagram/tikz.rs +assertion_line: 77 +expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzMeasure)" +--- +\meter{} diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_nop.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_nop.snap new file mode 100644 index 00000000..48334e6a --- /dev/null +++ b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_nop.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/generation/diagram/tikz.rs +assertion_line: 72 +expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzNop)" +--- +\qw diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap.snap new file mode 100644 index 00000000..4bef445d --- /dev/null +++ b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/generation/diagram/tikz.rs +assertion_line: 62 +expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzSwap(4))" +--- +\swap{4} diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap_target.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap_target.snap new file mode 100644 index 00000000..3acd2f2d --- /dev/null +++ b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap_target.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/generation/diagram/tikz.rs +assertion_line: 67 +expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzSwapTarget)" +--- +\targX{} diff --git a/src/program/latex/generation/diagram/tikz.rs b/src/program/latex/generation/diagram/tikz.rs new file mode 100644 index 00000000..43aaa9e5 --- /dev/null +++ b/src/program/latex/generation/diagram/tikz.rs @@ -0,0 +1,80 @@ +use std::fmt::format; + +pub mod quantikz; + +pub trait Tikz { + fn generate_default_quantikz_diagram(&self); +} + +pub enum TikzOperator { + TikzLeftKet(u32), + TikzControl(i32), + TikzCnotTarget, + TikzCphaseTarget, + TikzSwap(i32), + TikzSwapTarget, + TikzNop, + TikzMeasure, +} + +impl TikzOperator { + fn get_tikz_operator(tikz_operator: Self) -> String { + match tikz_operator { + Self::TikzLeftKet(qubit) => format(format_args!(r#"\lstick{{\ket{{q_{{{qubit}}}}}}}"#)), // \lstick{\ket{q_{qubit}}} + Self::TikzControl(offset) => format(format_args!(r#"\ctrl{{{offset}}}"#)), // \ctrl{offset} + Self::TikzCnotTarget => r"\targ{}".to_string(), // \targ{} + Self::TikzCphaseTarget => r"\control{}".to_string(), // \control{} + Self::TikzSwap(offset) => format(format_args!(r"\swap{{{offset}}}")), // \swap{offset} + Self::TikzSwapTarget => r"\targX{}".to_string(), // \targX{} + Self::TikzNop => r"\qw".to_string(), // \qw + Self::TikzMeasure => r"\meter{}".to_string(), // \meter{} + } + } +} + +#[cfg(test)] +mod tests { + mod tikz_operators { + use super::super::TikzOperator; + + #[test] + fn test_tikz_left_ket() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzLeftKet(0))); + } + + #[test] + fn test_tikz_control() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzControl(2))); + } + + #[test] + fn test_tikz_cnot_target() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzCnotTarget)); + } + + #[test] + fn test_tikz_cphase_target() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzCphaseTarget)); + } + + #[test] + fn test_tikz_swap() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzSwap(4))); + } + + #[test] + fn test_tikz_swap_target() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzSwapTarget)); + } + + #[test] + fn test_tikz_nop() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzNop)); + } + + #[test] + fn test_tikz_measure() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzMeasure)); + } + } +} \ No newline at end of file diff --git a/src/program/latex/generation/diagram/tikz/quantikz.rs b/src/program/latex/generation/diagram/tikz/quantikz.rs new file mode 100644 index 00000000..1665995f --- /dev/null +++ b/src/program/latex/generation/diagram/tikz/quantikz.rs @@ -0,0 +1,10 @@ +use super::Tikz; + +pub struct Quantikz; + +impl Tikz for Quantikz { + fn generate_default_quantikz_diagram(&self) { + // TODO: Generate a Quantikz diagram of the program with default settings. + println!("Generating default Quantikz circuit.") + } +} \ No newline at end of file diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs deleted file mode 100644 index 49cf0ee9..00000000 --- a/src/program/latex/mod.rs +++ /dev/null @@ -1,173 +0,0 @@ -use std::fmt::format; - -use crate::Program; - -#[derive(Debug)] -/// Settings to control the layout and rendering of circuits. -pub struct DiagramSettings { - /// Convert numerical constants, such as pi, to LaTeX form. - texify_numerical_constants: bool, - - /// Include qubits with indices between those explicitly referenced in the Quil program. - /// For example, if true, the diagram for `CNOT 0 2` would have three qubit lines: 0, 1, 2. - impute_missing_qubits: bool, - - /// Label qubit lines. - label_qubit_lines: bool, - - /// Write controlled rotations in a compact form. - /// For example, `RX(pi)` as `X_{\\pi}`, instead of the longer `R_X(\\pi)` - abbreviate_controlled_rotations: bool, - - /// The length by which qubit lines should be extended with open wires at the right of the diagram. - /// The default of 1 is the natural choice. The main reason for including this option - /// is that it may be appropriate for this to be 0 in subdiagrams. - qubit_line_open_wire_length: u32, - - /// Align measurement operations which appear at the end of the program. - right_align_terminal_measurements: bool, -} - -impl Default for DiagramSettings { - fn default() -> Self { - Self { - texify_numerical_constants: true, - impute_missing_qubits: false, - label_qubit_lines: true, - abbreviate_controlled_rotations: false, - qubit_line_open_wire_length: 1, - right_align_terminal_measurements: true, - } - } -} - -pub enum TikzOperator { - TikzLeftKet(u32), - TikzControl(i32), - TikzCnotTarget, - TikzCphaseTarget, - TikzSwap(i32), - TikzSwapTarget, - TikzNop, - TikzMeasure, -} - -impl TikzOperator { - fn get_tikz_operator(tikz_operator: Self) -> String { - match tikz_operator { - Self::TikzLeftKet(qubit) => format(format_args!(r#"\lstick{{\ket{{q_{{{qubit}}}}}}}"#)), // \lstick{\ket{q_{qubit}}} - Self::TikzControl(offset) => format(format_args!(r#"\ctrl{{{offset}}}"#)), // \ctrl{offset} - Self::TikzCnotTarget => r"\targ{}".to_string(), // \targ{} - Self::TikzCphaseTarget => r"\control{}".to_string(), // \control{} - Self::TikzSwap(offset) => format(format_args!(r"\swap{{{offset}}}")), // \swap{offset} - Self::TikzSwapTarget => r"\targX{}".to_string(), // \targX{} - Self::TikzNop => r"\qw".to_string(), // \qw - Self::TikzMeasure => r"\meter{}".to_string(), // \meter{} - } - } -} - -#[derive(thiserror::Error, Debug)] -pub enum LatexGenError { - // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. - #[error("This is an error on {qubit_index}.")] - SomeError{qubit_index: u32}, -} - -pub trait ToLatex { - fn to_latex(self, diagram_settings: DiagramSettings) -> Result; -} - -impl ToLatex for Program { - fn to_latex(self, diagram_settings: DiagramSettings) -> Result { - // TODO: Generate the Program LaTeX. - let latex = ""; - - Ok(latex.to_string()) - } -} - -#[cfg(test)] -mod tests { - use super::{DiagramSettings, ToLatex}; - use crate::Program; - use std::str::FromStr; - - /// Take an instruction and return the LaTeX using the to_latex method. - pub fn get_latex(instructions: &str) -> String { - let program = Program::from_str(instructions).expect("Program should be returned."); - program - .to_latex(DiagramSettings::default()) - .expect("LaTeX should generate without error.") - } - - #[test] - /// Test functionality of to_latex using default settings. - fn test_to_latex() { - let program = Program::from_str("").expect(""); - program.to_latex(DiagramSettings::default()).expect(""); - } - - mod gates { - use crate::program::latex::tests::get_latex; - - #[test] - fn test_gate_x() { - insta::assert_snapshot!(get_latex("X 0")); - } - - #[test] - fn test_gate_y() { - insta::assert_snapshot!(get_latex("Y 1")); - } - - #[test] - fn test_gate_controlled() { - insta::assert_snapshot!(get_latex("CONTROLLED H 3 2")); - } - } - - mod tikz_operators { - use crate::program::latex::TikzOperator; - - #[test] - fn test_tikz_left_ket() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzLeftKet(0))); - } - - #[test] - fn test_tikz_control() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzControl(2))); - } - - #[test] - fn test_tikz_cnot_target() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzCnotTarget)); - } - - #[test] - fn test_tikz_cphase_target() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzCphaseTarget)); - } - - #[test] - fn test_tikz_swap() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzSwap(4))); - } - - #[test] - fn test_tikz_swap_target() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzSwapTarget)); - } - - #[test] - fn test_tikz_nop() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzNop)); - } - - #[test] - fn test_tikz_measure() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzMeasure)); - } - } -} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new deleted file mode 100644 index 37015ec6..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/mod.rs -assertion_line: 116 -expression: "get_latex(\"X 0\")" ---- - diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap deleted file mode 100644 index 899f6ee6..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 157 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_cnot_target)" ---- -\targ{} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap deleted file mode 100644 index e5f43424..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 150 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_control(2))" ---- -\ctrl{2} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap deleted file mode 100644 index 07bd40b2..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 164 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_cphase_target)" ---- -\control{} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap deleted file mode 100644 index f1cce67e..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 143 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_left_ket(0))" ---- -\lstick{\ket{q_{0}}} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap deleted file mode 100644 index 8daec78b..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 190 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_measure)" ---- -\meter{} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap deleted file mode 100644 index ebc5c10c..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 185 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_nop)" ---- -\qw diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap deleted file mode 100644 index 06a9f7fe..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 171 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_swap(4))" ---- -\swap{4} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap deleted file mode 100644 index faf86045..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 178 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_swap_target)" ---- -\targX{} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap b/src/program/snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_controlled.snap similarity index 100% rename from src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap rename to src/program/snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_controlled.snap diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap b/src/program/snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_x.snap similarity index 100% rename from src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap rename to src/program/snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_x.snap diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap b/src/program/snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_y.snap similarity index 100% rename from src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap rename to src/program/snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_y.snap From 1d418a4d67a8744d44f09c51392fdc7a3219e95e Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 17 Feb 2023 13:33:17 -0800 Subject: [PATCH 009/118] Revert "Initial commit for LaTeX abstract factory model." This reverts commit a25abb4acc1cc9c706ab60234eb6a6c17d85702e. --- Cargo.lock | 120 +++++++++++- Cargo.toml | 3 +- src/program/latex.rs | 118 ------------ src/program/latex/README.md | 119 ------------ src/program/latex/generation.rs | 7 - src/program/latex/generation/diagram.rs | 51 ------ ...sts__tikz_operators__tikz_cnot_target.snap | 6 - ...__tests__tikz_operators__tikz_control.snap | 6 - ...s__tikz_operators__tikz_cphase_target.snap | 6 - ..._tests__tikz_operators__tikz_left_ket.snap | 6 - ...__tests__tikz_operators__tikz_measure.snap | 6 - ...tikz__tests__tikz_operators__tikz_nop.snap | 6 - ...ikz__tests__tikz_operators__tikz_swap.snap | 6 - ...sts__tikz_operators__tikz_swap_target.snap | 6 - src/program/latex/generation/diagram/tikz.rs | 80 -------- .../latex/generation/diagram/tikz/quantikz.rs | 10 - src/program/latex/mod.rs | 173 ++++++++++++++++++ ...latex__tests__gates__gate_controlled.snap} | 0 ...program__latex__tests__gates__gate_x.snap} | 0 ...gram__latex__tests__gates__gate_x.snap.new | 6 + ...program__latex__tests__gates__gate_y.snap} | 0 ...sts__tikz_operators__tikz_cnot_target.snap | 6 + ...__tests__tikz_operators__tikz_control.snap | 6 + ...s__tikz_operators__tikz_cphase_target.snap | 6 + ..._tests__tikz_operators__tikz_left_ket.snap | 6 + ...__tests__tikz_operators__tikz_measure.snap | 6 + ...atex__tests__tikz_operators__tikz_nop.snap | 6 + ...tex__tests__tikz_operators__tikz_swap.snap | 6 + ...sts__tikz_operators__tikz_swap_target.snap | 6 + 29 files changed, 347 insertions(+), 436 deletions(-) delete mode 100644 src/program/latex.rs delete mode 100644 src/program/latex/README.md delete mode 100644 src/program/latex/generation.rs delete mode 100644 src/program/latex/generation/diagram.rs delete mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cnot_target.snap delete mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_control.snap delete mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cphase_target.snap delete mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_left_ket.snap delete mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_measure.snap delete mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_nop.snap delete mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap.snap delete mode 100644 src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap_target.snap delete mode 100644 src/program/latex/generation/diagram/tikz.rs delete mode 100644 src/program/latex/generation/diagram/tikz/quantikz.rs create mode 100644 src/program/latex/mod.rs rename src/program/{snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_controlled.snap => latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap} (100%) rename src/program/{snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_x.snap => latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap} (100%) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new rename src/program/{snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_y.snap => latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap} (100%) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap diff --git a/Cargo.lock b/Cargo.lock index 90d94d8f..93947a46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "anes" version = "0.1.6" @@ -25,6 +40,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -70,6 +100,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + [[package]] name = "cfg-if" version = "1.0.0" @@ -234,6 +270,28 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2 1.0.47", + "quote 1.0.21", + "syn 1.0.103", + "synstructure", +] + [[package]] name = "fastrand" version = "1.8.0" @@ -361,6 +419,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec" + [[package]] name = "half" version = "1.8.2" @@ -444,6 +508,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "latex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41cf333d23534df4dbd7513d5cbf52513b6b4741b9e642efa3e835c984ad241" +dependencies = [ + "failure", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -565,6 +638,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + [[package]] name = "nom" version = "7.1.1" @@ -614,6 +696,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.30.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.15.0" @@ -694,7 +785,7 @@ version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" dependencies = [ - "unicode-xid", + "unicode-xid 0.1.0", ] [[package]] @@ -757,6 +848,7 @@ dependencies = [ "dot-writer", "indexmap", "insta", + "latex", "lexical", "nom", "nom_locate", @@ -909,6 +1001,12 @@ dependencies = [ "syn 1.0.103", ] +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + [[package]] name = "rustc_version" version = "0.4.0" @@ -1045,7 +1143,7 @@ checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", - "unicode-xid", + "unicode-xid 0.1.0", ] [[package]] @@ -1059,6 +1157,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2 1.0.47", + "quote 1.0.21", + "syn 1.0.103", + "unicode-xid 0.2.4", +] + [[package]] name = "tempfile" version = "3.3.0" @@ -1131,6 +1241,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 16a6c6cc..b96981a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ categories = ["parser-implementations", "science", "compilers", "emulators"] [dependencies] dot-writer = { version = "0.1.2", optional = true } +latex = { version = "0.3.1", optional = true } indexmap = "1.6.1" lexical = "6.1.1" nom = "7.1.1" @@ -29,7 +30,7 @@ rstest = "0.15.0" [features] graphviz-dot = ["dot-writer"] -latex = [] +to-latex = ["latex"] [[bench]] name = "parser" diff --git a/src/program/latex.rs b/src/program/latex.rs deleted file mode 100644 index c5fc7977..00000000 --- a/src/program/latex.rs +++ /dev/null @@ -1,118 +0,0 @@ -use crate::Program; -use generation::{LatexFactory, diagram::DiagramFactory}; - -mod generation; - -pub struct Request { - document: Document, - settings: Option, -} - -impl Default for Request { - fn default() -> Self { - Self { document: Document::Diagram, settings: None} - } -} - -pub enum Document { - Diagram, - None, -} - -pub struct Settings; - -// TODO: Move inside of the ConcreteFactory -impl Default for Settings { - fn default() -> Self { - Self { } - } -} - -#[derive(thiserror::Error, Debug)] -pub enum LatexGenerationError { - // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. - #[error("This is an error on {qubit_index}.")] - SampleError{qubit_index: u32}, - #[error("Error NoRequest: A request is required to use this feature.")] - NoRequest, -} - -#[cfg(feature = "latex")] -pub trait Latex { - /// Returns a Result containing a string of LaTeX or a LatexGenerationError. - /// - /// # Arguments - /// * `option` - An Option containing a LaTeX Request. - /// * `Option::Some` - A valid Request that can be parsed. - /// * `Option::None` - An invalid Request that throws an error. - fn to_latex(self, request: Request) -> Result; -} - -impl Latex for Program { - fn to_latex(self, request: Request) -> Result { - match request.document { - Document::Diagram => { - let diagram_factory = Box::new(DiagramFactory {}); - let tikz_diagram_factory = diagram_factory.create_tikz_diagram(); - - tikz_diagram_factory.generate_default_quantikz_diagram(); - }, - Document::None => { - panic!("{}", LatexGenerationError::NoRequest); - }, - } - - // // TODO: Generate the Program LaTeX. - // let latex = ""; - - Ok("".to_string()) - } -} - -#[cfg(test)] -mod tests { - use super::{Program, Latex, Request, Document}; - use std::str::FromStr; - - /// Take an instruction and return the LaTeX using the to_latex method. - pub fn get_quantikz_diagram(instructions: &str) -> String { - let program = Program::from_str(instructions).expect("program should be returned"); - program.to_latex(Request::default()).expect("Quantikz diagram should generate without error") - } - - #[test] - #[should_panic(expected = "")] - /// Test functionality of to_latex using a request with a None document. - fn test_to_latex_should_panic_with_document_none() { - // Create an empty program as a string. - let program = Program::from_str("").expect("empty program should be created"); - program.to_latex(Request {document: Document::None, settings: None}).expect("function should panic with None document"); - } - - #[test] - /// Test functionality of to_latex using a default request of Document::Diagram and Settings::None. - fn test_to_latex_with_default_request() { - // Create an empty program as a string. - let program = Program::from_str("").expect("empty program should be created"); - program.to_latex(Request::default()).expect("function should work with default request"); - } - - mod gates { - use crate::program::latex::tests::get_quantikz_diagram; - - #[test] - fn test_quantikz_default_diagram_gate_x() { - insta::assert_snapshot!(get_quantikz_diagram("X 0")); - } - - #[test] - fn test_quantikz_default_diagram_gate_y() { - insta::assert_snapshot!(get_quantikz_diagram("Y 1")); - } - - #[test] - fn test_quantikz_default_diagram_gate_controlled() { - insta::assert_snapshot!(get_quantikz_diagram("CONTROLLED H 3 2")); - } - } -} diff --git a/src/program/latex/README.md b/src/program/latex/README.md deleted file mode 100644 index bef64412..00000000 --- a/src/program/latex/README.md +++ /dev/null @@ -1,119 +0,0 @@ -# Quil LaTeX Generation using the AbstractFactory Design Pattern - ---- -## Context - -LaTeX is more than a circuit building utility, in general, it can be thought of as a powerful document generation tool with an expansive ecosystem of libraries each with their own unique packages. The abstract factory pattern used in this design opens LaTeX to quil-rs as more than a circuit building utility. It also narrows down LaTeX to documents that are explicitly made available (product branding opportunity) and specific to quantum programs which can be easily expanded upon, and encouraged to, using this design pattern. Provided the type of document is known, this feature can generate it in LaTeX. - - ---- -## User Compatibility - -If the user knows nothing about LaTeX but knows that the program can generate circuit diagrams that they can then copy and paste into a LaTeX renderer, they only need to call to_latex() using Python bindings. The default is to produce the same results with the same simplicity as pyQuil's to_latex feature, which is a Quantikz diagram of qubits and gates displayed on a circuit. - -With some knowledge of LaTeX, the user could also generate other documents made available, which this design encourages. For instance, if a user requests a graphical representation of a quil program that is made available, e.g. the probability density, a graph ConcreteFactory can be implemented that produces a pgfplots AbstractProduct which is defined stylistically in a, e.g. "quilplots", ConcreteProduct. - - ---- -## File Structure - -program/ -|-- latex.rs -|-- latex/ - |-- generation.rs - |-- generation/ - |-- diagram.rs - |-- diagram/ - |-- tikz.rs - |-- tikz/ - |-- quantikz.rs - ---- -## Directory and File Descriptions - -latex.rs -Defines the API to use the LaTeX generation feature. - -latex/ -Module containing all LaTeX generation code. - -generation.rs -Defines a LaTeX AbstractFactory that builds document-specific ConcreteFactories. - -generation/ -Module containing all LaTeX document-building ConcreteFactories and their products. - -diagram.rs -Defines a ConcreteFactory that builds AbstractDiagrams. - -diagram/ -Module containing the LaTeX AbstractDiagram and its ConcreteDiagrams. - -tikz.rs -Defines an AbstractDiagram TikZ that is an expansive diagram building library used to build ConcreteDiagrams. - -tikz/ -Module containing all ConcreteDiagrams that can be built using the TikZ library. - -quantikz.rs -Defines a ConcreteDiagram that is a Quantikz circuit from the TikZ library. - -**snapshots/ -Directories of snapshot unit tests. - - ---- -## Keywords - -LaTeX: a document generation tool. -TikZ: a LaTeX library used to generate a variety of LaTeX diagrams. -Quantikz: a TikZ package used to generate quantum circuits. -pgfplots: a LaTeX library used to visualize scientific/technical graphs. - - - ---- -## API Design - -quil-rs to_latex feature should be as flexible as Python's optional parameters. With Python bindings this can be as simple as calling to_latex(). The method signature for the same quil-rs function is - -`to_latex(self, request: Option) -> Result`. - -A Request is a struct with two attributes: - -document: Document, // An enum variant representing the type of document being requested. -settings: Settings, // Specific settings for how the document is rendered. - -Defaults -document: "diagram" // Specifically, a Quantikz diagram same as pyQuil. -settings: None // Default settings at the product level. - -Note on Settings - -- If settings are not specified then the default settings are defined in the ConcreteFactory if they can be generalized over all diagrams, or (inclusively) in the ConcreteProduct if they cannot be generalized but can only be applied on a specific type of diagram (e.q. Quantikz). Refer to the documentation for these specifications. - -- Without specifying them, settings are inferred depending on the type of document being created. More concretely, settings for diagrams are different from settings for plots. The program determines what type of settings are being used by matching it to the requested document type. This allows the document factory to apply the settings to the document being produced. - - ---- -## Program Flow - -Example generating a default (Quantikz) LaTeX diagram - -1. The Program makes a default request using the API defined in latex.rs. - -2. The request is parsed in latex.rs which is then passed to the create_diagram method on the AbstractFactory (latex/generation.rs) to build a diagram of a Quil Program with no settings. - -3. The AbstractFactory is then defined into a ConcreteFactory that builds diagrams (latex/generation/diagram.rs) with generalized settings. - -4. The ConcreteFactory then makes a request to generate a default diagram from the TikZ AbstractProduct (latex/generation/diagram/tikz.rs). - -5. The AbstractProduct is then defined defined into a ConcreteProduct that builds Quantikz diagrams (latex/generation/diagram/tikz/quantikz.rs). - -6. The Quantikz diagram is built at the ConcreteDiagram level and is returned to the Program - - ---- -## Conclusion - -The use of the AbstractFactory pattern opens several avenues within the LaTeX ecosystem for the rendering of any kind of document that might be useful in visualizing Quil programs or even generating reports (considering the many more uses for LaTeX beyond diagram rendering). The vision is that at some point, Quil Programs may want to be documented in another form that LaTeX will likely be able to provide. In this case, the LatexFactory can be expanded to implement a new factory that can construct the desired Quil document e.g. Pgfplots, a LaTeX package used to generate scientific/technical graphs. \ No newline at end of file diff --git a/src/program/latex/generation.rs b/src/program/latex/generation.rs deleted file mode 100644 index 5bfbdfea..00000000 --- a/src/program/latex/generation.rs +++ /dev/null @@ -1,7 +0,0 @@ -use self::diagram::tikz::Tikz; - -pub mod diagram; - -pub trait LatexFactory { - fn create_tikz_diagram(&self) -> Box; -} \ No newline at end of file diff --git a/src/program/latex/generation/diagram.rs b/src/program/latex/generation/diagram.rs deleted file mode 100644 index 80f49bdd..00000000 --- a/src/program/latex/generation/diagram.rs +++ /dev/null @@ -1,51 +0,0 @@ -use self::tikz::{Tikz, quantikz::Quantikz}; -use super::LatexFactory; - -pub mod tikz; - -pub struct DiagramFactory; - -impl LatexFactory for DiagramFactory { - fn create_tikz_diagram(&self) -> Box { - Box::new(Quantikz {}) - } -} - -#[derive(Debug)] -/// Settings to control the layout and rendering of circuits. -pub struct DiagramSettings { - /// Convert numerical constants, such as pi, to LaTeX form. - texify_numerical_constants: bool, - - /// Include qubits with indices between those explicitly referenced in the Quil program. - /// For example, if true, the diagram for `CNOT 0 2` would have three qubit lines: 0, 1, 2. - impute_missing_qubits: bool, - - /// Label qubit lines. - label_qubit_lines: bool, - - /// Write controlled rotations in a compact form. - /// For example, `RX(pi)` as `X_{\\pi}`, instead of the longer `R_X(\\pi)` - abbreviate_controlled_rotations: bool, - - /// The length by which qubit lines should be extended with open wires at the right of the diagram. - /// The default of 1 is the natural choice. The main reason for including this option - /// is that it may be appropriate for this to be 0 in subdiagrams. - qubit_line_open_wire_length: u32, - - /// Align measurement operations which appear at the end of the program. - right_align_terminal_measurements: bool, -} - -impl Default for DiagramSettings { - fn default() -> Self { - Self { - texify_numerical_constants: true, - impute_missing_qubits: false, - label_qubit_lines: true, - abbreviate_controlled_rotations: false, - qubit_line_open_wire_length: 1, - right_align_terminal_measurements: true, - } - } -} \ No newline at end of file diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cnot_target.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cnot_target.snap deleted file mode 100644 index 85acfa48..00000000 --- a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cnot_target.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/generation/diagram/tikz.rs -assertion_line: 52 -expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzCnotTarget)" ---- -\targ{} diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_control.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_control.snap deleted file mode 100644 index 3cae6051..00000000 --- a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_control.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/generation/diagram/tikz.rs -assertion_line: 47 -expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzControl(2))" ---- -\ctrl{2} diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cphase_target.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cphase_target.snap deleted file mode 100644 index 91fe2967..00000000 --- a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_cphase_target.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/generation/diagram/tikz.rs -assertion_line: 57 -expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzCphaseTarget)" ---- -\control{} diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_left_ket.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_left_ket.snap deleted file mode 100644 index 350c39f3..00000000 --- a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_left_ket.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/generation/diagram/tikz.rs -assertion_line: 42 -expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzLeftKet(0))" ---- -\lstick{\ket{q_{0}}} diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_measure.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_measure.snap deleted file mode 100644 index e6714223..00000000 --- a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_measure.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/generation/diagram/tikz.rs -assertion_line: 77 -expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzMeasure)" ---- -\meter{} diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_nop.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_nop.snap deleted file mode 100644 index 48334e6a..00000000 --- a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_nop.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/generation/diagram/tikz.rs -assertion_line: 72 -expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzNop)" ---- -\qw diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap.snap deleted file mode 100644 index 4bef445d..00000000 --- a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/generation/diagram/tikz.rs -assertion_line: 62 -expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzSwap(4))" ---- -\swap{4} diff --git a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap_target.snap b/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap_target.snap deleted file mode 100644 index 3acd2f2d..00000000 --- a/src/program/latex/generation/diagram/snapshots/quil_rs__program__latex__generation__diagram__tikz__tests__tikz_operators__tikz_swap_target.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/generation/diagram/tikz.rs -assertion_line: 67 -expression: "TikzOperator::get_tikz_operator(TikzOperator::TikzSwapTarget)" ---- -\targX{} diff --git a/src/program/latex/generation/diagram/tikz.rs b/src/program/latex/generation/diagram/tikz.rs deleted file mode 100644 index 43aaa9e5..00000000 --- a/src/program/latex/generation/diagram/tikz.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::fmt::format; - -pub mod quantikz; - -pub trait Tikz { - fn generate_default_quantikz_diagram(&self); -} - -pub enum TikzOperator { - TikzLeftKet(u32), - TikzControl(i32), - TikzCnotTarget, - TikzCphaseTarget, - TikzSwap(i32), - TikzSwapTarget, - TikzNop, - TikzMeasure, -} - -impl TikzOperator { - fn get_tikz_operator(tikz_operator: Self) -> String { - match tikz_operator { - Self::TikzLeftKet(qubit) => format(format_args!(r#"\lstick{{\ket{{q_{{{qubit}}}}}}}"#)), // \lstick{\ket{q_{qubit}}} - Self::TikzControl(offset) => format(format_args!(r#"\ctrl{{{offset}}}"#)), // \ctrl{offset} - Self::TikzCnotTarget => r"\targ{}".to_string(), // \targ{} - Self::TikzCphaseTarget => r"\control{}".to_string(), // \control{} - Self::TikzSwap(offset) => format(format_args!(r"\swap{{{offset}}}")), // \swap{offset} - Self::TikzSwapTarget => r"\targX{}".to_string(), // \targX{} - Self::TikzNop => r"\qw".to_string(), // \qw - Self::TikzMeasure => r"\meter{}".to_string(), // \meter{} - } - } -} - -#[cfg(test)] -mod tests { - mod tikz_operators { - use super::super::TikzOperator; - - #[test] - fn test_tikz_left_ket() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzLeftKet(0))); - } - - #[test] - fn test_tikz_control() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzControl(2))); - } - - #[test] - fn test_tikz_cnot_target() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzCnotTarget)); - } - - #[test] - fn test_tikz_cphase_target() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzCphaseTarget)); - } - - #[test] - fn test_tikz_swap() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzSwap(4))); - } - - #[test] - fn test_tikz_swap_target() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzSwapTarget)); - } - - #[test] - fn test_tikz_nop() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzNop)); - } - - #[test] - fn test_tikz_measure() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzMeasure)); - } - } -} \ No newline at end of file diff --git a/src/program/latex/generation/diagram/tikz/quantikz.rs b/src/program/latex/generation/diagram/tikz/quantikz.rs deleted file mode 100644 index 1665995f..00000000 --- a/src/program/latex/generation/diagram/tikz/quantikz.rs +++ /dev/null @@ -1,10 +0,0 @@ -use super::Tikz; - -pub struct Quantikz; - -impl Tikz for Quantikz { - fn generate_default_quantikz_diagram(&self) { - // TODO: Generate a Quantikz diagram of the program with default settings. - println!("Generating default Quantikz circuit.") - } -} \ No newline at end of file diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs new file mode 100644 index 00000000..49cf0ee9 --- /dev/null +++ b/src/program/latex/mod.rs @@ -0,0 +1,173 @@ +use std::fmt::format; + +use crate::Program; + +#[derive(Debug)] +/// Settings to control the layout and rendering of circuits. +pub struct DiagramSettings { + /// Convert numerical constants, such as pi, to LaTeX form. + texify_numerical_constants: bool, + + /// Include qubits with indices between those explicitly referenced in the Quil program. + /// For example, if true, the diagram for `CNOT 0 2` would have three qubit lines: 0, 1, 2. + impute_missing_qubits: bool, + + /// Label qubit lines. + label_qubit_lines: bool, + + /// Write controlled rotations in a compact form. + /// For example, `RX(pi)` as `X_{\\pi}`, instead of the longer `R_X(\\pi)` + abbreviate_controlled_rotations: bool, + + /// The length by which qubit lines should be extended with open wires at the right of the diagram. + /// The default of 1 is the natural choice. The main reason for including this option + /// is that it may be appropriate for this to be 0 in subdiagrams. + qubit_line_open_wire_length: u32, + + /// Align measurement operations which appear at the end of the program. + right_align_terminal_measurements: bool, +} + +impl Default for DiagramSettings { + fn default() -> Self { + Self { + texify_numerical_constants: true, + impute_missing_qubits: false, + label_qubit_lines: true, + abbreviate_controlled_rotations: false, + qubit_line_open_wire_length: 1, + right_align_terminal_measurements: true, + } + } +} + +pub enum TikzOperator { + TikzLeftKet(u32), + TikzControl(i32), + TikzCnotTarget, + TikzCphaseTarget, + TikzSwap(i32), + TikzSwapTarget, + TikzNop, + TikzMeasure, +} + +impl TikzOperator { + fn get_tikz_operator(tikz_operator: Self) -> String { + match tikz_operator { + Self::TikzLeftKet(qubit) => format(format_args!(r#"\lstick{{\ket{{q_{{{qubit}}}}}}}"#)), // \lstick{\ket{q_{qubit}}} + Self::TikzControl(offset) => format(format_args!(r#"\ctrl{{{offset}}}"#)), // \ctrl{offset} + Self::TikzCnotTarget => r"\targ{}".to_string(), // \targ{} + Self::TikzCphaseTarget => r"\control{}".to_string(), // \control{} + Self::TikzSwap(offset) => format(format_args!(r"\swap{{{offset}}}")), // \swap{offset} + Self::TikzSwapTarget => r"\targX{}".to_string(), // \targX{} + Self::TikzNop => r"\qw".to_string(), // \qw + Self::TikzMeasure => r"\meter{}".to_string(), // \meter{} + } + } +} + +#[derive(thiserror::Error, Debug)] +pub enum LatexGenError { + // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. + #[error("This is an error on {qubit_index}.")] + SomeError{qubit_index: u32}, +} + +pub trait ToLatex { + fn to_latex(self, diagram_settings: DiagramSettings) -> Result; +} + +impl ToLatex for Program { + fn to_latex(self, diagram_settings: DiagramSettings) -> Result { + // TODO: Generate the Program LaTeX. + let latex = ""; + + Ok(latex.to_string()) + } +} + +#[cfg(test)] +mod tests { + use super::{DiagramSettings, ToLatex}; + use crate::Program; + use std::str::FromStr; + + /// Take an instruction and return the LaTeX using the to_latex method. + pub fn get_latex(instructions: &str) -> String { + let program = Program::from_str(instructions).expect("Program should be returned."); + program + .to_latex(DiagramSettings::default()) + .expect("LaTeX should generate without error.") + } + + #[test] + /// Test functionality of to_latex using default settings. + fn test_to_latex() { + let program = Program::from_str("").expect(""); + program.to_latex(DiagramSettings::default()).expect(""); + } + + mod gates { + use crate::program::latex::tests::get_latex; + + #[test] + fn test_gate_x() { + insta::assert_snapshot!(get_latex("X 0")); + } + + #[test] + fn test_gate_y() { + insta::assert_snapshot!(get_latex("Y 1")); + } + + #[test] + fn test_gate_controlled() { + insta::assert_snapshot!(get_latex("CONTROLLED H 3 2")); + } + } + + mod tikz_operators { + use crate::program::latex::TikzOperator; + + #[test] + fn test_tikz_left_ket() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzLeftKet(0))); + } + + #[test] + fn test_tikz_control() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzControl(2))); + } + + #[test] + fn test_tikz_cnot_target() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzCnotTarget)); + } + + #[test] + fn test_tikz_cphase_target() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzCphaseTarget)); + } + + #[test] + fn test_tikz_swap() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzSwap(4))); + } + + #[test] + fn test_tikz_swap_target() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzSwapTarget)); + } + + #[test] + fn test_tikz_nop() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzNop)); + } + + #[test] + fn test_tikz_measure() { + insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzMeasure)); + } + } +} diff --git a/src/program/snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_controlled.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_controlled.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap diff --git a/src/program/snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_x.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_x.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new new file mode 100644 index 00000000..37015ec6 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 116 +expression: "get_latex(\"X 0\")" +--- + diff --git a/src/program/snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_y.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap similarity index 100% rename from src/program/snapshots/quil_rs__program__latex__tests__gates__quantikz_default_diagram_gate_y.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap new file mode 100644 index 00000000..899f6ee6 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 157 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_cnot_target)" +--- +\targ{} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap new file mode 100644 index 00000000..e5f43424 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 150 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_control(2))" +--- +\ctrl{2} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap new file mode 100644 index 00000000..07bd40b2 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 164 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_cphase_target)" +--- +\control{} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap new file mode 100644 index 00000000..f1cce67e --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 143 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_left_ket(0))" +--- +\lstick{\ket{q_{0}}} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap new file mode 100644 index 00000000..8daec78b --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 190 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_measure)" +--- +\meter{} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap new file mode 100644 index 00000000..ebc5c10c --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 185 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_nop)" +--- +\qw diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap new file mode 100644 index 00000000..06a9f7fe --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 171 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_swap(4))" +--- +\swap{4} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap new file mode 100644 index 00000000..faf86045 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex.rs +assertion_line: 178 +expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_swap_target)" +--- +\targX{} From 3f217f4d0a63db26641d0688271d1473a34765c8 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 17 Feb 2023 13:40:16 -0800 Subject: [PATCH 010/118] Remove new snapshot of failed X gate unit test. --- .../quil_rs__program__latex__tests__gates__gate_x.snap.new | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new deleted file mode 100644 index 37015ec6..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/mod.rs -assertion_line: 116 -expression: "get_latex(\"X 0\")" ---- - From ed1f8a056abf8622438006a7d13573a602252904 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 17 Feb 2023 13:44:41 -0800 Subject: [PATCH 011/118] Remove latex dependency and rename feature to-latex to latex. --- Cargo.lock | 120 +---------------------------------------------------- Cargo.toml | 3 +- 2 files changed, 3 insertions(+), 120 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 93947a46..90d94d8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "anes" version = "0.1.6" @@ -40,21 +25,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "backtrace" -version = "0.3.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "bit-set" version = "0.5.3" @@ -100,12 +70,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - [[package]] name = "cfg-if" version = "1.0.0" @@ -270,28 +234,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" -[[package]] -name = "failure" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" -dependencies = [ - "backtrace", - "failure_derive", -] - -[[package]] -name = "failure_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" -dependencies = [ - "proc-macro2 1.0.47", - "quote 1.0.21", - "syn 1.0.103", - "synstructure", -] - [[package]] name = "fastrand" version = "1.8.0" @@ -419,12 +361,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "gimli" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec" - [[package]] name = "half" version = "1.8.2" @@ -508,15 +444,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "latex" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41cf333d23534df4dbd7513d5cbf52513b6b4741b9e642efa3e835c984ad241" -dependencies = [ - "failure", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -638,15 +565,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] - [[package]] name = "nom" version = "7.1.1" @@ -696,15 +614,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.30.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.15.0" @@ -785,7 +694,7 @@ version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" dependencies = [ - "unicode-xid 0.1.0", + "unicode-xid", ] [[package]] @@ -848,7 +757,6 @@ dependencies = [ "dot-writer", "indexmap", "insta", - "latex", "lexical", "nom", "nom_locate", @@ -1001,12 +909,6 @@ dependencies = [ "syn 1.0.103", ] -[[package]] -name = "rustc-demangle" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" - [[package]] name = "rustc_version" version = "0.4.0" @@ -1143,7 +1045,7 @@ checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", - "unicode-xid 0.1.0", + "unicode-xid", ] [[package]] @@ -1157,18 +1059,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2 1.0.47", - "quote 1.0.21", - "syn 1.0.103", - "unicode-xid 0.2.4", -] - [[package]] name = "tempfile" version = "3.3.0" @@ -1241,12 +1131,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index b96981a4..16a6c6cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ categories = ["parser-implementations", "science", "compilers", "emulators"] [dependencies] dot-writer = { version = "0.1.2", optional = true } -latex = { version = "0.3.1", optional = true } indexmap = "1.6.1" lexical = "6.1.1" nom = "7.1.1" @@ -30,7 +29,7 @@ rstest = "0.15.0" [features] graphviz-dot = ["dot-writer"] -to-latex = ["latex"] +latex = [] [[bench]] name = "parser" From e00633e97842723c62884c5d7504c885ec67c1e0 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 21 Feb 2023 17:20:01 -0800 Subject: [PATCH 012/118] Add comments and update names wrt Quantikz docs. --- src/program/latex/mod.rs | 194 ++++++++++++++++++++++++++------------- 1 file changed, 128 insertions(+), 66 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 49cf0ee9..b4d4efe7 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -1,85 +1,141 @@ +//! LaTeX diagram generation for quil programs. +//! +//! Provides a feature to generate diagrams using the LaTeX subpackage TikZ/ +//! Quantikz for a given quil Program. +//! +//! - Usage: `Program.to_latex(settings: Settings);` +//! +//! - Description: +//! [`Quantikz`] is a subpackage in the TikZ package used to generate qubit +//! circuits. A qubit is represented as a wire separated into multiple columns. +//! Each column contains a symbol of an operation on the qubit. Multiple qubits +//! can be stacked into rows with interactions between any number of them drawn +//! as a connecting bar to each involved qubit wire. Commands are used to +//! control what is rendered on a circuit, e.g. names of qubits, identifying +//! control/target qubits, gates, etc. View [`Quantikz`] for the documentation +//! on its usage and full set of commands. +//! +//! This module should be viewed as a self contained partial implementation of +//! [`Quantikz`] with all available commands listed as variants in a Command +//! enum. This feature provides the user variability in how they wish to render +//! their Program circuits with metadata contained in a Settings struct. +//! +//! [`Quantikz`]: https://arxiv.org/pdf/1809.03842.pdf + use std::fmt::format; use crate::Program; +/// Available commands used for building circuits with the same names taken +/// from the Quantikz documentation for easy reference. LaTeX string denoted +/// inside `backticks`. +/// Single wire commands: lstick, rstick, qw, meter +/// Multi-wire commands: ctrl, targ, control, (swap, targx) +pub enum Command { + /// `\lstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the left. + Lstick(u32), + /// `\rstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the right. + Rstick(u32), + /// `\qw`: Connect the current cell to the previous cell i.e. "do nothing". + Qw, + /// `\meter{wire}`: Measure a qubit. + Meter(u32), + /// `\ctrl{wire}`: Make a control qubit--different from Control. + Ctrl(u32), + /// `\targ{}`: Make a controlled-not gate. + Targ, + /// `\control{}`: Make a controlled-phase gate--different from Ctrl. + Control, + /// `\swap{wire}`: Make a swap gate--used with TargX. + Swap(u32), + /// `\targX{}`: Make a qubit the target for a swap--used with Swap. + TargX, +} + +impl Command { + /// Returns the LaTeX String for a given Command variant. + /// + /// # Arguments + /// `command` - A Command variant. + /// + /// # Examples + /// ``` + /// use quil_rs::program::latex::Command; + /// let lstick_ket_0 = Command::get_command(Command::Lstick(0)); + /// ``` + pub fn get_command(command: Self) -> String { + match command { + Self::Lstick(wire) => + format(format_args!(r#"\lstick{{\ket{{q_{{{wire}}}}}}}"#)), + Self::Rstick(wire) => + format(format_args!(r#"\rstick{{\ket{{q_{{{wire}}}}}}}"#)), + Self::Qw => r"\qw".to_string(), + Self::Meter(wire) => format(format_args!(r"\meter{{{wire}}}")), + Self::Ctrl(wire) => format(format_args!(r#"\ctrl{{{wire}}}"#)), + Self::Targ => r"\targ{}".to_string(), + Self::Control => r"\control{}".to_string(), + Self::Swap(wire) => format(format_args!(r"\swap{{{wire}}}")), + Self::TargX => r"\targX{}".to_string(), + } + } +} + +/// Settings contains the metadata that allows the user to customize how the +/// circuit is rendered or use the default implementation. #[derive(Debug)] -/// Settings to control the layout and rendering of circuits. -pub struct DiagramSettings { - /// Convert numerical constants, such as pi, to LaTeX form. +pub struct Settings { + /// Convert numerical constants, e.g. pi, to LaTeX form. texify_numerical_constants: bool, - - /// Include qubits with indices between those explicitly referenced in the Quil program. - /// For example, if true, the diagram for `CNOT 0 2` would have three qubit lines: 0, 1, 2. + /// Include all qubits implicitly referenced in the Quil program. impute_missing_qubits: bool, - /// Label qubit lines. label_qubit_lines: bool, - - /// Write controlled rotations in a compact form. - /// For example, `RX(pi)` as `X_{\\pi}`, instead of the longer `R_X(\\pi)` + /// Write controlled rotations in compact form. abbreviate_controlled_rotations: bool, - - /// The length by which qubit lines should be extended with open wires at the right of the diagram. - /// The default of 1 is the natural choice. The main reason for including this option - /// is that it may be appropriate for this to be 0 in subdiagrams. + /// Extend the length of open wires at the right of the diagram. qubit_line_open_wire_length: u32, - - /// Align measurement operations which appear at the end of the program. + /// Align measurement operations to appear at the end of the diagram. right_align_terminal_measurements: bool, } -impl Default for DiagramSettings { +impl Default for Settings { + /// Returns the default Settings. fn default() -> Self { Self { + /// false: π is pi. texify_numerical_constants: true, + /// true: `CNOT 0 2` would have three qubit lines: 0, 1, 2. impute_missing_qubits: false, + /// false: remove Lstick/Rstick from latex. label_qubit_lines: true, + /// true: `RX(pi)` displayed as `X_{\\pi}` instead of `R_X(\\pi)`. abbreviate_controlled_rotations: false, + /// 0: condenses the size of subdiagrams. qubit_line_open_wire_length: 1, + /// false: include Meter in the current column. right_align_terminal_measurements: true, } } } -pub enum TikzOperator { - TikzLeftKet(u32), - TikzControl(i32), - TikzCnotTarget, - TikzCphaseTarget, - TikzSwap(i32), - TikzSwapTarget, - TikzNop, - TikzMeasure, -} +// TODO: Implement functions to update the settings that allows the user customzie the rendering of the circuit. +impl Settings { -impl TikzOperator { - fn get_tikz_operator(tikz_operator: Self) -> String { - match tikz_operator { - Self::TikzLeftKet(qubit) => format(format_args!(r#"\lstick{{\ket{{q_{{{qubit}}}}}}}"#)), // \lstick{\ket{q_{qubit}}} - Self::TikzControl(offset) => format(format_args!(r#"\ctrl{{{offset}}}"#)), // \ctrl{offset} - Self::TikzCnotTarget => r"\targ{}".to_string(), // \targ{} - Self::TikzCphaseTarget => r"\control{}".to_string(), // \control{} - Self::TikzSwap(offset) => format(format_args!(r"\swap{{{offset}}}")), // \swap{offset} - Self::TikzSwapTarget => r"\targX{}".to_string(), // \targX{} - Self::TikzNop => r"\qw".to_string(), // \qw - Self::TikzMeasure => r"\meter{}".to_string(), // \meter{} - } - } } #[derive(thiserror::Error, Debug)] pub enum LatexGenError { - // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. + // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. Example error below. #[error("This is an error on {qubit_index}.")] SomeError{qubit_index: u32}, } -pub trait ToLatex { - fn to_latex(self, diagram_settings: DiagramSettings) -> Result; +pub trait Latex { + fn to_latex(self, settings: Settings) -> Result; } -impl ToLatex for Program { - fn to_latex(self, diagram_settings: DiagramSettings) -> Result { +impl Latex for Program { + fn to_latex(self, settings: Settings) -> Result { // TODO: Generate the Program LaTeX. let latex = ""; @@ -89,7 +145,7 @@ impl ToLatex for Program { #[cfg(test)] mod tests { - use super::{DiagramSettings, ToLatex}; + use super::{Settings, Latex}; use crate::Program; use std::str::FromStr; @@ -97,7 +153,7 @@ mod tests { pub fn get_latex(instructions: &str) -> String { let program = Program::from_str(instructions).expect("Program should be returned."); program - .to_latex(DiagramSettings::default()) + .to_latex(Settings::default()) .expect("LaTeX should generate without error.") } @@ -105,7 +161,7 @@ mod tests { /// Test functionality of to_latex using default settings. fn test_to_latex() { let program = Program::from_str("").expect(""); - program.to_latex(DiagramSettings::default()).expect(""); + program.to_latex(Settings::default()).expect(""); } mod gates { @@ -127,47 +183,53 @@ mod tests { } } - mod tikz_operators { - use crate::program::latex::TikzOperator; + /// Test module for command Operators + mod commands { + use crate::program::latex::Command; + + #[test] + fn test_command_left_ket() { + insta::assert_snapshot!(Command::get_command(Command::Lstick(0))); + } #[test] - fn test_tikz_left_ket() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzLeftKet(0))); + fn test_command_right_ket() { + insta::assert_snapshot!(Command::get_command(Command::Rstick(0))); } #[test] - fn test_tikz_control() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzControl(2))); + fn test_command_qw() { + insta::assert_snapshot!(Command::get_command(Command::Qw)); } #[test] - fn test_tikz_cnot_target() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzCnotTarget)); + fn test_command_measure() { + insta::assert_snapshot!(Command::get_command(Command::Meter(0))); } #[test] - fn test_tikz_cphase_target() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzCphaseTarget)); + fn test_command_control() { + insta::assert_snapshot!(Command::get_command(Command::Ctrl(0))); } #[test] - fn test_tikz_swap() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzSwap(4))); + fn test_command_cnot_target() { + insta::assert_snapshot!(Command::get_command(Command::Targ)); } #[test] - fn test_tikz_swap_target() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzSwapTarget)); + fn test_command_cphase_target() { + insta::assert_snapshot!(Command::get_command(Command::Control)); } #[test] - fn test_tikz_nop() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzNop)); + fn test_command_swap() { + insta::assert_snapshot!(Command::get_command(Command::Swap(0))); } #[test] - fn test_tikz_measure() { - insta::assert_snapshot!(TikzOperator::get_tikz_operator(TikzOperator::TikzMeasure)); + fn test_command_swap_target() { + insta::assert_snapshot!(Command::get_command(Command::TargX)); } } } From b7642bcbd9bf903f04533e022b709eec4f2a0dc7 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 21 Feb 2023 17:21:21 -0800 Subject: [PATCH 013/118] Update snapshot test names. --- ...rogram__latex__tests__commands__command_cnot_target.snap | 6 ++++++ ...s__program__latex__tests__commands__command_control.snap | 6 ++++++ ...gram__latex__tests__commands__command_cphase_target.snap | 6 ++++++ ...__program__latex__tests__commands__command_left_ket.snap | 6 ++++++ ...s__program__latex__tests__commands__command_measure.snap | 6 ++++++ ...uil_rs__program__latex__tests__commands__command_qw.snap | 6 ++++++ ..._program__latex__tests__commands__command_right_ket.snap | 6 ++++++ ...l_rs__program__latex__tests__commands__command_swap.snap | 6 ++++++ ...rogram__latex__tests__commands__command_swap_target.snap | 6 ++++++ ...__program__latex__tests__gates__gate_controlled.snap.new | 6 ++++++ .../quil_rs__program__latex__tests__gates__gate_x.snap.new | 6 ++++++ .../quil_rs__program__latex__tests__gates__gate_y.snap.new | 6 ++++++ ...ram__latex__tests__tikz_operators__tikz_cnot_target.snap | 6 ------ ...program__latex__tests__tikz_operators__tikz_control.snap | 6 ------ ...m__latex__tests__tikz_operators__tikz_cphase_target.snap | 6 ------ ...rogram__latex__tests__tikz_operators__tikz_left_ket.snap | 6 ------ ...program__latex__tests__tikz_operators__tikz_measure.snap | 6 ------ ...rs__program__latex__tests__tikz_operators__tikz_nop.snap | 6 ------ ...s__program__latex__tests__tikz_operators__tikz_swap.snap | 6 ------ ...ram__latex__tests__tikz_operators__tikz_swap_target.snap | 6 ------ 20 files changed, 72 insertions(+), 48 deletions(-) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_cnot_target.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_control.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_cphase_target.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_left_ket.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_measure.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_qw.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_right_ket.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap_target.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap.new create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap.new delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_cnot_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_cnot_target.snap new file mode 100644 index 00000000..b7baa2ee --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_cnot_target.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 213 +expression: "Command::get_command(Command::Targ)" +--- +\targ{} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_control.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_control.snap new file mode 100644 index 00000000..2a762b12 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_control.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 208 +expression: "Command::get_command(Command::Ctrl(0))" +--- +\ctrl{0} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_cphase_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_cphase_target.snap new file mode 100644 index 00000000..bad633b8 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_cphase_target.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 218 +expression: "Command::get_command(Command::Control)" +--- +\control{} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_left_ket.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_left_ket.snap new file mode 100644 index 00000000..1b0e0582 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_left_ket.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 188 +expression: "Command::get_command(Command::Lstick(0))" +--- +\lstick{\ket{q_{0}}} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_measure.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_measure.snap new file mode 100644 index 00000000..de386033 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_measure.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 203 +expression: "Command::get_command(Command::Meter(0))" +--- +\meter{0} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_qw.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_qw.snap new file mode 100644 index 00000000..4c5f0b43 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_qw.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 198 +expression: "Command::get_command(Command::Qw)" +--- +\qw diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_right_ket.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_right_ket.snap new file mode 100644 index 00000000..a7d2e849 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_right_ket.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 193 +expression: "Command::get_command(Command::Rstick(0))" +--- +\rstick{\ket{q_{0}}} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap.snap new file mode 100644 index 00000000..35ba9abb --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 223 +expression: "Command::get_command(Command::Swap(0))" +--- +\swap{0} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap_target.snap new file mode 100644 index 00000000..a45cb22a --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap_target.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 228 +expression: "Command::get_command(Command::TargX)" +--- +\targX{} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap.new b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap.new new file mode 100644 index 00000000..cd4d21de --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap.new @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 183 +expression: "get_latex(\"CONTROLLED H 3 2\")" +--- + diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new new file mode 100644 index 00000000..8fa1ef60 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 173 +expression: "get_latex(\"X 0\")" +--- + diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap.new b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap.new new file mode 100644 index 00000000..283ffa16 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap.new @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 178 +expression: "get_latex(\"Y 1\")" +--- + diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap deleted file mode 100644 index 899f6ee6..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cnot_target.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 157 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_cnot_target)" ---- -\targ{} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap deleted file mode 100644 index e5f43424..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_control.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 150 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_control(2))" ---- -\ctrl{2} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap deleted file mode 100644 index 07bd40b2..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_cphase_target.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 164 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_cphase_target)" ---- -\control{} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap deleted file mode 100644 index f1cce67e..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_left_ket.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 143 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_left_ket(0))" ---- -\lstick{\ket{q_{0}}} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap deleted file mode 100644 index 8daec78b..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_measure.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 190 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_measure)" ---- -\meter{} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap deleted file mode 100644 index ebc5c10c..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_nop.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 185 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_nop)" ---- -\qw diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap deleted file mode 100644 index 06a9f7fe..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 171 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_swap(4))" ---- -\swap{4} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap deleted file mode 100644 index faf86045..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__tikz_operators__tikz_swap_target.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex.rs -assertion_line: 178 -expression: "TikzOperators::get_tikz_operator(TikzOperators::tikz_swap_target)" ---- -\targX{} From 865f495a05854ba5be4add64bf7c414f3875c2b9 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 22 Feb 2023 16:50:48 -0800 Subject: [PATCH 014/118] Add document template and unit tests. --- src/program/latex/mod.rs | 82 +++++++++++++++++-- ..._latex__tests__document__body_default.snap | 6 ++ ...ogram__latex__tests__document__footer.snap | 7 ++ ...ogram__latex__tests__document__header.snap | 11 +++ ...ram__latex__tests__document__template.snap | 14 ++++ ...ex__tests__gates__gate_controlled.snap.new | 6 -- ...gram__latex__tests__gates__gate_x.snap.new | 6 -- ...gram__latex__tests__gates__gate_y.snap.new | 6 -- 8 files changed, 113 insertions(+), 25 deletions(-) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__document__body_default.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__document__footer.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__document__header.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap.new delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap.new diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index b4d4efe7..c7afe97e 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -22,7 +22,7 @@ //! //! [`Quantikz`]: https://arxiv.org/pdf/1809.03842.pdf -use std::fmt::format; +use std::fmt::{format, Display}; use crate::Program; @@ -123,6 +123,39 @@ impl Settings { } +/// The structure of a LaTeX document. Typically a LaTeX document contains +/// metadata defining the setup and packages used in a document within a header +/// and footer while the body contains content and controls its presentation. +struct Document { + header: String, + body: String, + footer: String, +} + +impl Default for Document { + fn default() -> Self { + Self { + header: +r"\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd}".to_string(), + body: "".to_string(), + footer: +r"\end{tikzcd} +\end{document}".to_string(), + } + } +} + +impl Display for Document { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}\n{}\n{}", self.header, self.body, self.footer) + } +} + #[derive(thiserror::Error, Debug)] pub enum LatexGenError { // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. Example error below. @@ -131,15 +164,22 @@ pub enum LatexGenError { } pub trait Latex { + /// Returns a Result containing a quil Program as a LaTeX string. + /// + /// # Arguments + /// `settings` - Customizes the rendering of a circuit. fn to_latex(self, settings: Settings) -> Result; } impl Latex for Program { fn to_latex(self, settings: Settings) -> Result { - // TODO: Generate the Program LaTeX. - let latex = ""; + let body = String::from(""); + + // TODO: Build the document body. + + let document = Document {body: body, ..Default::default()}; - Ok(latex.to_string()) + Ok(document.to_string()) } } @@ -149,12 +189,13 @@ mod tests { use crate::Program; use std::str::FromStr; - /// Take an instruction and return the LaTeX using the to_latex method. + /// Helper function takes instructions and return the LaTeX using the + /// Latex::to_latex method. pub fn get_latex(instructions: &str) -> String { - let program = Program::from_str(instructions).expect("Program should be returned."); + let program = Program::from_str(instructions).expect("program `{instructions}` should be returned"); program .to_latex(Settings::default()) - .expect("LaTeX should generate without error.") + .expect("LaTeX should generate for program `{instructions}`") } #[test] @@ -164,6 +205,33 @@ mod tests { program.to_latex(Settings::default()).expect(""); } + mod document { + use crate::program::latex::{Document, tests::get_latex}; + + #[test] + fn test_template() { + insta::assert_snapshot!(get_latex("")); + } + + #[test] + fn test_header() { + let document = Document::default(); + insta::assert_snapshot!(document.header); + } + + #[test] + fn test_body_default() { + let document = Document::default(); + insta::assert_snapshot!(document.body); + } + + #[test] + fn test_footer() { + let document = Document::default(); + insta::assert_snapshot!(document.footer); + } + } + mod gates { use crate::program::latex::tests::get_latex; diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__document__body_default.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__document__body_default.snap new file mode 100644 index 00000000..4c720ae9 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__document__body_default.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 219 +expression: "r\"\"" +--- + diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__document__footer.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__document__footer.snap new file mode 100644 index 00000000..a5a39950 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__document__footer.snap @@ -0,0 +1,7 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 224 +expression: "r\"\\end{tikzcd}\n\\end{document}\"" +--- +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__document__header.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__document__header.snap new file mode 100644 index 00000000..f5e7a774 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__document__header.snap @@ -0,0 +1,11 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 207 +expression: "r\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\"" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap new file mode 100644 index 00000000..a822a65e --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap @@ -0,0 +1,14 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 213 +expression: "get_latex(\"\")" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} + +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap.new b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap.new deleted file mode 100644 index cd4d21de..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_controlled.snap.new +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/mod.rs -assertion_line: 183 -expression: "get_latex(\"CONTROLLED H 3 2\")" ---- - diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new deleted file mode 100644 index 8fa1ef60..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_x.snap.new +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/mod.rs -assertion_line: 173 -expression: "get_latex(\"X 0\")" ---- - diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap.new b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap.new deleted file mode 100644 index 283ffa16..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_y.snap.new +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/mod.rs -assertion_line: 178 -expression: "get_latex(\"Y 1\")" ---- - From dc5b5631e353645786d759ea629b0c7b015eef7b Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 24 Feb 2023 19:52:29 -0800 Subject: [PATCH 015/118] Add gate command and update command variant data types. --- src/program/latex/mod.rs | 38 ++++++++++++------- ..._latex__tests__commands__command_gate.snap | 6 +++ 2 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_gate.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index c7afe97e..5786b871 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -33,21 +33,23 @@ use crate::Program; /// Multi-wire commands: ctrl, targ, control, (swap, targx) pub enum Command { /// `\lstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the left. - Lstick(u32), + Lstick(String), /// `\rstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the right. - Rstick(u32), + Rstick(String), + /// ` \gate{name}`: Make a gate on the wire. + Gate(String), /// `\qw`: Connect the current cell to the previous cell i.e. "do nothing". Qw, /// `\meter{wire}`: Measure a qubit. - Meter(u32), + Meter(String), /// `\ctrl{wire}`: Make a control qubit--different from Control. - Ctrl(u32), + Ctrl(String), /// `\targ{}`: Make a controlled-not gate. Targ, /// `\control{}`: Make a controlled-phase gate--different from Ctrl. Control, /// `\swap{wire}`: Make a swap gate--used with TargX. - Swap(u32), + Swap(String), /// `\targX{}`: Make a qubit the target for a swap--used with Swap. TargX, } @@ -69,12 +71,17 @@ impl Command { format(format_args!(r#"\lstick{{\ket{{q_{{{wire}}}}}}}"#)), Self::Rstick(wire) => format(format_args!(r#"\rstick{{\ket{{q_{{{wire}}}}}}}"#)), + Self::Gate(name) => + format(format_args!(r#"\gate{{{name}}}"#)), Self::Qw => r"\qw".to_string(), - Self::Meter(wire) => format(format_args!(r"\meter{{{wire}}}")), - Self::Ctrl(wire) => format(format_args!(r#"\ctrl{{{wire}}}"#)), + Self::Meter(wire) => + format(format_args!(r#"\meter{{{wire}}}"#)), + Self::Ctrl(wire) => + format(format_args!(r#"\ctrl{{{wire}}}"#)), Self::Targ => r"\targ{}".to_string(), Self::Control => r"\control{}".to_string(), - Self::Swap(wire) => format(format_args!(r"\swap{{{wire}}}")), + Self::Swap(wire) => + format(format_args!(r#"\swap{{{wire}}}"#)), Self::TargX => r"\targX{}".to_string(), } } @@ -257,12 +264,17 @@ mod tests { #[test] fn test_command_left_ket() { - insta::assert_snapshot!(Command::get_command(Command::Lstick(0))); + insta::assert_snapshot!(Command::get_command(Command::Lstick("0".to_string()))); } #[test] fn test_command_right_ket() { - insta::assert_snapshot!(Command::get_command(Command::Rstick(0))); + insta::assert_snapshot!(Command::get_command(Command::Rstick("0".to_string()))); + } + + #[test] + fn test_command_gate() { + insta::assert_snapshot!(Command::get_command(Command::Gate("X".to_string()))); } #[test] @@ -272,12 +284,12 @@ mod tests { #[test] fn test_command_measure() { - insta::assert_snapshot!(Command::get_command(Command::Meter(0))); + insta::assert_snapshot!(Command::get_command(Command::Meter("0".to_string()))); } #[test] fn test_command_control() { - insta::assert_snapshot!(Command::get_command(Command::Ctrl(0))); + insta::assert_snapshot!(Command::get_command(Command::Ctrl("0".to_string()))); } #[test] @@ -292,7 +304,7 @@ mod tests { #[test] fn test_command_swap() { - insta::assert_snapshot!(Command::get_command(Command::Swap(0))); + insta::assert_snapshot!(Command::get_command(Command::Swap("0".to_string()))); } #[test] diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_gate.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_gate.snap new file mode 100644 index 00000000..71a30ac3 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_gate.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 311 +expression: "Command::get_command(Command::Gate(\"X\".to_string()))" +--- +\gate{X} From 6486920c5952f574125c15bb61ec42d2a52a2835 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 24 Feb 2023 19:54:57 -0800 Subject: [PATCH 016/118] Add test for single qubit with two gates X and Y. --- src/program/latex/mod.rs | 5 +++++ ...ts__gates__text_gates_x_and_y_single_qubit.snap | 14 ++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__text_gates_x_and_y_single_qubit.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 5786b871..d7757f33 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -256,6 +256,11 @@ mod tests { fn test_gate_controlled() { insta::assert_snapshot!(get_latex("CONTROLLED H 3 2")); } + + #[test] + fn text_gates_x_and_y_single_qubit() { + insta::assert_snapshot!(get_latex("X 0\nY 0")); + } } /// Test module for command Operators diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__text_gates_x_and_y_single_qubit.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__text_gates_x_and_y_single_qubit.snap new file mode 100644 index 00000000..503b08d6 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__text_gates_x_and_y_single_qubit.snap @@ -0,0 +1,14 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 366 +expression: "r\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{X} & \\gate{Y} & \\gate{Z} & \\qw \\\\\n\\end{tikzcd}\n\\end{document}\".to_string()" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \gate{X} & \gate{Y} & \gate{Z} & \qw \\ +\end{tikzcd} +\end{document} From 5cbfb4d8f4884c7f04ff82eaf7cf3c06c3526f14 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 24 Feb 2023 19:58:13 -0800 Subject: [PATCH 017/118] Build a Diagram from a vector of Circuits. --- src/program/latex/mod.rs | 132 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 2 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index d7757f33..ad4a4a1d 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -22,9 +22,11 @@ //! //! [`Quantikz`]: https://arxiv.org/pdf/1809.03842.pdf +use std::collections::HashMap; use std::fmt::{format, Display}; use crate::Program; +use crate::instruction; /// Available commands used for building circuits with the same names taken /// from the Quantikz documentation for easy reference. LaTeX string denoted @@ -139,6 +141,7 @@ struct Document { footer: String, } +// TODO: Move TikZ/Quantikz into a separate struct. Keep Document abstract enough to represent any variant of LaTeX Documents. impl Default for Document { fn default() -> Self { Self { @@ -163,6 +166,77 @@ impl Display for Document { } } +/// A Diagram represents a collection of circuits. It encodes the relationships +/// between the circuits and their positions or the row that it fills. A row is +/// one of the Circuits in the HashMap. At this view over the circuits, Diagram +/// can form a relationship between circuits based on information about the +/// column and row. For example, one row, say qubit 0, at some column can hold +/// information that it is the control. If another row, say qubit 1, at this +/// same exact column says that it is the target, then it can inform qubit 0 +/// that it is controlling qubit 1. This information is then placed into the +/// circuit as the diagram forms the equivalent LaTeX form for each qubit. +struct Diagram { + /// A HashMap of circuits and the name of the wire as the key. + circuits: HashMap>, +} + +impl Diagram { + /// Takes a new or existing circuit and adds or updates it using the name + /// (String) as the key. If the wire exists, then the circuit chains onto + /// it by updating the next column using the Quantikz command associated + /// with its attributes (e.g. gate, do_nothing, etc). + /// + /// # Arguments + /// `&mut self` - exposes HashMap> + /// `circuit` - the circuit to be pushed or updated in circuits + fn push_circuit(&mut self, circuit: Circuit) { + // allocate a new circuit onto the heap then push it to the circuits vec with a + match self.circuits.get_mut(&circuit.wire) { + // update the exisiting circuit + Some(circuit) => { + // chain onto the old circuit + }, + // add a new circuit + None => { + self.circuits.insert(circuit.wire.clone(), Box::new(circuit));}, + } + } +} + +impl Display for Diagram { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "TODO: Make me LaTeX") + } +} + +/// A circuit represents a single wire. A wire chains columns together of +/// various Quantikz elements (using `&`). Encoded in each column is an index +/// which determines the placement of the element on the circuit. Each column +/// can hold only one element, therefore, each encoded index is unique between +/// all of the attributes. Using this property, a String can be generated. +#[derive(Debug)] +struct Circuit { + /// abstract attribute representing total-1 elements on the circuit + column: u32, + /// a wire, ket(qubit) placed using the Lstick or Rstick commands + wire: String, + /// gate element(s) placed at column_u32 on wire using the Gate command + gate: Vec<(u32, String)>, + /// a column_u32 that contains nothing placed using the Qw command + do_nothing: Vec, +} + +impl Default for Circuit { + fn default() -> Self { + Self { + column: 0, + wire: String::from(""), + gate: vec![], + do_nothing: vec![], + } + } +} + #[derive(thiserror::Error, Debug)] pub enum LatexGenError { // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. Example error below. @@ -180,9 +254,63 @@ pub trait Latex { impl Latex for Program { fn to_latex(self, settings: Settings) -> Result { - let body = String::from(""); + // get a reference to the current program + let instructions = Program::to_instructions(&self, false); + + // instruction + // X 0, Y 1, + + // store circuit strings + let mut diagram = Diagram { circuits: HashMap::new() }; + + for instruction in instructions { + let mut circuit = Circuit::default(); + + match instruction { + instruction::Instruction::Gate(gate) => { + // println!("{:?}", gate.name); + + for qubit in gate.qubits { + match qubit { + _ => { + circuit.wire = qubit.to_string(); + } + } + } + + circuit.gate.push((circuit.column, gate.name)); + + // println!("{:?}", circuit); + }, + _ => (), + } + + diagram.push_circuit(circuit) + } + + // Program of single qubit passing through two gates + // X 0 + // Y 0 + // Circuit is a single wire with an X gate and a Y gate + + // If using a vector and stacking the program the following would produce + // diagram => ["\lstick{\ket{0}} \gate{X} \qw", "\lstick{\ket{0}} \gate{Y} \qw"] + // + // """ + // \lstick{\ket{0}} \gate{X} \qw + // \lstick{\ket{0}} \gate{Y} \qw + // """ + // + // The program should produce: + // \lstick{\ket{0}} \gate{x} \gate{y} \qw + // + // Need to manage this situation. Best way to do this would be to manage the state of a Diagram that contains Wires. Where each Wire Contains a Circuit with entries that contain details on how to manage particular cases, + // Questions to ask + // is this qubit a control or target at this circuit entry at this point on the wire? + // TODO: Build the document body. + let body = diagram.to_string(); let document = Document {body: body, ..Default::default()}; @@ -208,7 +336,7 @@ mod tests { #[test] /// Test functionality of to_latex using default settings. fn test_to_latex() { - let program = Program::from_str("").expect(""); + let program = Program::from_str("X 0").expect(""); program.to_latex(Settings::default()).expect(""); } From f3ea53b5552a5091ff875a113a58878689f8d0e6 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 28 Feb 2023 11:17:35 -0800 Subject: [PATCH 018/118] Remove extraneous gate from snapshot test. --- ..._latex__tests__gates__text_gates_x_and_y_single_qubit.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__text_gates_x_and_y_single_qubit.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__text_gates_x_and_y_single_qubit.snap index 503b08d6..5f2c719e 100644 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__text_gates_x_and_y_single_qubit.snap +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__text_gates_x_and_y_single_qubit.snap @@ -1,7 +1,7 @@ --- source: src/program/latex/mod.rs assertion_line: 366 -expression: "r\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{X} & \\gate{Y} & \\gate{Z} & \\qw \\\\\n\\end{tikzcd}\n\\end{document}\".to_string()" +expression: "r\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{X} & \\gate{Y} & \\qw \\\\\n\\end{tikzcd}\n\\end{document}\".to_string()" --- \documentclass[convert={density=300,outext=.png}]{standalone} \usepackage[margin=1in]{geometry} @@ -9,6 +9,6 @@ expression: "r\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n \usetikzlibrary{quantikz} \begin{document} \begin{tikzcd} -\lstick{\ket{q_{0}}} & \gate{X} & \gate{Y} & \gate{Z} & \qw \\ +\lstick{\ket{q_{0}}} & \gate{X} & \gate{Y} & \qw \end{tikzcd} \end{document} From 2a974043b99461209223c2cc455c55b0e76ae465 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 28 Feb 2023 11:21:40 -0800 Subject: [PATCH 019/118] Add LaTeX gen for single qubit gate instructions. --- src/program/latex/mod.rs | 152 ++++++++++++++++++++++++--------------- 1 file changed, 94 insertions(+), 58 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index ad4a4a1d..c06c99a2 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -162,7 +162,7 @@ r"\end{tikzcd} impl Display for Document { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}\n{}\n{}", self.header, self.body, self.footer) + write!(f, "{}{}{}", self.header, self.body, self.footer) } } @@ -175,37 +175,87 @@ impl Display for Document { /// same exact column says that it is the target, then it can inform qubit 0 /// that it is controlling qubit 1. This information is then placed into the /// circuit as the diagram forms the equivalent LaTeX form for each qubit. +#[derive(Debug)] struct Diagram { - /// A HashMap of circuits and the name of the wire as the key. - circuits: HashMap>, + /// Settings + settings: Settings, + /// a HashMap of wires with the name of the wire as the key. + circuit: HashMap>, } impl Diagram { - /// Takes a new or existing circuit and adds or updates it using the name - /// (String) as the key. If the wire exists, then the circuit chains onto - /// it by updating the next column using the Quantikz command associated - /// with its attributes (e.g. gate, do_nothing, etc). + /// Takes a new or existing wire and adds or updates it using the name + /// (String) as the key. If a wire exists with the same name, then the + /// contents of the new wire are added to it by updating the next column + /// using the Quantikz command associated with its attributes (e.g. gate, + /// do_nothing, etc). /// /// # Arguments /// `&mut self` - exposes HashMap> /// `circuit` - the circuit to be pushed or updated in circuits - fn push_circuit(&mut self, circuit: Circuit) { - // allocate a new circuit onto the heap then push it to the circuits vec with a - match self.circuits.get_mut(&circuit.wire) { - // update the exisiting circuit - Some(circuit) => { - // chain onto the old circuit + fn push_wire(&mut self, wire: Wire) { + // find wire in circuit collection + match self.circuit.get_mut(&wire.name) { + // wire found, push to existing wire + Some(wire_in_circuit) => { + // indicate a new item to be added by incrementing column + wire_in_circuit.column += 1; + + // if wire contains gates to add + if !wire.gates.is_empty() { + if let Some(gate) = wire.gates.get(&0) { + // add gates to wire in circuit + wire_in_circuit.gates.insert(wire_in_circuit.column, gate.to_string()); + } + } + }, - // add a new circuit + // no wire found, insert new wire None => { - self.circuits.insert(circuit.wire.clone(), Box::new(circuit));}, + self.circuit.insert(wire.name.clone(), Box::new(wire)); + }, } + + // println!("{:?}", wire); } } impl Display for Diagram { + /// Converts the Diagram Circuit to LaTeX string. Returns a Result. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "TODO: Make me LaTeX") + // add a newline between the first line and the header + let mut body = String::from("\n"); + + for key in self.circuit.keys() { + // a single line of LaTeX representing a wire from the circuit + let mut line = String::from(""); + + // are labels on in settings? + if self.settings.label_qubit_lines { + // add label to left side of wire + line.push_str(&Command::get_command(Command::Lstick(key.to_string()))); + } + + // convert each attribute other than the default to string. + if let Some(wire) = self.circuit.get(key) { + for c in 0..=wire.column { + if let Some(gate) = wire.gates.get(&c) { + line.push_str(" & "); + line.push_str(&Command::get_command(Command::Gate(gate.to_string()))); + } + } + } + + // chain an empty column qw to the end of the line + line.push_str(" & "); + line.push_str(&Command::get_command(Command::Qw)); + + // add a newline between each new line or the footer + line.push('\n'); + body.push_str(&line); + } + + write!(f, "{}", body) } } @@ -215,23 +265,23 @@ impl Display for Diagram { /// can hold only one element, therefore, each encoded index is unique between /// all of the attributes. Using this property, a String can be generated. #[derive(Debug)] -struct Circuit { +struct Wire { /// abstract attribute representing total-1 elements on the circuit column: u32, /// a wire, ket(qubit) placed using the Lstick or Rstick commands - wire: String, + name: String, /// gate element(s) placed at column_u32 on wire using the Gate command - gate: Vec<(u32, String)>, + gates: HashMap, /// a column_u32 that contains nothing placed using the Qw command do_nothing: Vec, } -impl Default for Circuit { +impl Default for Wire { fn default() -> Self { Self { column: 0, - wire: String::from(""), - gate: vec![], + name: String::from(""), + gates: HashMap::new(), do_nothing: vec![], } } @@ -240,8 +290,8 @@ impl Default for Circuit { #[derive(thiserror::Error, Debug)] pub enum LatexGenError { // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. Example error below. - #[error("This is an error on {qubit_index}.")] - SomeError{qubit_index: u32}, + #[error("Tried to pop gate from new circuit and append to wire={wire} but found None.")] + NoGateInInst{wire: String}, } pub trait Latex { @@ -261,58 +311,44 @@ impl Latex for Program { // X 0, Y 1, // store circuit strings - let mut diagram = Diagram { circuits: HashMap::new() }; + let mut diagram = Diagram {settings, circuit: HashMap::new() }; for instruction in instructions { - let mut circuit = Circuit::default(); - match instruction { + // parse gate instructions into a new circuit instruction::Instruction::Gate(gate) => { - // println!("{:?}", gate.name); + // println!("GATE: {:?}", gate.name); + // for each qubit in a single gate instruction for qubit in gate.qubits { + // create a new wire + let mut wire = Wire::default(); + match qubit { + // for Fixed and Variable qubit variants _ => { - circuit.wire = qubit.to_string(); + // set the name of the wire to the qubit name + wire.name = qubit.to_string(); } } - } - circuit.gate.push((circuit.column, gate.name)); + // add the gate to the wire at column 0 + wire.gates.insert(wire.column, gate.name.clone()); + + // push wire to diagram circuit + diagram.push_wire(wire); - // println!("{:?}", circuit); + // println!("WIRE: {:?}", wire); + } }, + // do nothing for all other instructions _ => (), } - - diagram.push_circuit(circuit) } - // Program of single qubit passing through two gates - // X 0 - // Y 0 - // Circuit is a single wire with an X gate and a Y gate - - // If using a vector and stacking the program the following would produce - // diagram => ["\lstick{\ket{0}} \gate{X} \qw", "\lstick{\ket{0}} \gate{Y} \qw"] - // - // """ - // \lstick{\ket{0}} \gate{X} \qw - // \lstick{\ket{0}} \gate{Y} \qw - // """ - // - // The program should produce: - // \lstick{\ket{0}} \gate{x} \gate{y} \qw - // - // Need to manage this situation. Best way to do this would be to manage the state of a Diagram that contains Wires. Where each Wire Contains a Circuit with entries that contain details on how to manage particular cases, - // Questions to ask - // is this qubit a control or target at this circuit entry at this point on the wire? - - - // TODO: Build the document body. let body = diagram.to_string(); - let document = Document {body: body, ..Default::default()}; + println!("{}", document.to_string()); Ok(document.to_string()) } @@ -336,7 +372,7 @@ mod tests { #[test] /// Test functionality of to_latex using default settings. fn test_to_latex() { - let program = Program::from_str("X 0").expect(""); + let program = Program::from_str("X 0\nY 0").expect(""); program.to_latex(Settings::default()).expect(""); } From f39f1d894acc846194a0dba20a6b57186addbb8a Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 28 Feb 2023 12:56:41 -0800 Subject: [PATCH 020/118] Add multi-line n-qubit single qubit gates. --- src/program/latex/mod.rs | 36 +++++++++++++++---- ...m__latex__tests__commands__command_nr.snap | 6 ++++ ...ram__latex__tests__document__template.snap | 5 ++- ...ts__gates__gates_x_and_y_single_qubit.snap | 14 ++++++++ ...ests__gates__gates_x_and_y_two_qubits.snap | 16 +++++++++ 5 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_nr.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_x_and_y_single_qubit.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_x_and_y_two_qubits.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index c06c99a2..3cef759d 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -42,6 +42,8 @@ pub enum Command { Gate(String), /// `\qw`: Connect the current cell to the previous cell i.e. "do nothing". Qw, + /// `\\`: Start a new row + Nr, /// `\meter{wire}`: Measure a qubit. Meter(String), /// `\ctrl{wire}`: Make a control qubit--different from Control. @@ -76,6 +78,7 @@ impl Command { Self::Gate(name) => format(format_args!(r#"\gate{{{name}}}"#)), Self::Qw => r"\qw".to_string(), + Self::Nr => r"\\".to_string(), Self::Meter(wire) => format(format_args!(r#"\meter{{{wire}}}"#)), Self::Ctrl(wire) => @@ -224,9 +227,10 @@ impl Display for Diagram { /// Converts the Diagram Circuit to LaTeX string. Returns a Result. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // add a newline between the first line and the header - let mut body = String::from("\n"); + let mut body = String::from('\n'); - for key in self.circuit.keys() { + let mut i = 0; // used to omit trailing Nr + for key in self.circuit.keys() { // a single line of LaTeX representing a wire from the circuit let mut line = String::from(""); @@ -250,6 +254,14 @@ impl Display for Diagram { line.push_str(" & "); line.push_str(&Command::get_command(Command::Qw)); + // if this is the last key iteration, omit Nr from end of line + if i < self.circuit.len() - 1 { + // indicate a new row + line.push(' '); + line.push_str(&Command::get_command(Command::Nr)); + i += 1; + } + // add a newline between each new line or the footer line.push('\n'); body.push_str(&line); @@ -372,7 +384,7 @@ mod tests { #[test] /// Test functionality of to_latex using default settings. fn test_to_latex() { - let program = Program::from_str("X 0\nY 0").expect(""); + let program = Program::from_str("X 0\nY 1").expect(""); program.to_latex(Settings::default()).expect(""); } @@ -417,14 +429,19 @@ mod tests { } #[test] - fn test_gate_controlled() { - insta::assert_snapshot!(get_latex("CONTROLLED H 3 2")); + fn test_gates_x_and_y_single_qubit() { + insta::assert_snapshot!(get_latex("X 0\nY 0")); } #[test] - fn text_gates_x_and_y_single_qubit() { - insta::assert_snapshot!(get_latex("X 0\nY 0")); + fn test_gates_x_and_y_two_qubits() { + insta::assert_snapshot!(get_latex("X 0\nY 1")); } + + // #[test] + // fn test_gate_controlled() { + // insta::assert_snapshot!(get_latex("CONTROLLED H 3 2")); + // } } /// Test module for command Operators @@ -451,6 +468,11 @@ mod tests { insta::assert_snapshot!(Command::get_command(Command::Qw)); } + #[test] + fn test_command_nr() { + insta::assert_snapshot!(Command::get_command(Command::Nr)); + } + #[test] fn test_command_measure() { insta::assert_snapshot!(Command::get_command(Command::Meter("0".to_string()))); diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_nr.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_nr.snap new file mode 100644 index 00000000..85810332 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_nr.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 473 +expression: "r\"\\\\\"" +--- +\\ diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap index a822a65e..baa6556b 100644 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap @@ -1,7 +1,7 @@ --- source: src/program/latex/mod.rs -assertion_line: 213 -expression: "get_latex(\"\")" +assertion_line: 397 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\end{tikzcd}\n\\end{document}\"#" --- \documentclass[convert={density=300,outext=.png}]{standalone} \usepackage[margin=1in]{geometry} @@ -9,6 +9,5 @@ expression: "get_latex(\"\")" \usetikzlibrary{quantikz} \begin{document} \begin{tikzcd} - \end{tikzcd} \end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_x_and_y_single_qubit.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_x_and_y_single_qubit.snap new file mode 100644 index 00000000..ec23cf73 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_x_and_y_single_qubit.snap @@ -0,0 +1,14 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 434 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{X} & \\gate{Y} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \gate{X} & \gate{Y} & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_x_and_y_two_qubits.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_x_and_y_two_qubits.snap new file mode 100644 index 00000000..74cac101 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_x_and_y_two_qubits.snap @@ -0,0 +1,16 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 442 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{X} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\gate{Y} & \\qw\n\\end{tikzcd}\n\\end{document}\n\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \gate{X} & \qw \\ +\lstick{\ket{q_{1}}} & \gate{Y} & \qw +\end{tikzcd} +\end{document} + From 1a3d66cf35134a1b2db3c83f4d9b6cd3b3e90f01 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 28 Feb 2023 13:40:34 -0800 Subject: [PATCH 021/118] Update doc string example for get_command. --- src/program/latex/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 3cef759d..384703d1 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -67,7 +67,8 @@ impl Command { /// # Examples /// ``` /// use quil_rs::program::latex::Command; - /// let lstick_ket_0 = Command::get_command(Command::Lstick(0)); + /// let ket_0 = "0".to_string(); + /// let lstick_ket_0 = Command::get_command(Command::Lstick(ket_0)); /// ``` pub fn get_command(command: Self) -> String { match command { From 6a4894deab038608afdbbba8fbdea61e991b8311 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 1 Mar 2023 12:16:06 -0800 Subject: [PATCH 022/118] Preserve order of wires by indexing circuit keys in vector. --- src/program/latex/mod.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 384703d1..d9d2dc8b 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -183,7 +183,9 @@ impl Display for Document { struct Diagram { /// Settings settings: Settings, - /// a HashMap of wires with the name of the wire as the key. + /// preserves the order of wires through indexing the circuit keys + order: Vec, + /// a HashMap of wires with the name of the wire as the key circuit: HashMap>, } @@ -214,8 +216,9 @@ impl Diagram { } }, - // no wire found, insert new wire + // no wire found, preserve insertion order and insert new wire None => { + self.order.push(wire.name.clone()); self.circuit.insert(wire.name.clone(), Box::new(wire)); }, } @@ -231,7 +234,7 @@ impl Display for Diagram { let mut body = String::from('\n'); let mut i = 0; // used to omit trailing Nr - for key in self.circuit.keys() { + for key in &self.order { // a single line of LaTeX representing a wire from the circuit let mut line = String::from(""); @@ -324,7 +327,7 @@ impl Latex for Program { // X 0, Y 1, // store circuit strings - let mut diagram = Diagram {settings, circuit: HashMap::new() }; + let mut diagram = Diagram {settings, order: vec![], circuit: HashMap::new()}; for instruction in instructions { match instruction { From 04a17237ec06782d9a503ded419f403506bb3f1e Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 1 Mar 2023 15:09:48 -0800 Subject: [PATCH 023/118] Add initial CNOT LaTeX gen for variable qubits. --- src/program/latex/mod.rs | 56 ++++++++++++++----- ...ests__gates__gates_cnot_ctrl_0_targ_1.snap | 15 +++++ ...ests__gates__gates_cnot_ctrl_1_targ_0.snap | 15 +++++ 3 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_1.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_1_targ_0.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index d9d2dc8b..32687adb 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -133,7 +133,9 @@ impl Default for Settings { // TODO: Implement functions to update the settings that allows the user customzie the rendering of the circuit. impl Settings { - + fn label_qubit_lines(&self, key: String) -> String { + Command::get_command(Command::Lstick(key.to_string())) + } } /// The structure of a LaTeX document. Typically a LaTeX document contains @@ -190,6 +192,21 @@ struct Diagram { } impl Diagram { + /// Returns a string indicating whether the qubit at row x column on the + /// wire is a control or target qubit. Using order, a qubit whose index = 0 + /// is a control whereas index > 0, without modifiers, is a target. + /// + /// # Arguments + /// `&usize position` - the index of the qubit in &self.order + fn set_ctrl_targ(&self, position: &usize) -> String { + if *position == 0 { + // the target qubit lies on the next wire 1 + Command::get_command(Command::Ctrl(String::from("1"))) + } else { + Command::get_command(Command::Targ) + } + } + /// Takes a new or existing wire and adds or updates it using the name /// (String) as the key. If a wire exists with the same name, then the /// contents of the new wire are added to it by updating the next column @@ -198,7 +215,7 @@ impl Diagram { /// /// # Arguments /// `&mut self` - exposes HashMap> - /// `circuit` - the circuit to be pushed or updated in circuits + /// `wire` - the wire to be pushed or updated to in circuits fn push_wire(&mut self, wire: Wire) { // find wire in circuit collection match self.circuit.get_mut(&wire.name) { @@ -233,23 +250,30 @@ impl Display for Diagram { // add a newline between the first line and the header let mut body = String::from('\n'); - let mut i = 0; // used to omit trailing Nr - for key in &self.order { + for i in 0..self.order.len() { // a single line of LaTeX representing a wire from the circuit let mut line = String::from(""); // are labels on in settings? if self.settings.label_qubit_lines { // add label to left side of wire - line.push_str(&Command::get_command(Command::Lstick(key.to_string()))); + line.push_str(&self.settings.label_qubit_lines(self.order[i].clone())); } // convert each attribute other than the default to string. - if let Some(wire) = self.circuit.get(key) { + if let Some(wire) = self.circuit.get(&self.order[i]) { for c in 0..=wire.column { if let Some(gate) = wire.gates.get(&c) { + // println!("GATE: {gate}"); + line.push_str(" & "); + + // determine if target or control + if gate == "CNOT" { + line.push_str(&self.set_ctrl_targ(&i)); + } else { line.push_str(&Command::get_command(Command::Gate(gate.to_string()))); + } } } } @@ -263,7 +287,6 @@ impl Display for Diagram { // indicate a new row line.push(' '); line.push_str(&Command::get_command(Command::Nr)); - i += 1; } // add a newline between each new line or the footer @@ -340,13 +363,8 @@ impl Latex for Program { // create a new wire let mut wire = Wire::default(); - match qubit { - // for Fixed and Variable qubit variants - _ => { - // set the name of the wire to the qubit name - wire.name = qubit.to_string(); - } - } + // set name of wire for any qubit variant as String + wire.name = qubit.to_string(); // add the gate to the wire at column 0 wire.gates.insert(wire.column, gate.name.clone()); @@ -442,6 +460,16 @@ mod tests { insta::assert_snapshot!(get_latex("X 0\nY 1")); } + #[test] + fn test_gates_cnot_ctrl_0_targ_1() { + insta::assert_snapshot!(get_latex("CNOT 0 1")); + } + + #[test] + fn test_gates_cnot_ctrl_1_targ_0() { + insta::assert_snapshot!(get_latex("CNOT 1 0")); + } + // #[test] // fn test_gate_controlled() { // insta::assert_snapshot!(get_latex("CONTROLLED H 3 2")); diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_1.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_1.snap new file mode 100644 index 00000000..26753a91 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_1.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 449 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\ctrl{1} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\targ{} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \ctrl{1} & \qw \\ +\lstick{\ket{q_{1}}} & \targ{} & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_1_targ_0.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_1_targ_0.snap new file mode 100644 index 00000000..30945e23 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_1_targ_0.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 469 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{1}}} & \\ctrl{1} & \\qw \\\\\n\\lstick{\\ket{q_{0}}} & \\targ{} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{1}}} & \ctrl{1} & \qw \\ +\lstick{\ket{q_{0}}} & \targ{} & \qw +\end{tikzcd} +\end{document} From 568fcdfce1ce203bba8ddcb5131031c80fa498bd Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 6 Mar 2023 20:14:24 -0800 Subject: [PATCH 024/118] Add CNOT gate. --- src/program/latex/mod.rs | 214 +++++++++++++----- ...ests__gates__gates_cnot_ctrl_1_targ_0.snap | 8 +- 2 files changed, 165 insertions(+), 57 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 32687adb..619a475d 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -22,7 +22,7 @@ //! //! [`Quantikz`]: https://arxiv.org/pdf/1809.03842.pdf -use std::collections::HashMap; +use std::collections::{HashMap, BTreeMap}; use std::fmt::{format, Display}; use crate::Program; @@ -133,8 +133,8 @@ impl Default for Settings { // TODO: Implement functions to update the settings that allows the user customzie the rendering of the circuit. impl Settings { - fn label_qubit_lines(&self, key: String) -> String { - Command::get_command(Command::Lstick(key.to_string())) + fn label_qubit_lines(&self, name: u64) -> String { + Command::get_command(Command::Lstick(name.to_string())) } } @@ -181,14 +181,29 @@ impl Display for Document { /// same exact column says that it is the target, then it can inform qubit 0 /// that it is controlling qubit 1. This information is then placed into the /// circuit as the diagram forms the equivalent LaTeX form for each qubit. +/// The circuit dimension is column:`column+1`x row:`circuit.len+1` where the +/// product of these two is the total number of items on the entire circuit. #[derive(Debug)] struct Diagram { /// Settings settings: Settings, - /// preserves the order of wires through indexing the circuit keys - order: Vec, - /// a HashMap of wires with the name of the wire as the key - circuit: HashMap>, + /// total-1 elements on each wire + column: u32, + /// maintains the instruction order of qubits + order: Vec, + /// a BTreeMap of wires with the name of the wire as the key + circuit: BTreeMap>, +} + +impl Default for Diagram { + fn default() -> Self { + Self { + settings: Settings::default(), + column: 0, + order: vec![], + circuit: BTreeMap::new(), + } + } } impl Diagram { @@ -198,13 +213,74 @@ impl Diagram { /// /// # Arguments /// `&usize position` - the index of the qubit in &self.order - fn set_ctrl_targ(&self, position: &usize) -> String { - if *position == 0 { - // the target qubit lies on the next wire 1 - Command::get_command(Command::Ctrl(String::from("1"))) - } else { - Command::get_command(Command::Targ) + fn set_ctrl_targ(&mut self) { + + let mut ctrl = None; + let mut targ = None; + + // for every CNOT the first qubit is the control + for qubit in &self.order { + // get the wire from the circuit as mutible + if let Some(wire) = self.circuit.get_mut(qubit) { + + // if ctrl = Some, the remaining qubits are target qubits + if let Some(_) = ctrl { + // set wire at column as a target qubit + wire.targ.insert(self.column, true); + targ = Some(wire.name); // identify the target qubit + + // if ctrl = None, this is the first qubit which is the control + } else { + ctrl = Some(wire.name); // identify the control qubit + } + } } + + // physical vector between qubits on the diagram with a positive direction from control to target and negative, from target to control, with the magnitude being the distance between them + let vector: i64; + if let Some(ctrl) = ctrl { + if let Some(targ) = targ { + // distance between qubits is the order of the ctrl and targ qubits within the circuit + + // represent open and close parenthesis indicating a range + let mut start: i64 = -1; // first qubit in order is ctrl + let mut close: i64 = -1; // second qubit in order is targ + + // find the range between the qubits + for i in 0..self.order.len() { + + // first qubit found is the control + if self.order[i] == ctrl { + // starting index containing the control qubit + start = i as i64; + + // second qubit found is the targ + } else if self.order[i] == targ { + // closing index containing the target qubit + close = i as i64; + } + } + + // all qubits arranged on the circuit in increasing order + if targ > ctrl { + // the vector is pointing from the target to the control + vector = -1 * (start - close); + } else { + // the vector is pointing from the control to the target + vector = start - close; + } + + // error if 0 is the result + if vector == 0 { + LatexGenError::FoundCNOTWithoutCtrlOrTarg{vector}; + } + + // set wire at column as the control qubit of target qubit computed as the distance from the control qubit + self.circuit.get_mut(&ctrl).and_then(|wire| wire.ctrl.insert(self.column, vector)); + } + } + + println!("{:?}", self); } /// Takes a new or existing wire and adds or updates it using the name @@ -222,21 +298,18 @@ impl Diagram { // wire found, push to existing wire Some(wire_in_circuit) => { // indicate a new item to be added by incrementing column - wire_in_circuit.column += 1; + self.column += 1; // if wire contains gates to add - if !wire.gates.is_empty() { - if let Some(gate) = wire.gates.get(&0) { - // add gates to wire in circuit - wire_in_circuit.gates.insert(wire_in_circuit.column, gate.to_string()); - } + if let Some(gate) = wire.gates.get(&0) { + // add gates to wire in circuit + wire_in_circuit.gates.insert(self.column, gate.to_string()); } - }, // no wire found, preserve insertion order and insert new wire None => { - self.order.push(wire.name.clone()); - self.circuit.insert(wire.name.clone(), Box::new(wire)); + self.order.push(wire.name); + self.circuit.insert(wire.name, Box::new(wire)); }, } @@ -250,30 +323,40 @@ impl Display for Diagram { // add a newline between the first line and the header let mut body = String::from('\n'); - for i in 0..self.order.len() { + let mut i = 0; // used to omit trailing Nr + // write the LaTeX string for each wire in the circuit + for key in self.circuit.keys() { // a single line of LaTeX representing a wire from the circuit let mut line = String::from(""); // are labels on in settings? if self.settings.label_qubit_lines { // add label to left side of wire - line.push_str(&self.settings.label_qubit_lines(self.order[i].clone())); + line.push_str(&self.settings.label_qubit_lines(*key)); } - // convert each attribute other than the default to string. - if let Some(wire) = self.circuit.get(&self.order[i]) { - for c in 0..=wire.column { + // convert each column in the wire to string + if let Some(wire) = self.circuit.get(key) { + for c in 0..=self.column { if let Some(gate) = wire.gates.get(&c) { - // println!("GATE: {gate}"); - line.push_str(" & "); - // determine if target or control if gate == "CNOT" { - line.push_str(&self.set_ctrl_targ(&i)); + if let Some(targ) = wire.ctrl.get(&c) { + line.push_str(&Command::get_command(Command::Ctrl(targ.to_string()))); + + } else if let Some(_) = wire.targ.get(&c) { + line.push_str(&Command::get_command(Command::Targ)); + + } else { + LatexGenError::FoundCNOTWithoutCtrlOrTarg{ vector: 0}; + } } else { - line.push_str(&Command::get_command(Command::Gate(gate.to_string()))); + line.push_str(&Command::get_command(Command::Gate(gate.to_string()))); } + } else { + line.push_str(" & "); + line.push_str(&Command::get_command(Command::Qw)); } } } @@ -287,6 +370,7 @@ impl Display for Diagram { // indicate a new row line.push(' '); line.push_str(&Command::get_command(Command::Nr)); + i += 1; } // add a newline between each new line or the footer @@ -305,12 +389,14 @@ impl Display for Diagram { /// all of the attributes. Using this property, a String can be generated. #[derive(Debug)] struct Wire { - /// abstract attribute representing total-1 elements on the circuit - column: u32, /// a wire, ket(qubit) placed using the Lstick or Rstick commands - name: String, + name: u64, /// gate element(s) placed at column_u32 on wire using the Gate command gates: HashMap, + /// control at key=column with value=targ + ctrl: HashMap, + /// target at key=column + targ: HashMap, /// a column_u32 that contains nothing placed using the Qw command do_nothing: Vec, } @@ -318,9 +404,10 @@ struct Wire { impl Default for Wire { fn default() -> Self { Self { - column: 0, - name: String::from(""), + name: 0, gates: HashMap::new(), + ctrl: HashMap::new(), + targ: HashMap::new(), do_nothing: vec![], } } @@ -331,6 +418,8 @@ pub enum LatexGenError { // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. Example error below. #[error("Tried to pop gate from new circuit and append to wire={wire} but found None.")] NoGateInInst{wire: String}, + #[error("Tried to calculate distance between control and target qubits and found {vector}.")] + FoundCNOTWithoutCtrlOrTarg{vector: i64}, } pub trait Latex { @@ -346,11 +435,8 @@ impl Latex for Program { // get a reference to the current program let instructions = Program::to_instructions(&self, false); - // instruction - // X 0, Y 1, - // store circuit strings - let mut diagram = Diagram {settings, order: vec![], circuit: HashMap::new()}; + let mut diagram = Diagram {settings, ..Default::default()}; for instruction in instructions { match instruction { @@ -360,19 +446,41 @@ impl Latex for Program { // for each qubit in a single gate instruction for qubit in gate.qubits { - // create a new wire - let mut wire = Wire::default(); - // set name of wire for any qubit variant as String - wire.name = qubit.to_string(); - - // add the gate to the wire at column 0 - wire.gates.insert(wire.column, gate.name.clone()); - - // push wire to diagram circuit - diagram.push_wire(wire); + match qubit { + instruction::Qubit::Fixed(qubit) => { + + // create a new wire + let mut wire = Wire::default(); + + // set name of wire for any qubit variant as String + wire.name = qubit; + + // TODO: reduce code duplication + if let Some(_) = diagram.circuit.get(&qubit) { + // add the gate to the wire at column 0 + wire.gates.insert(0, gate.name.clone()); + } else { + if gate.name == "CNOT" { + wire.gates.insert(diagram.column, gate.name.clone()); + } else { + // add the gate to the wire at column 0 + wire.gates.insert(0, gate.name.clone()); + } + } + + // push wire to diagram circuit + diagram.push_wire(wire); + + // println!("WIRE: {:?}", wire); + }, + _ => (), + } + } - // println!("WIRE: {:?}", wire); + // set qubit relationships based on gate type + if gate.name == "CNOT" { + diagram.set_ctrl_targ(); } }, // do nothing for all other instructions @@ -406,7 +514,7 @@ mod tests { #[test] /// Test functionality of to_latex using default settings. fn test_to_latex() { - let program = Program::from_str("X 0\nY 1").expect(""); + let program = Program::from_str("H 0\nCNOT 0 1").expect(""); program.to_latex(Settings::default()).expect(""); } diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_1_targ_0.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_1_targ_0.snap index 30945e23..380d94b2 100644 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_1_targ_0.snap +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_1_targ_0.snap @@ -1,7 +1,7 @@ --- source: src/program/latex/mod.rs -assertion_line: 469 -expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{1}}} & \\ctrl{1} & \\qw \\\\\n\\lstick{\\ket{q_{0}}} & \\targ{} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +assertion_line: 532 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\targ{} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\ctrl{-1} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" --- \documentclass[convert={density=300,outext=.png}]{standalone} \usepackage[margin=1in]{geometry} @@ -9,7 +9,7 @@ expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\ \usetikzlibrary{quantikz} \begin{document} \begin{tikzcd} -\lstick{\ket{q_{1}}} & \ctrl{1} & \qw \\ -\lstick{\ket{q_{0}}} & \targ{} & \qw +\lstick{\ket{q_{0}}} & \targ{} & \qw \\ +\lstick{\ket{q_{1}}} & \ctrl{-1} & \qw \end{tikzcd} \end{document} From 4a8cf9b9e2cdeab8096d5735bff8a48addc7e5dd Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 6 Mar 2023 20:43:44 -0800 Subject: [PATCH 025/118] Add combined single and multi qubit gate tests. --- src/program/latex/mod.rs | 10 ++++++++++ ...ts__gates__gates_h_and_cnot_ctrl_0_targ_1.snap | 15 +++++++++++++++ ...ts__gates__gates_h_and_cnot_ctrl_1_targ_0.snap | 15 +++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_h_and_cnot_ctrl_0_targ_1.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_h_and_cnot_ctrl_1_targ_0.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 619a475d..37c904ae 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -578,6 +578,16 @@ mod tests { insta::assert_snapshot!(get_latex("CNOT 1 0")); } + #[test] + fn test_gates_h_and_cnot_ctrl_0_targ_1() { + insta::assert_snapshot!(get_latex("H 0\nCNOT 0 1")); + } + + #[test] + fn test_gates_h_and_cnot_ctrl_1_targ_0() { + insta::assert_snapshot!(get_latex("H 1\nCNOT 1 0")); + } + // #[test] // fn test_gate_controlled() { // insta::assert_snapshot!(get_latex("CONTROLLED H 3 2")); diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_h_and_cnot_ctrl_0_targ_1.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_h_and_cnot_ctrl_0_targ_1.snap new file mode 100644 index 00000000..0cbecae8 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_h_and_cnot_ctrl_0_targ_1.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 583 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{H} & \\ctrl{1} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\qw & \\targ{} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \gate{H} & \ctrl{1} & \qw \\ +\lstick{\ket{q_{1}}} & \qw & \targ{} & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_h_and_cnot_ctrl_1_targ_0.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_h_and_cnot_ctrl_1_targ_0.snap new file mode 100644 index 00000000..c8ff5398 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_h_and_cnot_ctrl_1_targ_0.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 588 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\qw & \\targ{} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\gate{H} & \\ctrl{-1} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \qw & \targ{} & \qw \\ +\lstick{\ket{q_{1}}} & \gate{H} & \ctrl{-1} & \qw +\end{tikzcd} +\end{document} From e615f2c4286746863525be2d826431e6b6469207 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 7 Mar 2023 10:10:55 -0800 Subject: [PATCH 026/118] Add settings test module with snapshot test. --- src/program/latex/mod.rs | 80 +++++++++++++++---- ...ngs__settings_label_qubit_lines_false.snap | 15 ++++ 2 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_label_qubit_lines_false.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 37c904ae..e27a857b 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -333,6 +333,9 @@ impl Display for Diagram { if self.settings.label_qubit_lines { // add label to left side of wire line.push_str(&self.settings.label_qubit_lines(*key)); + } else { + // add qw buffer to first column + line.push_str(&Command::get_command(Command::Qw)); } // convert each column in the wire to string @@ -504,26 +507,31 @@ mod tests { /// Helper function takes instructions and return the LaTeX using the /// Latex::to_latex method. - pub fn get_latex(instructions: &str) -> String { - let program = Program::from_str(instructions).expect("program `{instructions}` should be returned"); + pub fn get_latex(instructions: &str, settings: Settings) -> String { + let program = Program::from_str(instructions) + .expect("program `{instructions}` should be returned"); program - .to_latex(Settings::default()) + .to_latex(settings) .expect("LaTeX should generate for program `{instructions}`") } #[test] /// Test functionality of to_latex using default settings. fn test_to_latex() { - let program = Program::from_str("H 0\nCNOT 0 1").expect(""); - program.to_latex(Settings::default()).expect(""); + let program = Program::from_str("H 0\nCNOT 0 1") + .expect("Quil program should be returned"); + program + .to_latex(Settings::default()) + .expect("LaTeX should generate for Quil program"); } + /// Test module for the Document mod document { - use crate::program::latex::{Document, tests::get_latex}; + use crate::program::latex::{Document, Settings, tests::get_latex}; #[test] fn test_template() { - insta::assert_snapshot!(get_latex("")); + insta::assert_snapshot!(get_latex("", Settings::default())); } #[test] @@ -545,47 +553,68 @@ mod tests { } } + /// Test module for gates mod gates { - use crate::program::latex::tests::get_latex; + use crate::program::latex::{Settings, tests::get_latex}; #[test] fn test_gate_x() { - insta::assert_snapshot!(get_latex("X 0")); + insta::assert_snapshot!(get_latex( + "X 0", + Settings::default() + )); } #[test] fn test_gate_y() { - insta::assert_snapshot!(get_latex("Y 1")); + insta::assert_snapshot!(get_latex( + "Y 1", + Settings::default())); } #[test] fn test_gates_x_and_y_single_qubit() { - insta::assert_snapshot!(get_latex("X 0\nY 0")); + insta::assert_snapshot!(get_latex( + "X 0\nY 0", + Settings::default())); } #[test] fn test_gates_x_and_y_two_qubits() { - insta::assert_snapshot!(get_latex("X 0\nY 1")); + insta::assert_snapshot!(get_latex( + "X 0\nY 1", + Settings::default() + )); } #[test] fn test_gates_cnot_ctrl_0_targ_1() { - insta::assert_snapshot!(get_latex("CNOT 0 1")); + insta::assert_snapshot!(get_latex( + "CNOT 0 1", + Settings::default())); } #[test] fn test_gates_cnot_ctrl_1_targ_0() { - insta::assert_snapshot!(get_latex("CNOT 1 0")); + insta::assert_snapshot!(get_latex( + "CNOT 1 0", + Settings::default())); } #[test] fn test_gates_h_and_cnot_ctrl_0_targ_1() { - insta::assert_snapshot!(get_latex("H 0\nCNOT 0 1")); + insta::assert_snapshot!(get_latex( + "H 0\nCNOT 0 1", + Settings::default() + )); } #[test] fn test_gates_h_and_cnot_ctrl_1_targ_0() { - insta::assert_snapshot!(get_latex("H 1\nCNOT 1 0")); + insta::assert_snapshot!(get_latex( + "H 1\nCNOT 1 0", + Settings::default() + )); } // #[test] @@ -594,7 +623,7 @@ mod tests { // } } - /// Test module for command Operators + /// Test module for Quantikz Commands mod commands { use crate::program::latex::Command; @@ -653,4 +682,21 @@ mod tests { insta::assert_snapshot!(Command::get_command(Command::TargX)); } } + + /// Test module for Settings + mod settings { + use crate::program::latex::{Settings, tests::get_latex}; + + #[test] + fn test_settings_label_qubit_lines_false() { + let settings = Settings { + label_qubit_lines: false, + ..Default::default() + }; + insta::assert_snapshot!(get_latex( + "H 0\nCNOT 0 1", + settings + )); + } + } } diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_label_qubit_lines_false.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_label_qubit_lines_false.snap new file mode 100644 index 00000000..ac3540f6 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_label_qubit_lines_false.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 689 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\qw & \\gate{H} & \\ctrl{1} & \\qw \\\\\n\\qw & \\qw & \\targ{} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\qw & \gate{H} & \ctrl{1} & \qw \\ +\qw & \qw & \targ{} & \qw +\end{tikzcd} +\end{document} From 8494931d2c88b93b6d497ea592a2bf145fb4f7a6 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 7 Mar 2023 13:58:19 -0800 Subject: [PATCH 027/118] Add impute missing qubits setting. --- src/program/latex/mod.rs | 103 ++++++++++++++++-- ...ng_qubits_true_ctrl_greater_than_targ.snap | 17 +++ ...ssing_qubits_true_ctrl_less_than_targ.snap | 17 +++ 3 files changed, 126 insertions(+), 11 deletions(-) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_impute_missing_qubits_true_ctrl_greater_than_targ.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_impute_missing_qubits_true_ctrl_less_than_targ.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index e27a857b..c70cc95f 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -98,17 +98,17 @@ impl Command { #[derive(Debug)] pub struct Settings { /// Convert numerical constants, e.g. pi, to LaTeX form. - texify_numerical_constants: bool, + pub texify_numerical_constants: bool, /// Include all qubits implicitly referenced in the Quil program. - impute_missing_qubits: bool, + pub impute_missing_qubits: bool, /// Label qubit lines. - label_qubit_lines: bool, + pub label_qubit_lines: bool, /// Write controlled rotations in compact form. - abbreviate_controlled_rotations: bool, + pub abbreviate_controlled_rotations: bool, /// Extend the length of open wires at the right of the diagram. - qubit_line_open_wire_length: u32, + pub qubit_line_open_wire_length: u32, /// Align measurement operations to appear at the end of the diagram. - right_align_terminal_measurements: bool, + pub right_align_terminal_measurements: bool, } impl Default for Settings { @@ -133,9 +133,56 @@ impl Default for Settings { // TODO: Implement functions to update the settings that allows the user customzie the rendering of the circuit. impl Settings { - fn label_qubit_lines(&self, name: u64) -> String { + pub fn label_qubit_lines(&self, name: u64) -> String { Command::get_command(Command::Lstick(name.to_string())) } + + /// Adds missing qubits between the first qubit and last qubit in a + /// diagram's circuit. If a missing qubit is found, a new wire is created + /// and pushed to the diagram's circuit. + /// + /// # Arguments + /// `&mut Vec order` - the qubit order of the diagram + /// `&mut BTreeMap> circuit` - the circuit of the diagram + /// + /// # Examples + /// ``` + /// use quil_rs::{Program, program::latex::{Settings, Latex}}; + /// use std::str::FromStr; + /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); + /// let settings = Settings { + /// impute_missing_qubits: true, + /// ..Default::default() + /// }; + /// program.to_latex(settings).expect(""); + /// ``` + pub fn impute_missing_qubits(&self, order: &mut Vec, circuit: &mut BTreeMap>) { + + // get the first qubit in the BTreeMap + let mut first = 0; + if let Some(f) = circuit.first_key_value() { + first = *f.0 + 1; + } + + // get the last qubit in the BTreeMap + let mut last = 0; + if let Some(l) = circuit.last_key_value() { + last = *l.0 - 1; + } + + // search through the range of qubits + for qubit in first..=last { + // if the qubit is not found impute it + match circuit.get(&qubit) { + Some(_) => (), + None => { + let wire = Wire {name: qubit, ..Default::default()}; + order.push(qubit); + circuit.insert(qubit, Box::new(wire)); + }, + } + } + } } /// The structure of a LaTeX document. Typically a LaTeX document contains @@ -391,7 +438,7 @@ impl Display for Diagram { /// can hold only one element, therefore, each encoded index is unique between /// all of the attributes. Using this property, a String can be generated. #[derive(Debug)] -struct Wire { +pub struct Wire { /// a wire, ket(qubit) placed using the Lstick or Rstick commands name: u64, /// gate element(s) placed at column_u32 on wire using the Gate command @@ -474,13 +521,20 @@ impl Latex for Program { // push wire to diagram circuit diagram.push_wire(wire); - + + println!("inner qubit {qubit}"); // println!("WIRE: {:?}", wire); }, _ => (), } } + // are implicit qubits required in settings and are there at least two or more qubits in the diagram? + if diagram.settings.impute_missing_qubits && diagram.order.len() > 1 { + // add implicit qubits to circuit + diagram.settings.impute_missing_qubits(&mut diagram.order, &mut diagram.circuit); + } + // set qubit relationships based on gate type if gate.name == "CNOT" { diagram.set_ctrl_targ(); @@ -518,10 +572,13 @@ mod tests { #[test] /// Test functionality of to_latex using default settings. fn test_to_latex() { - let program = Program::from_str("H 0\nCNOT 0 1") + let program = Program::from_str("H 5\nCNOT 5 2") .expect("Quil program should be returned"); + + let settings = Settings {impute_missing_qubits: true, ..Default::default()}; + program - .to_latex(Settings::default()) + .to_latex(settings) .expect("LaTeX should generate for Quil program"); } @@ -698,5 +755,29 @@ mod tests { settings )); } + + #[test] + fn test_settings_impute_missing_qubits_true_ctrl_less_than_targ() { + let settings = Settings { + impute_missing_qubits: true, + ..Default::default() + }; + insta::assert_snapshot!(get_latex( + "H 0\nCNOT 0 3", + settings + )); + } + + #[test] + fn test_settings_impute_missing_qubits_true_ctrl_greater_than_targ() { + let settings = Settings { + impute_missing_qubits: true, + ..Default::default() + }; + insta::assert_snapshot!(get_latex( + "H 5\nCNOT 5 2", + settings + )); + } } } diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_impute_missing_qubits_true_ctrl_greater_than_targ.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_impute_missing_qubits_true_ctrl_greater_than_targ.snap new file mode 100644 index 00000000..a2725f66 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_impute_missing_qubits_true_ctrl_greater_than_targ.snap @@ -0,0 +1,17 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 777 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{2}}} & \\qw & \\targ{} & \\qw \\\\\n\\lstick{\\ket{q_{3}}} & \\qw & \\qw & \\qw \\\\\n\\lstick{\\ket{q_{4}}} & \\qw & \\qw & \\qw \\\\\n\\lstick{\\ket{q_{5}}} & \\gate{H} & \\ctrl{-3} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{2}}} & \qw & \targ{} & \qw \\ +\lstick{\ket{q_{3}}} & \qw & \qw & \qw \\ +\lstick{\ket{q_{4}}} & \qw & \qw & \qw \\ +\lstick{\ket{q_{5}}} & \gate{H} & \ctrl{-3} & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_impute_missing_qubits_true_ctrl_less_than_targ.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_impute_missing_qubits_true_ctrl_less_than_targ.snap new file mode 100644 index 00000000..e0d93ceb --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_impute_missing_qubits_true_ctrl_less_than_targ.snap @@ -0,0 +1,17 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 712 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{H} & \\ctrl{3} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\qw & \\qw & \\qw \\\\\n\\lstick{\\ket{q_{2}}} & \\qw & \\qw & \\qw \\\\\n\\lstick{\\ket{q_{3}}} & \\qw & \\targ{} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \gate{H} & \ctrl{3} & \qw \\ +\lstick{\ket{q_{1}}} & \qw & \qw & \qw \\ +\lstick{\ket{q_{2}}} & \qw & \qw & \qw \\ +\lstick{\ket{q_{3}}} & \qw & \targ{} & \qw +\end{tikzcd} +\end{document} From 2048ccdc0b562f2acbcb4f588b5fdf4a23737dd0 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 7 Mar 2023 19:05:18 -0800 Subject: [PATCH 028/118] Debug CNOT with two TDD tests of complex Programs. --- src/program/latex/mod.rs | 184 +++++++++++------- ...programs__program_h0_cnot01_x1_cnot12.snap | 16 ++ ...programs__program_h5_cnot52_y2_cnot23.snap | 16 ++ 3 files changed, 146 insertions(+), 70 deletions(-) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_h0_cnot01_x1_cnot12.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_h5_cnot52_y2_cnot23.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index c70cc95f..5e80e38f 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -254,80 +254,107 @@ impl Default for Diagram { } impl Diagram { - /// Returns a string indicating whether the qubit at row x column on the - /// wire is a control or target qubit. Using order, a qubit whose index = 0 - /// is a control whereas index > 0, without modifiers, is a target. + /// For every instruction containing control and target qubits this method + /// identifies which qubit is the target and which qubit is controlling it. + /// The logic of this function is visualized using a physical vector with + /// the tail at the control qubit and the head pointing to the target + /// qubit. The distance between the qubits represents the number of wires + /// between them therefore, the space that the vector needs to traverse. If + /// the control qubit comes before the target qubit the direction is + /// positive, otherwise, it is negative. See [`Quantikz`] documentation on + /// CNOT for some background that helps justify this approach. + /// + /// This function is expensive with a time complexity of O(n^2). This is + /// because every time a new CNOT is added, all other columns with qubits + /// containing a CNOT must be updated since another qubit in a separate + /// instruction may be inserted in between wires. This insertion increases + /// the space between wires which [`Quantikz`] uses to determine how long + /// the line should stretch between control and target qubits. Since it is + /// impossible to determine how many wires will be inserted between control + /// and target qubits for a custom body diagram, all columns should be + /// updated to ensure the connections between all control and target qubits + /// in every column is preserved. /// /// # Arguments - /// `&usize position` - the index of the qubit in &self.order + /// `&mut self` - self as mutible allowing to update the circuit qubits fn set_ctrl_targ(&mut self) { - - let mut ctrl = None; - let mut targ = None; - - // for every CNOT the first qubit is the control - for qubit in &self.order { - // get the wire from the circuit as mutible - if let Some(wire) = self.circuit.get_mut(qubit) { - - // if ctrl = Some, the remaining qubits are target qubits - if let Some(_) = ctrl { - // set wire at column as a target qubit - wire.targ.insert(self.column, true); - targ = Some(wire.name); // identify the target qubit + // ensure every column preserves the connection between ctrl and targ + for c in 0..=self.column { + + let mut ctrl = None; + let mut targ = None; + + // determine the control and target qubits in the column + for qubit in &self.order { + // get the wire from the circuit as mutible + if let Some(wire) = self.circuit.get_mut(qubit) { + + if let Some(_) = wire.gates.get(&c) { + // if ctrl is Some, the remaining qubits are target qubits + if let Some(_) = ctrl { + // set wire at column as a target qubit + wire.targ.insert(c, true); + targ = Some(wire.name); // set the target qubit - // if ctrl = None, this is the first qubit which is the control - } else { - ctrl = Some(wire.name); // identify the control qubit + // if ctrl is None, this is the first qubit which is the control + } else { + ctrl = Some(wire.name); // set the control qubit + } + } } - } - } - - // physical vector between qubits on the diagram with a positive direction from control to target and negative, from target to control, with the magnitude being the distance between them - let vector: i64; - if let Some(ctrl) = ctrl { - if let Some(targ) = targ { - // distance between qubits is the order of the ctrl and targ qubits within the circuit - - // represent open and close parenthesis indicating a range - let mut start: i64 = -1; // first qubit in order is ctrl - let mut close: i64 = -1; // second qubit in order is targ + } - // find the range between the qubits - for i in 0..self.order.len() { + // determine the physical vector where a positive vector points from control to target, negative, from target to control. The magnitude of the vector is the absolute value of the distance between them + if let Some(ctrl) = ctrl { + if let Some(targ) = targ { + // distance between qubits is the order of the ctrl and targ qubits within the circuit + + // represent inclusive [open, close] brackets of a range + let mut open = None; // opening qubit in range + let mut close = None; // closing qubit in range + + // find the range between the qubits + let mut i = 0; + for wire in &self.circuit { + // get each existing qubit in the circuit + if *wire.0 == ctrl || *wire.0 == targ { + // if the qubit is the ctrl or target + if let Some(_) = open { + close = Some(i); + break; + + // open qubit in range not found, set open qubit + } else { + open = Some(i) + } + } - // first qubit found is the control - if self.order[i] == ctrl { - // starting index containing the control qubit - start = i as i64; + i += 1; + } - // second qubit found is the targ - } else if self.order[i] == targ { - // closing index containing the target qubit - close = i as i64; + let mut vector: i64 = 0; + if let Some(open) = open { + if let Some(close) = close { + if ctrl < targ { + // a vector with a head from the ctrl to the targ + vector = 1 * (close - open); + } else { + // a vector with a head from the targ to the ctrl + vector = -1 * (close - open); + } + } } - } - // all qubits arranged on the circuit in increasing order - if targ > ctrl { - // the vector is pointing from the target to the control - vector = -1 * (start - close); - } else { - // the vector is pointing from the control to the target - vector = start - close; - } + // a qubit cannot be a ctrl and targ of itself + if vector == 0 { + LatexGenError::FoundCNOTWithoutCtrlOrTarg{vector}; + } - // error if 0 is the result - if vector == 0 { - LatexGenError::FoundCNOTWithoutCtrlOrTarg{vector}; + // set wire at column as the control qubit of target qubit computed as the distance from the control qubit + self.circuit.get_mut(&ctrl).and_then(|wire| wire.ctrl.insert(c, vector)); } - - // set wire at column as the control qubit of target qubit computed as the distance from the control qubit - self.circuit.get_mut(&ctrl).and_then(|wire| wire.ctrl.insert(self.column, vector)); } } - - println!("{:?}", self); } /// Takes a new or existing wire and adds or updates it using the name @@ -359,8 +386,6 @@ impl Diagram { self.circuit.insert(wire.name, Box::new(wire)); }, } - - // println!("{:?}", wire); } } @@ -447,8 +472,6 @@ pub struct Wire { ctrl: HashMap, /// target at key=column targ: HashMap, - /// a column_u32 that contains nothing placed using the Qw command - do_nothing: Vec, } impl Default for Wire { @@ -458,7 +481,6 @@ impl Default for Wire { gates: HashMap::new(), ctrl: HashMap::new(), targ: HashMap::new(), - do_nothing: vec![], } } } @@ -492,8 +514,6 @@ impl Latex for Program { match instruction { // parse gate instructions into a new circuit instruction::Instruction::Gate(gate) => { - // println!("GATE: {:?}", gate.name); - // for each qubit in a single gate instruction for qubit in gate.qubits { @@ -521,9 +541,6 @@ impl Latex for Program { // push wire to diagram circuit diagram.push_wire(wire); - - println!("inner qubit {qubit}"); - // println!("WIRE: {:?}", wire); }, _ => (), } @@ -780,4 +797,31 @@ mod tests { )); } } + + /// Test various programs for LaTeX accuracy + mod programs { + use crate::program::latex::{Settings, tests::get_latex}; + + #[test] + fn test_program_h0_cnot01_x1_cnot12() { + let settings = Settings { + ..Default::default() + }; + insta::assert_snapshot!(get_latex( + "H 0\nCNOT 0 1\nX 1\nCNOT 1 2", + settings + )); + } + + #[test] + fn test_program_h5_cnot52_y2_cnot23() { + let settings = Settings { + ..Default::default() + }; + insta::assert_snapshot!(get_latex( + "H 5\nCNOT 5 2\nY 2\nCNOT 2 3", + settings + )); + } + } } diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_h0_cnot01_x1_cnot12.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_h0_cnot01_x1_cnot12.snap new file mode 100644 index 00000000..b5a11844 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_h0_cnot01_x1_cnot12.snap @@ -0,0 +1,16 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 799 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{H} & \\ctrl{1} & \\qw & \\qw & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\qw & \\targ{} & \\gate{X} & \\ctrl{1} & \\qw \\\\\n\\lstick{\\ket{q_{2}}} & \\qw & \\qw & \\qw & \\targ{} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \gate{H} & \ctrl{1} & \qw & \qw & \qw \\ +\lstick{\ket{q_{1}}} & \qw & \targ{} & \gate{X} & \ctrl{1} & \qw \\ +\lstick{\ket{q_{2}}} & \qw & \qw & \qw & \targ{} & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_h5_cnot52_y2_cnot23.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_h5_cnot52_y2_cnot23.snap new file mode 100644 index 00000000..75ed071b --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_h5_cnot52_y2_cnot23.snap @@ -0,0 +1,16 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 804 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{2}}} & \\qw & \\targ{} & \\gate{Y} & \\ctrl{1} & \\qw \\\\\n\\lstick{\\ket{q_{3}}} & \\qw & \\qw & \\qw & \\targ{} & \\qw \\\\\n\\lstick{\\ket{q_{5}}} & \\gate{H} & \\ctrl{-2} & \\qw & \\qw & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{2}}} & \qw & \targ{} & \gate{Y} & \ctrl{1} & \qw \\ +\lstick{\ket{q_{3}}} & \qw & \qw & \qw & \targ{} & \qw \\ +\lstick{\ket{q_{5}}} & \gate{H} & \ctrl{-2} & \qw & \qw & \qw +\end{tikzcd} +\end{document} From 509404894bef453b3528e695b600072916de964f Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 7 Mar 2023 19:57:47 -0800 Subject: [PATCH 029/118] Optimize ctrl/targ programs with only one call to set_ctrl_targ. --- src/program/latex/mod.rs | 49 ++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 5e80e38f..77ac6ca2 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -264,16 +264,16 @@ impl Diagram { /// positive, otherwise, it is negative. See [`Quantikz`] documentation on /// CNOT for some background that helps justify this approach. /// - /// This function is expensive with a time complexity of O(n^2). This is - /// because every time a new CNOT is added, all other columns with qubits - /// containing a CNOT must be updated since another qubit in a separate - /// instruction may be inserted in between wires. This insertion increases - /// the space between wires which [`Quantikz`] uses to determine how long - /// the line should stretch between control and target qubits. Since it is + /// This function is expensive with a time complexity of O(n^2). + /// [`Quantikz`] uses the space between wires to determine how long a line + /// should stretch between control and target qubits. Since it is /// impossible to determine how many wires will be inserted between control - /// and target qubits for a custom body diagram, all columns should be - /// updated to ensure the connections between all control and target qubits - /// in every column is preserved. + /// and target qubits (e.g. a user decides to impute missing qubits or some + /// number of other instructions are added containing qubits between them) + /// for a custom body diagram, this method should be run after all wires + /// are inserted into the cicuit. If a Quil program contains control and + /// target qubits then this method parses the complete circuit and set the + /// relationships between wires. /// /// # Arguments /// `&mut self` - self as mutible allowing to update the circuit qubits @@ -509,7 +509,7 @@ impl Latex for Program { // store circuit strings let mut diagram = Diagram {settings, ..Default::default()}; - + let mut has_ctrl_targ = false; for instruction in instructions { match instruction { // parse gate instructions into a new circuit @@ -533,6 +533,10 @@ impl Latex for Program { } else { if gate.name == "CNOT" { wire.gates.insert(diagram.column, gate.name.clone()); + + if !has_ctrl_targ { + has_ctrl_targ = true; + } } else { // add the gate to the wire at column 0 wire.gates.insert(0, gate.name.clone()); @@ -544,24 +548,25 @@ impl Latex for Program { }, _ => (), } - } - - // are implicit qubits required in settings and are there at least two or more qubits in the diagram? - if diagram.settings.impute_missing_qubits && diagram.order.len() > 1 { - // add implicit qubits to circuit - diagram.settings.impute_missing_qubits(&mut diagram.order, &mut diagram.circuit); - } - - // set qubit relationships based on gate type - if gate.name == "CNOT" { - diagram.set_ctrl_targ(); - } + } }, // do nothing for all other instructions _ => (), } } + // are implicit qubits required in settings and are there at least two or more qubits in the diagram? + if diagram.settings.impute_missing_qubits && diagram.order.len() > 1 { + // add implicit qubits to circuit + diagram.settings.impute_missing_qubits(&mut diagram.order, &mut diagram.circuit); + } + + // only call method for programs with control and target gates + if has_ctrl_targ { + // identify control and target qubits + diagram.set_ctrl_targ(); + } + let body = diagram.to_string(); let document = Document {body: body, ..Default::default()}; println!("{}", document.to_string()); From dad6ac49ade66d8acf9548be155e018298bc4fed Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 8 Mar 2023 11:19:10 -0800 Subject: [PATCH 030/118] Allow CNOT for a single qubit. --- src/program/latex/mod.rs | 62 ++++++++++++++----- ...ests__gates__gates_cnot_ctrl_0_targ_0.snap | 14 +++++ 2 files changed, 60 insertions(+), 16 deletions(-) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_0.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 77ac6ca2..5d671ba1 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -278,24 +278,37 @@ impl Diagram { /// # Arguments /// `&mut self` - self as mutible allowing to update the circuit qubits fn set_ctrl_targ(&mut self) { + // used to track if a single qubit is a ctrl and targ + let mut found_unitary = false; + // ensure every column preserves the connection between ctrl and targ - for c in 0..=self.column { + 'column: for c in 0..=self.column { + // qubit has been set as ctrl and targ parse next column + if found_unitary { + found_unitary = false; + continue 'column; + } - let mut ctrl = None; - let mut targ = None; + let mut ctrl = None; // the ctrl qubit of this column + let mut targ = None; // the targ qubit of this column // determine the control and target qubits in the column for qubit in &self.order { // get the wire from the circuit as mutible if let Some(wire) = self.circuit.get_mut(qubit) { - if let Some(_) = wire.gates.get(&c) { + if let Some(gate) = wire.gates.get(&c) { + if gate != "CNOT" { + continue 'column; + } + // if ctrl is Some, the remaining qubits are target qubits if let Some(_) = ctrl { // set wire at column as a target qubit wire.targ.insert(c, true); targ = Some(wire.name); // set the target qubit - + break; + // if ctrl is None, this is the first qubit which is the control } else { ctrl = Some(wire.name); // set the control qubit @@ -345,13 +358,21 @@ impl Diagram { } } - // a qubit cannot be a ctrl and targ of itself - if vector == 0 { - LatexGenError::FoundCNOTWithoutCtrlOrTarg{vector}; - } - // set wire at column as the control qubit of target qubit computed as the distance from the control qubit self.circuit.get_mut(&ctrl).and_then(|wire| wire.ctrl.insert(c, vector)); + } else { + // no target indicates CNOT applied on one qubit + if let Some(wire) = self.circuit.get_mut(&ctrl) { + // the wire at this column is the control + wire.ctrl.insert(c, 0); + + // the wire at the next column is the targ + wire.targ.insert(c + 1, true); + + // this is a unitary operation + found_unitary = true; + continue 'column; + } } } } @@ -423,8 +444,6 @@ impl Display for Diagram { } else if let Some(_) = wire.targ.get(&c) { line.push_str(&Command::get_command(Command::Targ)); - } else { - LatexGenError::FoundCNOTWithoutCtrlOrTarg{ vector: 0}; } } else { line.push_str(&Command::get_command(Command::Gate(gate.to_string()))); @@ -490,8 +509,6 @@ pub enum LatexGenError { // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. Example error below. #[error("Tried to pop gate from new circuit and append to wire={wire} but found None.")] NoGateInInst{wire: String}, - #[error("Tried to calculate distance between control and target qubits and found {vector}.")] - FoundCNOTWithoutCtrlOrTarg{vector: i64}, } pub trait Latex { @@ -594,10 +611,16 @@ mod tests { #[test] /// Test functionality of to_latex using default settings. fn test_to_latex() { - let program = Program::from_str("H 5\nCNOT 5 2") + // let program = Program::from_str("H 5\nCNOT 5 2\nY 2\nCNOT 2 3") + // .expect("Quil program should be returned"); + + let program = Program::from_str("H 0\nCNOT 0 0\nCNOT 0 1") .expect("Quil program should be returned"); - let settings = Settings {impute_missing_qubits: true, ..Default::default()}; + let settings = Settings { + impute_missing_qubits: true, + ..Default::default() + }; program .to_latex(settings) @@ -666,6 +689,13 @@ mod tests { )); } + #[test] + fn test_gates_cnot_ctrl_0_targ_0() { + insta::assert_snapshot!(get_latex( + "CNOT 0 0", + Settings::default())); + } + #[test] fn test_gates_cnot_ctrl_0_targ_1() { insta::assert_snapshot!(get_latex( diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_0.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_0.snap new file mode 100644 index 00000000..b62003c4 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_0.snap @@ -0,0 +1,14 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 687 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\ctrl{0} & \\targ{} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \ctrl{0} & \targ{} & \qw +\end{tikzcd} +\end{document} From 423a7ec799ec6997407a81c702919e942976e1ff Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 8 Mar 2023 14:59:01 -0800 Subject: [PATCH 031/118] Revert "Allow CNOT for a single qubit." This reverts commit dad6ac49ade66d8acf9548be155e018298bc4fed. --- src/program/latex/mod.rs | 62 +++++-------------- ...ests__gates__gates_cnot_ctrl_0_targ_0.snap | 14 ----- 2 files changed, 16 insertions(+), 60 deletions(-) delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_0.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 5d671ba1..77ac6ca2 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -278,37 +278,24 @@ impl Diagram { /// # Arguments /// `&mut self` - self as mutible allowing to update the circuit qubits fn set_ctrl_targ(&mut self) { - // used to track if a single qubit is a ctrl and targ - let mut found_unitary = false; - // ensure every column preserves the connection between ctrl and targ - 'column: for c in 0..=self.column { - // qubit has been set as ctrl and targ parse next column - if found_unitary { - found_unitary = false; - continue 'column; - } + for c in 0..=self.column { - let mut ctrl = None; // the ctrl qubit of this column - let mut targ = None; // the targ qubit of this column + let mut ctrl = None; + let mut targ = None; // determine the control and target qubits in the column for qubit in &self.order { // get the wire from the circuit as mutible if let Some(wire) = self.circuit.get_mut(qubit) { - if let Some(gate) = wire.gates.get(&c) { - if gate != "CNOT" { - continue 'column; - } - + if let Some(_) = wire.gates.get(&c) { // if ctrl is Some, the remaining qubits are target qubits if let Some(_) = ctrl { // set wire at column as a target qubit wire.targ.insert(c, true); targ = Some(wire.name); // set the target qubit - break; - + // if ctrl is None, this is the first qubit which is the control } else { ctrl = Some(wire.name); // set the control qubit @@ -358,21 +345,13 @@ impl Diagram { } } + // a qubit cannot be a ctrl and targ of itself + if vector == 0 { + LatexGenError::FoundCNOTWithoutCtrlOrTarg{vector}; + } + // set wire at column as the control qubit of target qubit computed as the distance from the control qubit self.circuit.get_mut(&ctrl).and_then(|wire| wire.ctrl.insert(c, vector)); - } else { - // no target indicates CNOT applied on one qubit - if let Some(wire) = self.circuit.get_mut(&ctrl) { - // the wire at this column is the control - wire.ctrl.insert(c, 0); - - // the wire at the next column is the targ - wire.targ.insert(c + 1, true); - - // this is a unitary operation - found_unitary = true; - continue 'column; - } } } } @@ -444,6 +423,8 @@ impl Display for Diagram { } else if let Some(_) = wire.targ.get(&c) { line.push_str(&Command::get_command(Command::Targ)); + } else { + LatexGenError::FoundCNOTWithoutCtrlOrTarg{ vector: 0}; } } else { line.push_str(&Command::get_command(Command::Gate(gate.to_string()))); @@ -509,6 +490,8 @@ pub enum LatexGenError { // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. Example error below. #[error("Tried to pop gate from new circuit and append to wire={wire} but found None.")] NoGateInInst{wire: String}, + #[error("Tried to calculate distance between control and target qubits and found {vector}.")] + FoundCNOTWithoutCtrlOrTarg{vector: i64}, } pub trait Latex { @@ -611,16 +594,10 @@ mod tests { #[test] /// Test functionality of to_latex using default settings. fn test_to_latex() { - // let program = Program::from_str("H 5\nCNOT 5 2\nY 2\nCNOT 2 3") - // .expect("Quil program should be returned"); - - let program = Program::from_str("H 0\nCNOT 0 0\nCNOT 0 1") + let program = Program::from_str("H 5\nCNOT 5 2") .expect("Quil program should be returned"); - let settings = Settings { - impute_missing_qubits: true, - ..Default::default() - }; + let settings = Settings {impute_missing_qubits: true, ..Default::default()}; program .to_latex(settings) @@ -689,13 +666,6 @@ mod tests { )); } - #[test] - fn test_gates_cnot_ctrl_0_targ_0() { - insta::assert_snapshot!(get_latex( - "CNOT 0 0", - Settings::default())); - } - #[test] fn test_gates_cnot_ctrl_0_targ_1() { insta::assert_snapshot!(get_latex( diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_0.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_0.snap deleted file mode 100644 index b62003c4..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_0.snap +++ /dev/null @@ -1,14 +0,0 @@ ---- -source: src/program/latex/mod.rs -assertion_line: 687 -expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\ctrl{0} & \\targ{} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" ---- -\documentclass[convert={density=300,outext=.png}]{standalone} -\usepackage[margin=1in]{geometry} -\usepackage{tikz} -\usetikzlibrary{quantikz} -\begin{document} -\begin{tikzcd} -\lstick{\ket{q_{0}}} & \ctrl{0} & \targ{} & \qw -\end{tikzcd} -\end{document} From e36623a702f455c7db06b8c89f756bcd23e781ae Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 8 Mar 2023 15:22:05 -0800 Subject: [PATCH 032/118] Handle CNOT without target. --- src/program/latex/mod.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 77ac6ca2..b2a1c68a 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -279,7 +279,7 @@ impl Diagram { /// `&mut self` - self as mutible allowing to update the circuit qubits fn set_ctrl_targ(&mut self) { // ensure every column preserves the connection between ctrl and targ - for c in 0..=self.column { + 'column: for c in 0..=self.column { let mut ctrl = None; let mut targ = None; @@ -288,8 +288,11 @@ impl Diagram { for qubit in &self.order { // get the wire from the circuit as mutible if let Some(wire) = self.circuit.get_mut(qubit) { + if let Some(gate) = wire.gates.get(&c) { + if gate != "CNOT" { + continue 'column; + } - if let Some(_) = wire.gates.get(&c) { // if ctrl is Some, the remaining qubits are target qubits if let Some(_) = ctrl { // set wire at column as a target qubit @@ -345,13 +348,10 @@ impl Diagram { } } - // a qubit cannot be a ctrl and targ of itself - if vector == 0 { - LatexGenError::FoundCNOTWithoutCtrlOrTarg{vector}; - } - // set wire at column as the control qubit of target qubit computed as the distance from the control qubit self.circuit.get_mut(&ctrl).and_then(|wire| wire.ctrl.insert(c, vector)); + } else { + panic!("{}", LatexGenError::FoundCNOTWithNoTarget); } } } @@ -422,9 +422,6 @@ impl Display for Diagram { } else if let Some(_) = wire.targ.get(&c) { line.push_str(&Command::get_command(Command::Targ)); - - } else { - LatexGenError::FoundCNOTWithoutCtrlOrTarg{ vector: 0}; } } else { line.push_str(&Command::get_command(Command::Gate(gate.to_string()))); @@ -490,8 +487,8 @@ pub enum LatexGenError { // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. Example error below. #[error("Tried to pop gate from new circuit and append to wire={wire} but found None.")] NoGateInInst{wire: String}, - #[error("Tried to calculate distance between control and target qubits and found {vector}.")] - FoundCNOTWithoutCtrlOrTarg{vector: i64}, + #[error("Tried to parse CNOT and found a control qubit without a target.")] + FoundCNOTWithNoTarget, } pub trait Latex { @@ -680,6 +677,14 @@ mod tests { Settings::default())); } + #[test] + #[should_panic] + fn test_gates_cnot_error_single_qubit() { + get_latex( + "CNOT 0 0", + Settings::default()); + } + #[test] fn test_gates_h_and_cnot_ctrl_0_targ_1() { insta::assert_snapshot!(get_latex( From b2b1f1a554efd58c4e0c10b1f9fde96aa6e85baa Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 8 Mar 2023 14:46:50 -0800 Subject: [PATCH 033/118] Working draft of CCNOT and CONTROLLED. --- src/program/latex/mod.rs | 79 +++++++++++++++++-- ...am__latex__tests__gates__gate_toffoli.snap | 16 ++++ ...sts__modifiers__modifier_toffoli_gate.snap | 16 ++++ 3 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_toffoli.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_toffoli_gate.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 5d671ba1..56f02bd4 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -533,9 +533,9 @@ impl Latex for Program { instruction::Instruction::Gate(gate) => { // for each qubit in a single gate instruction for qubit in gate.qubits { - match qubit { instruction::Qubit::Fixed(qubit) => { + println!("{qubit}"); // create a new wire let mut wire = Wire::default(); @@ -548,7 +548,24 @@ impl Latex for Program { // add the gate to the wire at column 0 wire.gates.insert(0, gate.name.clone()); } else { - if gate.name == "CNOT" { + if gate.name.starts_with('C') { + + // count how many controlled modifiers are in the instruction + let mut controlleds = 0; + for modifer in &gate.modifiers { + match modifer { + instruction::GateModifier::Controlled => controlleds += 1, + _ => (), + } + } + + // count how many 'C's are in the gate name which are controlleds + for c in gate.name.chars() { + if c == 'C' { + controlleds += 1; + } + } + wire.gates.insert(diagram.column, gate.name.clone()); if !has_ctrl_targ { @@ -614,7 +631,7 @@ mod tests { // let program = Program::from_str("H 5\nCNOT 5 2\nY 2\nCNOT 2 3") // .expect("Quil program should be returned"); - let program = Program::from_str("H 0\nCNOT 0 0\nCNOT 0 1") + let program = Program::from_str("CCNOT 1 2 0") .expect("Quil program should be returned"); let settings = Settings { @@ -726,10 +743,56 @@ mod tests { )); } - // #[test] - // fn test_gate_controlled() { - // insta::assert_snapshot!(get_latex("CONTROLLED H 3 2")); - // } + #[test] + fn test_gate_toffoli() { + insta::assert_snapshot!(get_latex( + "CCNOT 1 2 0", + Settings::default() + )); + } + + #[test] + fn test_gate_ccnot_and_controlled_cnot_equality() { + let ccnot = get_latex( + "CCNOT 1 2 0", + Settings::default() + ); + + let controlled = get_latex( + "CONTROLLED CNOT 1 2 0", + Settings::default() + ); + + assert_eq!(ccnot, controlled); + } + } + + /// Test module for modifiers + mod modifiers { + use crate::program::latex::{Settings, tests::get_latex}; + + #[test] + fn test_modifier_toffoli_gate() { + insta::assert_snapshot!(get_latex( + "CONTROLLED CNOT 2 1 0", + Settings::default() + )); + } + + #[test] + fn test_modifier_controlled_cnot_and_ccnot_equality() { + let controlled = get_latex( + "CONTROLLED CNOT 2 1 0", + Settings::default() + ); + + let ccnot = get_latex( + "CCNOT 2 1 0", + Settings::default() + ); + + assert_eq!(controlled, ccnot); + } } /// Test module for Quantikz Commands @@ -859,4 +922,4 @@ mod tests { )); } } -} +} \ No newline at end of file diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_toffoli.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_toffoli.snap new file mode 100644 index 00000000..4835d759 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_toffoli.snap @@ -0,0 +1,16 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 731 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\targ{} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\ctrl{-1} & \\qw \\\\\n\\lstick{\\ket{q_{2}}} & \\ctrl{-2} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \targ{} & \qw \\ +\lstick{\ket{q_{1}}} & \ctrl{-1} & \qw \\ +\lstick{\ket{q_{2}}} & \ctrl{-2} & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_toffoli_gate.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_toffoli_gate.snap new file mode 100644 index 00000000..c56ba8ef --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_toffoli_gate.snap @@ -0,0 +1,16 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 750 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\targ{} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\ctrl{-1} & \\qw \\\\\n\\lstick{\\ket{q_{2}}} & \\ctrl{-2} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \targ{} & \qw \\ +\lstick{\ket{q_{1}}} & \ctrl{-1} & \qw \\ +\lstick{\ket{q_{2}}} & \ctrl{-2} & \qw +\end{tikzcd} +\end{document} From 298075f07ecbcb854b77d078a01a2c142bc43f84 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 8 Mar 2023 14:54:24 -0800 Subject: [PATCH 034/118] Revert "Allow CNOT for a single qubit." This reverts commit dad6ac49ade66d8acf9548be155e018298bc4fed. --- src/program/latex/mod.rs | 57 +++++-------------- ...ests__gates__gates_cnot_ctrl_0_targ_0.snap | 14 ----- 2 files changed, 15 insertions(+), 56 deletions(-) delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_0.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 56f02bd4..39a3354a 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -278,37 +278,24 @@ impl Diagram { /// # Arguments /// `&mut self` - self as mutible allowing to update the circuit qubits fn set_ctrl_targ(&mut self) { - // used to track if a single qubit is a ctrl and targ - let mut found_unitary = false; - // ensure every column preserves the connection between ctrl and targ - 'column: for c in 0..=self.column { - // qubit has been set as ctrl and targ parse next column - if found_unitary { - found_unitary = false; - continue 'column; - } + for c in 0..=self.column { - let mut ctrl = None; // the ctrl qubit of this column - let mut targ = None; // the targ qubit of this column + let mut ctrl = None; + let mut targ = None; // determine the control and target qubits in the column for qubit in &self.order { // get the wire from the circuit as mutible if let Some(wire) = self.circuit.get_mut(qubit) { - if let Some(gate) = wire.gates.get(&c) { - if gate != "CNOT" { - continue 'column; - } - + if let Some(_) = wire.gates.get(&c) { // if ctrl is Some, the remaining qubits are target qubits if let Some(_) = ctrl { // set wire at column as a target qubit wire.targ.insert(c, true); targ = Some(wire.name); // set the target qubit - break; - + // if ctrl is None, this is the first qubit which is the control } else { ctrl = Some(wire.name); // set the control qubit @@ -358,21 +345,13 @@ impl Diagram { } } + // a qubit cannot be a ctrl and targ of itself + if vector == 0 { + LatexGenError::FoundCNOTWithoutCtrlOrTarg{vector}; + } + // set wire at column as the control qubit of target qubit computed as the distance from the control qubit self.circuit.get_mut(&ctrl).and_then(|wire| wire.ctrl.insert(c, vector)); - } else { - // no target indicates CNOT applied on one qubit - if let Some(wire) = self.circuit.get_mut(&ctrl) { - // the wire at this column is the control - wire.ctrl.insert(c, 0); - - // the wire at the next column is the targ - wire.targ.insert(c + 1, true); - - // this is a unitary operation - found_unitary = true; - continue 'column; - } } } } @@ -444,6 +423,8 @@ impl Display for Diagram { } else if let Some(_) = wire.targ.get(&c) { line.push_str(&Command::get_command(Command::Targ)); + } else { + LatexGenError::FoundCNOTWithoutCtrlOrTarg{ vector: 0}; } } else { line.push_str(&Command::get_command(Command::Gate(gate.to_string()))); @@ -509,6 +490,8 @@ pub enum LatexGenError { // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. Example error below. #[error("Tried to pop gate from new circuit and append to wire={wire} but found None.")] NoGateInInst{wire: String}, + #[error("Tried to calculate distance between control and target qubits and found {vector}.")] + FoundCNOTWithoutCtrlOrTarg{vector: i64}, } pub trait Latex { @@ -634,10 +617,7 @@ mod tests { let program = Program::from_str("CCNOT 1 2 0") .expect("Quil program should be returned"); - let settings = Settings { - impute_missing_qubits: true, - ..Default::default() - }; + let settings = Settings {impute_missing_qubits: true, ..Default::default()}; program .to_latex(settings) @@ -706,13 +686,6 @@ mod tests { )); } - #[test] - fn test_gates_cnot_ctrl_0_targ_0() { - insta::assert_snapshot!(get_latex( - "CNOT 0 0", - Settings::default())); - } - #[test] fn test_gates_cnot_ctrl_0_targ_1() { insta::assert_snapshot!(get_latex( diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_0.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_0.snap deleted file mode 100644 index b62003c4..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_cnot_ctrl_0_targ_0.snap +++ /dev/null @@ -1,14 +0,0 @@ ---- -source: src/program/latex/mod.rs -assertion_line: 687 -expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\ctrl{0} & \\targ{} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" ---- -\documentclass[convert={density=300,outext=.png}]{standalone} -\usepackage[margin=1in]{geometry} -\usepackage{tikz} -\usetikzlibrary{quantikz} -\begin{document} -\begin{tikzcd} -\lstick{\ket{q_{0}}} & \ctrl{0} & \targ{} & \qw -\end{tikzcd} -\end{document} From 39f8f46d2b5c3ecf3d390207900cf2e9363637f8 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 9 Mar 2023 13:01:58 -0800 Subject: [PATCH 035/118] Add CONTROLLED and CCNOT. --- src/program/latex/mod.rs | 99 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 73976087..47279f3a 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -238,6 +238,8 @@ struct Diagram { column: u32, /// maintains the instruction order of qubits order: Vec, + /// column and qubits at which the qubits form relationships + relationships: HashMap>, /// a BTreeMap of wires with the name of the wire as the key circuit: BTreeMap>, } @@ -248,6 +250,7 @@ impl Default for Diagram { settings: Settings::default(), column: 0, order: vec![], + relationships: HashMap::new(), circuit: BTreeMap::new(), } } @@ -281,36 +284,47 @@ impl Diagram { // ensure every column preserves the connection between ctrl and targ 'column: for c in 0..=self.column { - let mut ctrl = None; - let mut targ = None; + let mut ctrls = vec![]; // the control qubits + let mut targ = None; // the targ qubit - // determine the control and target qubits in the column - for qubit in &self.order { - // get the wire from the circuit as mutible - if let Some(wire) = self.circuit.get_mut(qubit) { - if let Some(gate) = wire.gates.get(&c) { - if gate != "CNOT" { - continue 'column; - } + // determine if a relationship exists at this column + if let Some(relationship) = self.relationships.get(&c) { + // determine the control and target qubits + for qubit in relationship { + // a relationship with one qubit is invalid + if relationship.len() < 2 { + panic!("{}", LatexGenError::FoundCNOTWithNoTarget); + } - // if ctrl is Some, the remaining qubits are target qubits - if let Some(_) = ctrl { - // set wire at column as a target qubit + // the last qubit is the targ + if *qubit == relationship[relationship.len()-1] { + if let Some(wire) = self.circuit.get_mut(qubit) { + // insert as target at this column wire.targ.insert(c, true); - targ = Some(wire.name); // set the target qubit - - - // if ctrl is None, this is the first qubit which is the control - } else { - ctrl = Some(wire.name); // set the control qubit + + // set 'column loop targ variable to this targ that control qubits will find distance from on their respective wires + targ = Some(wire.name) } + // all other qubits are the controls + } else { + if let Some(wire) = self.circuit.get_mut(qubit) { + // insert as control at this column with initial value 0, targeting themselves + wire.ctrl.insert(c, 0); + + // push ctrl to 'column loop ctrl variables with initial value requiring update based on targ + ctrls.push(wire.name); + } } } + } else { + // no relationships found on this column, go to next + continue 'column; } + // determine the physical vector where a positive vector points from control to target, negative, from target to control. The magnitude of the vector is the absolute value of the distance between them - if let Some(ctrl) = ctrl { - if let Some(targ) = targ { + if let Some(targ) = targ { + for ctrl in ctrls { // distance between qubits is the order of the ctrl and targ qubits within the circuit // represent inclusive [open, close] brackets of a range @@ -350,8 +364,6 @@ impl Diagram { } // set wire at column as the control qubit of target qubit computed as the distance from the control qubit self.circuit.get_mut(&ctrl).and_then(|wire| wire.ctrl.insert(c, vector)); - } else { - panic!("{}", LatexGenError::FoundCNOTWithNoTarget); } } } @@ -367,6 +379,8 @@ impl Diagram { /// `&mut self` - exposes HashMap> /// `wire` - the wire to be pushed or updated to in circuits fn push_wire(&mut self, wire: Wire) { + let qubit = wire.name; + // find wire in circuit collection match self.circuit.get_mut(&wire.name) { // wire found, push to existing wire @@ -374,7 +388,7 @@ impl Diagram { // indicate a new item to be added by incrementing column self.column += 1; - // if wire contains gates to add + // get the new gate from the wire and insert into existing wire if let Some(gate) = wire.gates.get(&0) { // add gates to wire in circuit wire_in_circuit.gates.insert(self.column, gate.to_string()); @@ -386,6 +400,22 @@ impl Diagram { self.circuit.insert(wire.name, Box::new(wire)); }, } + + // initalize relationships between multi qubit gates + if let Some(wire) = self.circuit.get(&qubit) { + // get the newly added gate if any at the column it was added + if let Some(gate) = wire.gates.get(&self.column) { + // tag relationships for multi qubit gates + if gate.starts_with('C') { + // add the qubits to the set of related qubits in the current column + if let Some(qubits) = self.relationships.get_mut(&self.column) { + qubits.push(qubit); + } else { + self.relationships.insert(self.column, vec![qubit]); + } + } + } + } } } @@ -416,7 +446,7 @@ impl Display for Diagram { if let Some(gate) = wire.gates.get(&c) { line.push_str(" & "); - if gate == "CNOT" { + if gate.starts_with('C') { if let Some(targ) = wire.ctrl.get(&c) { line.push_str(&Command::get_command(Command::Ctrl(targ.to_string()))); @@ -515,8 +545,6 @@ impl Latex for Program { for qubit in gate.qubits { match qubit { instruction::Qubit::Fixed(qubit) => { - println!("{qubit}"); - // create a new wire let mut wire = Wire::default(); @@ -529,23 +557,6 @@ impl Latex for Program { wire.gates.insert(0, gate.name.clone()); } else { if gate.name.starts_with('C') { - - // count how many controlled modifiers are in the instruction - let mut controlleds = 0; - for modifer in &gate.modifiers { - match modifer { - instruction::GateModifier::Controlled => controlleds += 1, - _ => (), - } - } - - // count how many 'C's are in the gate name which are controlleds - for c in gate.name.chars() { - if c == 'C' { - controlleds += 1; - } - } - wire.gates.insert(diagram.column, gate.name.clone()); if !has_ctrl_targ { From b6a9e202d348cbea2dc049b14d1211da70789d65 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 9 Mar 2023 14:06:41 -0800 Subject: [PATCH 036/118] Update doc and remove unused variables. --- src/program/latex/mod.rs | 95 ++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 53 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 47279f3a..827d7e6d 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -142,7 +142,6 @@ impl Settings { /// and pushed to the diagram's circuit. /// /// # Arguments - /// `&mut Vec order` - the qubit order of the diagram /// `&mut BTreeMap> circuit` - the circuit of the diagram /// /// # Examples @@ -156,7 +155,11 @@ impl Settings { /// }; /// program.to_latex(settings).expect(""); /// ``` - pub fn impute_missing_qubits(&self, order: &mut Vec, circuit: &mut BTreeMap>) { + pub fn impute_missing_qubits(&self, circuit: &mut BTreeMap>) { + // requires at least two qubits to impute missing qubits + if circuit.len() < 2 { + return + } // get the first qubit in the BTreeMap let mut first = 0; @@ -177,7 +180,6 @@ impl Settings { Some(_) => (), None => { let wire = Wire {name: qubit, ..Default::default()}; - order.push(qubit); circuit.insert(qubit, Box::new(wire)); }, } @@ -219,26 +221,14 @@ impl Display for Document { } } -/// A Diagram represents a collection of circuits. It encodes the relationships -/// between the circuits and their positions or the row that it fills. A row is -/// one of the Circuits in the HashMap. At this view over the circuits, Diagram -/// can form a relationship between circuits based on information about the -/// column and row. For example, one row, say qubit 0, at some column can hold -/// information that it is the control. If another row, say qubit 1, at this -/// same exact column says that it is the target, then it can inform qubit 0 -/// that it is controlling qubit 1. This information is then placed into the -/// circuit as the diagram forms the equivalent LaTeX form for each qubit. -/// The circuit dimension is column:`column+1`x row:`circuit.len+1` where the -/// product of these two is the total number of items on the entire circuit. +/// A Diagram represents a collection of wires in a circuit. It encodes the wires row in the diagram and its relationship to other wires. A row is one of the wires in the circuit BTreeMap. Diagram tracks relationships between wires with two pieces of information--1. the wires row (its order in the BTreeMap), and 2. the column that spreads between all wires that pass through a multi qubit gate 'e.g. CNOT'. The size of the diagram can be measured by multiplying the column with the length of the circuit. This is an [m x n] matrix where each element in the matrix represents an item to be rendered onto the diagram using one of the [`Quantikz`] commands. #[derive(Debug)] struct Diagram { - /// Settings + /// customizes how the diagram renders the circuit settings: Settings, - /// total-1 elements on each wire + /// total number of elements on each wire column: u32, - /// maintains the instruction order of qubits - order: Vec, - /// column and qubits at which the qubits form relationships + /// column at which qubits in positional order form relationships relationships: HashMap>, /// a BTreeMap of wires with the name of the wire as the key circuit: BTreeMap>, @@ -249,7 +239,6 @@ impl Default for Diagram { Self { settings: Settings::default(), column: 0, - order: vec![], relationships: HashMap::new(), circuit: BTreeMap::new(), } @@ -262,21 +251,22 @@ impl Diagram { /// The logic of this function is visualized using a physical vector with /// the tail at the control qubit and the head pointing to the target /// qubit. The distance between the qubits represents the number of wires - /// between them therefore, the space that the vector needs to traverse. If - /// the control qubit comes before the target qubit the direction is - /// positive, otherwise, it is negative. See [`Quantikz`] documentation on - /// CNOT for some background that helps justify this approach. + /// between them, i.e the space that the vector needs to traverse. If the + /// control qubit comes before the target qubit the direction is positive, + /// otherwise, it is negative. See [`Quantikz`] documentation on CNOT for + /// some background that helps justify this approach. /// - /// This function is expensive with a time complexity of O(n^2). - /// [`Quantikz`] uses the space between wires to determine how long a line - /// should stretch between control and target qubits. Since it is - /// impossible to determine how many wires will be inserted between control - /// and target qubits (e.g. a user decides to impute missing qubits or some - /// number of other instructions are added containing qubits between them) - /// for a custom body diagram, this method should be run after all wires - /// are inserted into the cicuit. If a Quil program contains control and - /// target qubits then this method parses the complete circuit and set the - /// relationships between wires. + /// This function is expensive with a time complexity of O(n^2). In the + /// worst case scenario every column contains a multi qubit gate with every + /// qubit as either a target or control. [`Quantikz`] uses the space + /// between wires to determine how long a line should stretch between + /// control and target qubits. Since it is impossible to determine how many + /// wires will be inserted between control and target qubits (e.g. a user + /// decides to impute missing qubits or some number of other instructions + /// are added containing qubits between them) for a custom body diagram, + /// this method can only be run after all wires are inserted into the + /// cicuit. In particular, only run this method if a Quil program contains + /// multi qubit gates. /// /// # Arguments /// `&mut self` - self as mutible allowing to update the circuit qubits @@ -297,7 +287,7 @@ impl Diagram { } // the last qubit is the targ - if *qubit == relationship[relationship.len()-1] { + if *qubit == relationship[relationship.len() - 1] { if let Some(wire) = self.circuit.get_mut(qubit) { // insert as target at this column wire.targ.insert(c, true); @@ -324,9 +314,8 @@ impl Diagram { // determine the physical vector where a positive vector points from control to target, negative, from target to control. The magnitude of the vector is the absolute value of the distance between them if let Some(targ) = targ { + // distance between qubits is the space between the ctrl and targ qubits in the circuit for ctrl in ctrls { - // distance between qubits is the order of the ctrl and targ qubits within the circuit - // represent inclusive [open, close] brackets of a range let mut open = None; // opening qubit in range let mut close = None; // closing qubit in range @@ -394,9 +383,8 @@ impl Diagram { wire_in_circuit.gates.insert(self.column, gate.to_string()); } }, - // no wire found, preserve insertion order and insert new wire + // no wire found insert new wire None => { - self.order.push(wire.name); self.circuit.insert(wire.name, Box::new(wire)); }, } @@ -484,20 +472,24 @@ impl Display for Diagram { } } -/// A circuit represents a single wire. A wire chains columns together of -/// various Quantikz elements (using `&`). Encoded in each column is an index -/// which determines the placement of the element on the circuit. Each column -/// can hold only one element, therefore, each encoded index is unique between -/// all of the attributes. Using this property, a String can be generated. +/// A Wire represents a single qubit. A wire only needs to keep track of all +/// the elements it contains mapped to some arbitrary column. Diagram keeps +/// track of where the Wire belongs in the larger circuit, its row, and knows +/// how each Wire relates to each other at that column. When Diagram parses the +/// wires as a collection, if the Wire relates to another at some column, then +/// its field will be updated at that column based on the knowledge Diagram has +/// about this connection. This updated value also looks arbitrary to Wire, it +/// does not explicitly define which qubit it relates to, but a digit that +/// describes how far away it is from the related qubit based on [`Quantikz`]. #[derive(Debug)] pub struct Wire { - /// a wire, ket(qubit) placed using the Lstick or Rstick commands + /// the name of ket(qubit) placed using the Lstick or Rstick commands name: u64, - /// gate element(s) placed at column_u32 on wire using the Gate command + /// gate elements placed at column on wire using the Gate command gates: HashMap, - /// control at key=column with value=targ + /// control at column with distance from targ wire ctrl: HashMap, - /// target at key=column + /// at this column is the wire a target? targ: HashMap, } @@ -514,9 +506,6 @@ impl Default for Wire { #[derive(thiserror::Error, Debug)] pub enum LatexGenError { - // TODO: Add variants for each error type using `thiserror` crate to return detailed Result::Err. Example error below. - #[error("Tried to pop gate from new circuit and append to wire={wire} but found None.")] - NoGateInInst{wire: String}, #[error("Tried to parse CNOT and found a control qubit without a target.")] FoundCNOTWithNoTarget, } @@ -581,9 +570,9 @@ impl Latex for Program { } // are implicit qubits required in settings and are there at least two or more qubits in the diagram? - if diagram.settings.impute_missing_qubits && diagram.order.len() > 1 { + if diagram.settings.impute_missing_qubits { // add implicit qubits to circuit - diagram.settings.impute_missing_qubits(&mut diagram.order, &mut diagram.circuit); + diagram.settings.impute_missing_qubits(&mut diagram.circuit); } // only call method for programs with control and target gates From 6e1dfba20158136dd099cb16ca423dbcb52a2a04 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 9 Mar 2023 14:37:19 -0800 Subject: [PATCH 037/118] Reformat doc. --- src/program/latex/mod.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 827d7e6d..9fa341d3 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -221,7 +221,15 @@ impl Display for Document { } } -/// A Diagram represents a collection of wires in a circuit. It encodes the wires row in the diagram and its relationship to other wires. A row is one of the wires in the circuit BTreeMap. Diagram tracks relationships between wires with two pieces of information--1. the wires row (its order in the BTreeMap), and 2. the column that spreads between all wires that pass through a multi qubit gate 'e.g. CNOT'. The size of the diagram can be measured by multiplying the column with the length of the circuit. This is an [m x n] matrix where each element in the matrix represents an item to be rendered onto the diagram using one of the [`Quantikz`] commands. +/// A Diagram represents a collection of wires in a circuit. It encodes the +/// wires row in the diagram and its relationship to other wires. A row is one +/// of the wires in the circuit BTreeMap. Diagram tracks relationships between +/// wires with two pieces of information--1. the wires row (its order in the +/// BTreeMap), and 2. the column that spreads between all wires that pass +/// through a multi qubit gate 'e.g. CNOT'. The size of the diagram can be +/// measured by multiplying the column with the length of the circuit. This is +/// an [m x n] matrix where each element in the matrix represents an item to be +/// rendered onto the diagram using one of the [`Quantikz`] commands. #[derive(Debug)] struct Diagram { /// customizes how the diagram renders the circuit From 9062255a727c7ebb9fa1ca009d9cccbf70232dd0 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 9 Mar 2023 15:41:57 -0800 Subject: [PATCH 038/118] Reformat file. --- src/program/latex/mod.rs | 401 +++++++++++++++++---------------------- 1 file changed, 176 insertions(+), 225 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 9fa341d3..ebbaecf9 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -1,35 +1,35 @@ //! LaTeX diagram generation for quil programs. -//! +//! //! Provides a feature to generate diagrams using the LaTeX subpackage TikZ/ //! Quantikz for a given quil Program. -//! +//! //! - Usage: `Program.to_latex(settings: Settings);` -//! +//! //! - Description: -//! [`Quantikz`] is a subpackage in the TikZ package used to generate qubit +//! [`Quantikz`] is a subpackage in the TikZ package used to generate qubit //! circuits. A qubit is represented as a wire separated into multiple columns. -//! Each column contains a symbol of an operation on the qubit. Multiple qubits -//! can be stacked into rows with interactions between any number of them drawn -//! as a connecting bar to each involved qubit wire. Commands are used to -//! control what is rendered on a circuit, e.g. names of qubits, identifying -//! control/target qubits, gates, etc. View [`Quantikz`] for the documentation +//! Each column contains a symbol of an operation on the qubit. Multiple qubits +//! can be stacked into rows with interactions between any number of them drawn +//! as a connecting bar to each involved qubit wire. Commands are used to +//! control what is rendered on a circuit, e.g. names of qubits, identifying +//! control/target qubits, gates, etc. View [`Quantikz`] for the documentation //! on its usage and full set of commands. -//! -//! This module should be viewed as a self contained partial implementation of -//! [`Quantikz`] with all available commands listed as variants in a Command -//! enum. This feature provides the user variability in how they wish to render +//! +//! This module should be viewed as a self contained partial implementation of +//! [`Quantikz`] with all available commands listed as variants in a Command +//! enum. This feature provides the user variability in how they wish to render //! their Program circuits with metadata contained in a Settings struct. -//! +//! //! [`Quantikz`]: https://arxiv.org/pdf/1809.03842.pdf -use std::collections::{HashMap, BTreeMap}; +use std::collections::{BTreeMap, HashMap}; use std::fmt::{format, Display}; -use crate::Program; use crate::instruction; +use crate::Program; -/// Available commands used for building circuits with the same names taken -/// from the Quantikz documentation for easy reference. LaTeX string denoted +/// Available commands used for building circuits with the same names taken +/// from the Quantikz documentation for easy reference. LaTeX string denoted /// inside `backticks`. /// Single wire commands: lstick, rstick, qw, meter /// Multi-wire commands: ctrl, targ, control, (swap, targx) @@ -42,10 +42,10 @@ pub enum Command { Gate(String), /// `\qw`: Connect the current cell to the previous cell i.e. "do nothing". Qw, - /// `\\`: Start a new row + /// `\\`: Start a new row Nr, /// `\meter{wire}`: Measure a qubit. - Meter(String), + Meter(String), /// `\ctrl{wire}`: Make a control qubit--different from Control. Ctrl(String), /// `\targ{}`: Make a controlled-not gate. @@ -60,10 +60,10 @@ pub enum Command { impl Command { /// Returns the LaTeX String for a given Command variant. - /// + /// /// # Arguments /// `command` - A Command variant. - /// + /// /// # Examples /// ``` /// use quil_rs::program::latex::Command; @@ -72,28 +72,22 @@ impl Command { /// ``` pub fn get_command(command: Self) -> String { match command { - Self::Lstick(wire) => - format(format_args!(r#"\lstick{{\ket{{q_{{{wire}}}}}}}"#)), - Self::Rstick(wire) => - format(format_args!(r#"\rstick{{\ket{{q_{{{wire}}}}}}}"#)), - Self::Gate(name) => - format(format_args!(r#"\gate{{{name}}}"#)), + Self::Lstick(wire) => format(format_args!(r#"\lstick{{\ket{{q_{{{wire}}}}}}}"#)), + Self::Rstick(wire) => format(format_args!(r#"\rstick{{\ket{{q_{{{wire}}}}}}}"#)), + Self::Gate(name) => format(format_args!(r#"\gate{{{name}}}"#)), Self::Qw => r"\qw".to_string(), Self::Nr => r"\\".to_string(), - Self::Meter(wire) => - format(format_args!(r#"\meter{{{wire}}}"#)), - Self::Ctrl(wire) => - format(format_args!(r#"\ctrl{{{wire}}}"#)), + Self::Meter(wire) => format(format_args!(r#"\meter{{{wire}}}"#)), + Self::Ctrl(wire) => format(format_args!(r#"\ctrl{{{wire}}}"#)), Self::Targ => r"\targ{}".to_string(), Self::Control => r"\control{}".to_string(), - Self::Swap(wire) => - format(format_args!(r#"\swap{{{wire}}}"#)), + Self::Swap(wire) => format(format_args!(r#"\swap{{{wire}}}"#)), Self::TargX => r"\targX{}".to_string(), } } } -/// Settings contains the metadata that allows the user to customize how the +/// Settings contains the metadata that allows the user to customize how the /// circuit is rendered or use the default implementation. #[derive(Debug)] pub struct Settings { @@ -114,17 +108,17 @@ pub struct Settings { impl Default for Settings { /// Returns the default Settings. fn default() -> Self { - Self { + Self { /// false: π is pi. - texify_numerical_constants: true, + texify_numerical_constants: true, /// true: `CNOT 0 2` would have three qubit lines: 0, 1, 2. - impute_missing_qubits: false, + impute_missing_qubits: false, /// false: remove Lstick/Rstick from latex. - label_qubit_lines: true, + label_qubit_lines: true, /// true: `RX(pi)` displayed as `X_{\\pi}` instead of `R_X(\\pi)`. - abbreviate_controlled_rotations: false, + abbreviate_controlled_rotations: false, /// 0: condenses the size of subdiagrams. - qubit_line_open_wire_length: 1, + qubit_line_open_wire_length: 1, /// false: include Meter in the current column. right_align_terminal_measurements: true, } @@ -137,13 +131,13 @@ impl Settings { Command::get_command(Command::Lstick(name.to_string())) } - /// Adds missing qubits between the first qubit and last qubit in a - /// diagram's circuit. If a missing qubit is found, a new wire is created + /// Adds missing qubits between the first qubit and last qubit in a + /// diagram's circuit. If a missing qubit is found, a new wire is created /// and pushed to the diagram's circuit. - /// + /// /// # Arguments /// `&mut BTreeMap> circuit` - the circuit of the diagram - /// + /// /// # Examples /// ``` /// use quil_rs::{Program, program::latex::{Settings, Latex}}; @@ -158,7 +152,7 @@ impl Settings { pub fn impute_missing_qubits(&self, circuit: &mut BTreeMap>) { // requires at least two qubits to impute missing qubits if circuit.len() < 2 { - return + return; } // get the first qubit in the BTreeMap @@ -179,17 +173,20 @@ impl Settings { match circuit.get(&qubit) { Some(_) => (), None => { - let wire = Wire {name: qubit, ..Default::default()}; + let wire = Wire { + name: qubit, + ..Default::default() + }; circuit.insert(qubit, Box::new(wire)); - }, + } } } } } -/// The structure of a LaTeX document. Typically a LaTeX document contains -/// metadata defining the setup and packages used in a document within a header -/// and footer while the body contains content and controls its presentation. +/// The structure of a LaTeX document. Typically a LaTeX document contains +/// metadata defining the setup and packages used in a document within a header +/// and footer while the body contains content and controls its presentation. struct Document { header: String, body: String, @@ -199,18 +196,18 @@ struct Document { // TODO: Move TikZ/Quantikz into a separate struct. Keep Document abstract enough to represent any variant of LaTeX Documents. impl Default for Document { fn default() -> Self { - Self { - header: -r"\documentclass[convert={density=300,outext=.png}]{standalone} + Self { + header: r"\documentclass[convert={density=300,outext=.png}]{standalone} \usepackage[margin=1in]{geometry} \usepackage{tikz} \usetikzlibrary{quantikz} \begin{document} -\begin{tikzcd}".to_string(), - body: "".to_string(), - footer: -r"\end{tikzcd} -\end{document}".to_string(), +\begin{tikzcd}" + .to_string(), + body: "".to_string(), + footer: r"\end{tikzcd} +\end{document}" + .to_string(), } } } @@ -221,14 +218,14 @@ impl Display for Document { } } -/// A Diagram represents a collection of wires in a circuit. It encodes the -/// wires row in the diagram and its relationship to other wires. A row is one -/// of the wires in the circuit BTreeMap. Diagram tracks relationships between -/// wires with two pieces of information--1. the wires row (its order in the -/// BTreeMap), and 2. the column that spreads between all wires that pass -/// through a multi qubit gate 'e.g. CNOT'. The size of the diagram can be -/// measured by multiplying the column with the length of the circuit. This is -/// an [m x n] matrix where each element in the matrix represents an item to be +/// A Diagram represents a collection of wires in a circuit. It encodes the +/// wires row in the diagram and its relationship to other wires. A row is one +/// of the wires in the circuit BTreeMap. Diagram tracks relationships between +/// wires with two pieces of information--1. the wires row (its order in the +/// BTreeMap), and 2. the column that spreads between all wires that pass +/// through a multi qubit gate 'e.g. CNOT'. The size of the diagram can be +/// measured by multiplying the column with the length of the circuit. This is +/// an [m x n] matrix where each element in the matrix represents an item to be /// rendered onto the diagram using one of the [`Quantikz`] commands. #[derive(Debug)] struct Diagram { @@ -244,9 +241,9 @@ struct Diagram { impl Default for Diagram { fn default() -> Self { - Self { - settings: Settings::default(), - column: 0, + Self { + settings: Settings::default(), + column: 0, relationships: HashMap::new(), circuit: BTreeMap::new(), } @@ -254,34 +251,33 @@ impl Default for Diagram { } impl Diagram { - /// For every instruction containing control and target qubits this method - /// identifies which qubit is the target and which qubit is controlling it. - /// The logic of this function is visualized using a physical vector with - /// the tail at the control qubit and the head pointing to the target - /// qubit. The distance between the qubits represents the number of wires - /// between them, i.e the space that the vector needs to traverse. If the - /// control qubit comes before the target qubit the direction is positive, - /// otherwise, it is negative. See [`Quantikz`] documentation on CNOT for + /// For every instruction containing control and target qubits this method + /// identifies which qubit is the target and which qubit is controlling it. + /// The logic of this function is visualized using a physical vector with + /// the tail at the control qubit and the head pointing to the target + /// qubit. The distance between the qubits represents the number of wires + /// between them, i.e the space that the vector needs to traverse. If the + /// control qubit comes before the target qubit the direction is positive, + /// otherwise, it is negative. See [`Quantikz`] documentation on CNOT for /// some background that helps justify this approach. - /// - /// This function is expensive with a time complexity of O(n^2). In the - /// worst case scenario every column contains a multi qubit gate with every - /// qubit as either a target or control. [`Quantikz`] uses the space - /// between wires to determine how long a line should stretch between - /// control and target qubits. Since it is impossible to determine how many - /// wires will be inserted between control and target qubits (e.g. a user - /// decides to impute missing qubits or some number of other instructions - /// are added containing qubits between them) for a custom body diagram, - /// this method can only be run after all wires are inserted into the - /// cicuit. In particular, only run this method if a Quil program contains + /// + /// This function is expensive with a time complexity of O(n^2). In the + /// worst case scenario every column contains a multi qubit gate with every + /// qubit as either a target or control. [`Quantikz`] uses the space + /// between wires to determine how long a line should stretch between + /// control and target qubits. Since it is impossible to determine how many + /// wires will be inserted between control and target qubits (e.g. a user + /// decides to impute missing qubits or some number of other instructions + /// are added containing qubits between them) for a custom body diagram, + /// this method can only be run after all wires are inserted into the + /// cicuit. In particular, only run this method if a Quil program contains /// multi qubit gates. - /// + /// /// # Arguments /// `&mut self` - self as mutible allowing to update the circuit qubits fn set_ctrl_targ(&mut self) { // ensure every column preserves the connection between ctrl and targ 'column: for c in 0..=self.column { - let mut ctrls = vec![]; // the control qubits let mut targ = None; // the targ qubit @@ -311,19 +307,18 @@ impl Diagram { // push ctrl to 'column loop ctrl variables with initial value requiring update based on targ ctrls.push(wire.name); - } + } } } } else { // no relationships found on this column, go to next continue 'column; } - // determine the physical vector where a positive vector points from control to target, negative, from target to control. The magnitude of the vector is the absolute value of the distance between them if let Some(targ) = targ { // distance between qubits is the space between the ctrl and targ qubits in the circuit - for ctrl in ctrls { + for ctrl in ctrls { // represent inclusive [open, close] brackets of a range let mut open = None; // opening qubit in range let mut close = None; // closing qubit in range @@ -334,19 +329,19 @@ impl Diagram { // get each existing qubit in the circuit if *wire.0 == ctrl || *wire.0 == targ { // if the qubit is the ctrl or target - if let Some(_) = open { - close = Some(i); - break; - - // open qubit in range not found, set open qubit - } else { - open = Some(i) - } - } + if let Some(_) = open { + close = Some(i); + break; - i += 1; + // open qubit in range not found, set open qubit + } else { + open = Some(i) + } } + i += 1; + } + let mut vector: i64 = 0; if let Some(open) = open { if let Some(close) = close { @@ -360,18 +355,20 @@ impl Diagram { } } // set wire at column as the control qubit of target qubit computed as the distance from the control qubit - self.circuit.get_mut(&ctrl).and_then(|wire| wire.ctrl.insert(c, vector)); + self.circuit + .get_mut(&ctrl) + .and_then(|wire| wire.ctrl.insert(c, vector)); } } } } /// Takes a new or existing wire and adds or updates it using the name - /// (String) as the key. If a wire exists with the same name, then the - /// contents of the new wire are added to it by updating the next column - /// using the Quantikz command associated with its attributes (e.g. gate, + /// (String) as the key. If a wire exists with the same name, then the + /// contents of the new wire are added to it by updating the next column + /// using the Quantikz command associated with its attributes (e.g. gate, /// do_nothing, etc). - /// + /// /// # Arguments /// `&mut self` - exposes HashMap> /// `wire` - the wire to be pushed or updated to in circuits @@ -390,11 +387,11 @@ impl Diagram { // add gates to wire in circuit wire_in_circuit.gates.insert(self.column, gate.to_string()); } - }, + } // no wire found insert new wire None => { self.circuit.insert(wire.name, Box::new(wire)); - }, + } } // initalize relationships between multi qubit gates @@ -409,7 +406,7 @@ impl Diagram { } else { self.relationships.insert(self.column, vec![qubit]); } - } + } } } } @@ -422,9 +419,9 @@ impl Display for Diagram { let mut body = String::from('\n'); let mut i = 0; // used to omit trailing Nr - // write the LaTeX string for each wire in the circuit + // write the LaTeX string for each wire in the circuit for key in self.circuit.keys() { - // a single line of LaTeX representing a wire from the circuit + // a single line of LaTeX representing a wire from the circuit let mut line = String::from(""); // are labels on in settings? @@ -444,8 +441,9 @@ impl Display for Diagram { if gate.starts_with('C') { if let Some(targ) = wire.ctrl.get(&c) { - line.push_str(&Command::get_command(Command::Ctrl(targ.to_string()))); - + line.push_str(&Command::get_command(Command::Ctrl( + targ.to_string(), + ))); } else if let Some(_) = wire.targ.get(&c) { line.push_str(&Command::get_command(Command::Targ)); } @@ -480,14 +478,14 @@ impl Display for Diagram { } } -/// A Wire represents a single qubit. A wire only needs to keep track of all -/// the elements it contains mapped to some arbitrary column. Diagram keeps -/// track of where the Wire belongs in the larger circuit, its row, and knows -/// how each Wire relates to each other at that column. When Diagram parses the -/// wires as a collection, if the Wire relates to another at some column, then -/// its field will be updated at that column based on the knowledge Diagram has -/// about this connection. This updated value also looks arbitrary to Wire, it -/// does not explicitly define which qubit it relates to, but a digit that +/// A Wire represents a single qubit. A wire only needs to keep track of all +/// the elements it contains mapped to some arbitrary column. Diagram keeps +/// track of where the Wire belongs in the larger circuit, its row, and knows +/// how each Wire relates to each other at that column. When Diagram parses the +/// wires as a collection, if the Wire relates to another at some column, then +/// its field will be updated at that column based on the knowledge Diagram has +/// about this connection. This updated value also looks arbitrary to Wire, it +/// does not explicitly define which qubit it relates to, but a digit that /// describes how far away it is from the related qubit based on [`Quantikz`]. #[derive(Debug)] pub struct Wire { @@ -503,9 +501,9 @@ pub struct Wire { impl Default for Wire { fn default() -> Self { - Self { - name: 0, - gates: HashMap::new(), + Self { + name: 0, + gates: HashMap::new(), ctrl: HashMap::new(), targ: HashMap::new(), } @@ -520,7 +518,7 @@ pub enum LatexGenError { pub trait Latex { /// Returns a Result containing a quil Program as a LaTeX string. - /// + /// /// # Arguments /// `settings` - Customizes the rendering of a circuit. fn to_latex(self, settings: Settings) -> Result; @@ -532,7 +530,10 @@ impl Latex for Program { let instructions = Program::to_instructions(&self, false); // store circuit strings - let mut diagram = Diagram {settings, ..Default::default()}; + let mut diagram = Diagram { + settings, + ..Default::default() + }; let mut has_ctrl_targ = false; for instruction in instructions { match instruction { @@ -551,7 +552,7 @@ impl Latex for Program { // TODO: reduce code duplication if let Some(_) = diagram.circuit.get(&qubit) { // add the gate to the wire at column 0 - wire.gates.insert(0, gate.name.clone()); + wire.gates.insert(0, gate.name.clone()); } else { if gate.name.starts_with('C') { wire.gates.insert(diagram.column, gate.name.clone()); @@ -561,17 +562,17 @@ impl Latex for Program { } } else { // add the gate to the wire at column 0 - wire.gates.insert(0, gate.name.clone()); + wire.gates.insert(0, gate.name.clone()); } } // push wire to diagram circuit diagram.push_wire(wire); - }, + } _ => (), } - } - }, + } + } // do nothing for all other instructions _ => (), } @@ -581,7 +582,7 @@ impl Latex for Program { if diagram.settings.impute_missing_qubits { // add implicit qubits to circuit diagram.settings.impute_missing_qubits(&mut diagram.circuit); - } + } // only call method for programs with control and target gates if has_ctrl_targ { @@ -590,7 +591,10 @@ impl Latex for Program { } let body = diagram.to_string(); - let document = Document {body: body, ..Default::default()}; + let document = Document { + body: body, + ..Default::default() + }; println!("{}", document.to_string()); Ok(document.to_string()) @@ -599,15 +603,15 @@ impl Latex for Program { #[cfg(test)] mod tests { - use super::{Settings, Latex}; + use super::{Latex, Settings}; use crate::Program; use std::str::FromStr; - /// Helper function takes instructions and return the LaTeX using the + /// Helper function takes instructions and return the LaTeX using the /// Latex::to_latex method. pub fn get_latex(instructions: &str, settings: Settings) -> String { - let program = Program::from_str(instructions) - .expect("program `{instructions}` should be returned"); + let program = + Program::from_str(instructions).expect("program `{instructions}` should be returned"); program .to_latex(settings) .expect("LaTeX should generate for program `{instructions}`") @@ -616,10 +620,12 @@ mod tests { #[test] /// Test functionality of to_latex using default settings. fn test_to_latex() { - let program = Program::from_str("H 5\nCNOT 5 2") - .expect("Quil program should be returned"); + let program = Program::from_str("H 5\nCNOT 5 2").expect("Quil program should be returned"); - let settings = Settings {impute_missing_qubits: true, ..Default::default()}; + let settings = Settings { + impute_missing_qubits: true, + ..Default::default() + }; program .to_latex(settings) @@ -628,7 +634,7 @@ mod tests { /// Test module for the Document mod document { - use crate::program::latex::{Document, Settings, tests::get_latex}; + use crate::program::latex::{tests::get_latex, Document, Settings}; #[test] fn test_template() { @@ -656,95 +662,64 @@ mod tests { /// Test module for gates mod gates { - use crate::program::latex::{Settings, tests::get_latex}; + use crate::program::latex::{tests::get_latex, Settings}; #[test] fn test_gate_x() { - insta::assert_snapshot!(get_latex( - "X 0", - Settings::default() - )); + insta::assert_snapshot!(get_latex("X 0", Settings::default())); } #[test] fn test_gate_y() { - insta::assert_snapshot!(get_latex( - "Y 1", - Settings::default())); + insta::assert_snapshot!(get_latex("Y 1", Settings::default())); } #[test] fn test_gates_x_and_y_single_qubit() { - insta::assert_snapshot!(get_latex( - "X 0\nY 0", - Settings::default())); + insta::assert_snapshot!(get_latex("X 0\nY 0", Settings::default())); } #[test] fn test_gates_x_and_y_two_qubits() { - insta::assert_snapshot!(get_latex( - "X 0\nY 1", - Settings::default() - )); + insta::assert_snapshot!(get_latex("X 0\nY 1", Settings::default())); } #[test] fn test_gates_cnot_ctrl_0_targ_1() { - insta::assert_snapshot!(get_latex( - "CNOT 0 1", - Settings::default())); + insta::assert_snapshot!(get_latex("CNOT 0 1", Settings::default())); } #[test] fn test_gates_cnot_ctrl_1_targ_0() { - insta::assert_snapshot!(get_latex( - "CNOT 1 0", - Settings::default())); + insta::assert_snapshot!(get_latex("CNOT 1 0", Settings::default())); } #[test] #[should_panic] fn test_gates_cnot_error_single_qubit() { - get_latex( - "CNOT 0 0", - Settings::default()); - } + get_latex("CNOT 0 0", Settings::default()); + } #[test] fn test_gates_h_and_cnot_ctrl_0_targ_1() { - insta::assert_snapshot!(get_latex( - "H 0\nCNOT 0 1", - Settings::default() - )); + insta::assert_snapshot!(get_latex("H 0\nCNOT 0 1", Settings::default())); } #[test] fn test_gates_h_and_cnot_ctrl_1_targ_0() { - insta::assert_snapshot!(get_latex( - "H 1\nCNOT 1 0", - Settings::default() - )); + insta::assert_snapshot!(get_latex("H 1\nCNOT 1 0", Settings::default())); } #[test] fn test_gate_toffoli() { - insta::assert_snapshot!(get_latex( - "CCNOT 1 2 0", - Settings::default() - )); + insta::assert_snapshot!(get_latex("CCNOT 1 2 0", Settings::default())); } #[test] fn test_gate_ccnot_and_controlled_cnot_equality() { - let ccnot = get_latex( - "CCNOT 1 2 0", - Settings::default() - ); + let ccnot = get_latex("CCNOT 1 2 0", Settings::default()); - let controlled = get_latex( - "CONTROLLED CNOT 1 2 0", - Settings::default() - ); + let controlled = get_latex("CONTROLLED CNOT 1 2 0", Settings::default()); assert_eq!(ccnot, controlled); } @@ -752,27 +727,18 @@ mod tests { /// Test module for modifiers mod modifiers { - use crate::program::latex::{Settings, tests::get_latex}; + use crate::program::latex::{tests::get_latex, Settings}; #[test] fn test_modifier_toffoli_gate() { - insta::assert_snapshot!(get_latex( - "CONTROLLED CNOT 2 1 0", - Settings::default() - )); + insta::assert_snapshot!(get_latex("CONTROLLED CNOT 2 1 0", Settings::default())); } #[test] fn test_modifier_controlled_cnot_and_ccnot_equality() { - let controlled = get_latex( - "CONTROLLED CNOT 2 1 0", - Settings::default() - ); + let controlled = get_latex("CONTROLLED CNOT 2 1 0", Settings::default()); - let ccnot = get_latex( - "CCNOT 2 1 0", - Settings::default() - ); + let ccnot = get_latex("CCNOT 2 1 0", Settings::default()); assert_eq!(controlled, ccnot); } @@ -840,7 +806,7 @@ mod tests { /// Test module for Settings mod settings { - use crate::program::latex::{Settings, tests::get_latex}; + use crate::program::latex::{tests::get_latex, Settings}; #[test] fn test_settings_label_qubit_lines_false() { @@ -848,10 +814,7 @@ mod tests { label_qubit_lines: false, ..Default::default() }; - insta::assert_snapshot!(get_latex( - "H 0\nCNOT 0 1", - settings - )); + insta::assert_snapshot!(get_latex("H 0\nCNOT 0 1", settings)); } #[test] @@ -860,10 +823,7 @@ mod tests { impute_missing_qubits: true, ..Default::default() }; - insta::assert_snapshot!(get_latex( - "H 0\nCNOT 0 3", - settings - )); + insta::assert_snapshot!(get_latex("H 0\nCNOT 0 3", settings)); } #[test] @@ -872,26 +832,20 @@ mod tests { impute_missing_qubits: true, ..Default::default() }; - insta::assert_snapshot!(get_latex( - "H 5\nCNOT 5 2", - settings - )); + insta::assert_snapshot!(get_latex("H 5\nCNOT 5 2", settings)); } } /// Test various programs for LaTeX accuracy mod programs { - use crate::program::latex::{Settings, tests::get_latex}; + use crate::program::latex::{tests::get_latex, Settings}; #[test] fn test_program_h0_cnot01_x1_cnot12() { let settings = Settings { ..Default::default() }; - insta::assert_snapshot!(get_latex( - "H 0\nCNOT 0 1\nX 1\nCNOT 1 2", - settings - )); + insta::assert_snapshot!(get_latex("H 0\nCNOT 0 1\nX 1\nCNOT 1 2", settings)); } #[test] @@ -899,10 +853,7 @@ mod tests { let settings = Settings { ..Default::default() }; - insta::assert_snapshot!(get_latex( - "H 5\nCNOT 5 2\nY 2\nCNOT 2 3", - settings - )); + insta::assert_snapshot!(get_latex("H 5\nCNOT 5 2\nY 2\nCNOT 2 3", settings)); } } -} \ No newline at end of file +} From 6f3c0c39190a708e258b269ba8c54b14ba020f81 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 10 Mar 2023 09:31:22 -0800 Subject: [PATCH 039/118] Propagate LatexGenError instead of panic. --- src/program/latex/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index ebbaecf9..cc73e9bf 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -275,7 +275,7 @@ impl Diagram { /// /// # Arguments /// `&mut self` - self as mutible allowing to update the circuit qubits - fn set_ctrl_targ(&mut self) { + fn set_ctrl_targ(&mut self) -> Result<(), LatexGenError> { // ensure every column preserves the connection between ctrl and targ 'column: for c in 0..=self.column { let mut ctrls = vec![]; // the control qubits @@ -287,7 +287,7 @@ impl Diagram { for qubit in relationship { // a relationship with one qubit is invalid if relationship.len() < 2 { - panic!("{}", LatexGenError::FoundCNOTWithNoTarget); + return Err(LatexGenError::FoundCNOTWithNoTarget); } // the last qubit is the targ @@ -361,6 +361,8 @@ impl Diagram { } } } + + Ok(()) } /// Takes a new or existing wire and adds or updates it using the name @@ -587,7 +589,7 @@ impl Latex for Program { // only call method for programs with control and target gates if has_ctrl_targ { // identify control and target qubits - diagram.set_ctrl_targ(); + diagram.set_ctrl_targ()?; } let body = diagram.to_string(); From d40808eccb767ad82c4eadacceade8e054361cc3 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 10 Mar 2023 15:28:39 -0800 Subject: [PATCH 040/118] Add PHASE, CZ, and CPHASE. --- src/program/latex/mod.rs | 135 +++++++++++++++++- ...ram__latex__tests__gates__gate_cphase.snap | 15 ++ ...program__latex__tests__gates__gate_cz.snap | 15 ++ ...tests__gates__gates_phase_pi_rotation.snap | 14 ++ 4 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_cphase.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_cz.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_phase_pi_rotation.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index cc73e9bf..38061bb9 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -25,6 +25,7 @@ use std::collections::{BTreeMap, HashMap}; use std::fmt::{format, Display}; +use crate::expression::Expression; use crate::instruction; use crate::Program; @@ -40,6 +41,8 @@ pub enum Command { Rstick(String), /// ` \gate{name}`: Make a gate on the wire. Gate(String), + /// `\phase{symbol}`: Make a phase on the wire with a rotation + Phase(String), /// `\qw`: Connect the current cell to the previous cell i.e. "do nothing". Qw, /// `\\`: Start a new row @@ -75,6 +78,7 @@ impl Command { Self::Lstick(wire) => format(format_args!(r#"\lstick{{\ket{{q_{{{wire}}}}}}}"#)), Self::Rstick(wire) => format(format_args!(r#"\rstick{{\ket{{q_{{{wire}}}}}}}"#)), Self::Gate(name) => format(format_args!(r#"\gate{{{name}}}"#)), + Self::Phase(symbol) => format(format_args!(r#"\phase{{{symbol}}}"#)), Self::Qw => r"\qw".to_string(), Self::Nr => r"\\".to_string(), Self::Meter(wire) => format(format_args!(r#"\meter{{{wire}}}"#)), @@ -87,6 +91,54 @@ impl Command { } } +#[derive(Debug)] +pub enum Symbol { + Alpha, + Beta, + Gamma, + Phi, + Pi, + Text(String), +} + +impl Symbol { + pub fn match_symbol(text: String) -> Symbol { + if text == "alpha" { + Symbol::Alpha + } else if text == "beta" { + Symbol::Beta + } else if text == "gamma" { + Symbol::Gamma + } else if text == "phi" { + Symbol::Phi + } else if text == "pi" { + Symbol::Pi + } else { + Symbol::Text(text) + } + } + /// Returns the LaTeX String for a given Symbol variant. + /// + /// # Arguments + /// `symbol` - A Symbol variant. + /// + /// # Examples + /// ``` + /// use quil_rs::program::latex::Symbol; + /// let alpha = Symbol::get_symbol(Symbol::Alpha); + /// ``` + pub fn get_symbol(symbol: &Parameter) -> String { + match symbol { + Parameter::Symbol(Symbol::Alpha) => r"\alpha".to_string(), + Parameter::Symbol(Symbol::Beta) => r"\beta".to_string(), + Parameter::Symbol(Symbol::Gamma) => r"\gamma".to_string(), + Parameter::Symbol(Symbol::Phi) => r"\phi".to_string(), + Parameter::Symbol(Symbol::Pi) => r"\pi".to_string(), + Parameter::Symbol(Symbol::Text(text)) => format(format_args!(r#"\text{{{text}}}"#)), + } + } +} + /// Settings contains the metadata that allows the user to customize how the /// circuit is rendered or use the default implementation. #[derive(Debug)] @@ -109,7 +161,7 @@ impl Default for Settings { /// Returns the default Settings. fn default() -> Self { Self { - /// false: π is pi. + /// false: pi is π. texify_numerical_constants: true, /// true: `CNOT 0 2` would have three qubit lines: 0, 1, 2. impute_missing_qubits: false, @@ -447,8 +499,33 @@ impl Display for Diagram { targ.to_string(), ))); } else if let Some(_) = wire.targ.get(&c) { - line.push_str(&Command::get_command(Command::Targ)); + // if this is a target and has a PHASE gate display `\phase{param}` + if gate.contains("PHASE") { + // set the phase parameters + if let Some(param) = wire.parameter.get(&c) { + line.push_str(&Command::get_command(Command::Phase( + Symbol::get_symbol(¶m[0]), + ))); + } + // if target has a Z gate display `gate{Z}` gate + } else if gate.contains("Z") { + line.push_str(&Command::get_command(Command::Gate( + "Z".to_string(), + ))) + // if target has a CNOT gate, display as targ{} + } else { + line.push_str(&Command::get_command(Command::Targ)); + } + } + // PHASE gates are displayed as `\phase{param}` + } else if gate.contains("PHASE") { + // set the phase parameters + if let Some(param) = wire.parameter.get(&c) { + line.push_str(&Command::get_command(Command::Phase( + Symbol::get_symbol(¶m[0]), + ))); } + // all other gates display as `\gate{name}` } else { line.push_str(&Command::get_command(Command::Gate(gate.to_string()))); } @@ -480,6 +557,11 @@ impl Display for Diagram { } } +#[derive(Debug)] +pub enum Parameter { + Symbol(Symbol), +} + /// A Wire represents a single qubit. A wire only needs to keep track of all /// the elements it contains mapped to some arbitrary column. Diagram keeps /// track of where the Wire belongs in the larger circuit, its row, and knows @@ -499,6 +581,8 @@ pub struct Wire { ctrl: HashMap, /// at this column is the wire a target? targ: HashMap, + /// any parameters that will be + parameter: HashMap>, } impl Default for Wire { @@ -508,10 +592,33 @@ impl Default for Wire { gates: HashMap::new(), ctrl: HashMap::new(), targ: HashMap::new(), + parameter: HashMap::new(), } } } +impl Wire { + /// Retrieves a gates parameters from Expression and matches them with its + /// symbolic definition which is then stored into wire at the specific + /// column. + pub fn set_param(&mut self, param: &Expression, column: u32) { + let text: String; + + match param { + Expression::Address(mr) => { + text = mr.to_string(); + } + Expression::Number(c) => { + text = c.re.to_string(); + } + expression => text = expression.to_string(), + } + + let param = vec![Parameter::Symbol(Symbol::match_symbol(text))]; + self.parameter.insert(column, param); + } +} + #[derive(thiserror::Error, Debug)] pub enum LatexGenError { #[error("Tried to parse CNOT and found a control qubit without a target.")] @@ -551,6 +658,13 @@ impl Latex for Program { // set name of wire for any qubit variant as String wire.name = qubit; + // set parameters for phase gates + if gate.name.contains("PHASE") { + for param in &gate.parameters { + wire.set_param(param, diagram.column); + } + } + // TODO: reduce code duplication if let Some(_) = diagram.circuit.get(&qubit) { // add the gate to the wire at column 0 @@ -622,7 +736,7 @@ mod tests { #[test] /// Test functionality of to_latex using default settings. fn test_to_latex() { - let program = Program::from_str("H 5\nCNOT 5 2").expect("Quil program should be returned"); + let program = Program::from_str("PHASE(pi) 0").expect("Quil program should be returned"); let settings = Settings { impute_missing_qubits: true, @@ -686,6 +800,11 @@ mod tests { insta::assert_snapshot!(get_latex("X 0\nY 1", Settings::default())); } + #[test] + fn test_gates_phase_pi_rotation() { + insta::assert_snapshot!(get_latex("PHASE(pi) 0", Settings::default())); + } + #[test] fn test_gates_cnot_ctrl_0_targ_1() { insta::assert_snapshot!(get_latex("CNOT 0 1", Settings::default())); @@ -725,6 +844,16 @@ mod tests { assert_eq!(ccnot, controlled); } + + #[test] + fn test_gate_cphase() { + insta::assert_snapshot!(get_latex("CPHASE(pi) 0 1", Settings::default())); + } + + #[test] + fn test_gate_cz() { + insta::assert_snapshot!(get_latex("CZ 0 1", Settings::default())); + } } /// Test module for modifiers diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_cphase.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_cphase.snap new file mode 100644 index 00000000..491baf03 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_cphase.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 841 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\ctrl{1} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\phase{\\pi} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \ctrl{1} & \qw \\ +\lstick{\ket{q_{1}}} & \phase{\pi} & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_cz.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_cz.snap new file mode 100644 index 00000000..414cf613 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gate_cz.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 846 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\ctrl{1} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\gate{Z} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \ctrl{1} & \qw \\ +\lstick{\ket{q_{1}}} & \gate{Z} & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_phase_pi_rotation.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_phase_pi_rotation.snap new file mode 100644 index 00000000..aed319c5 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_phase_pi_rotation.snap @@ -0,0 +1,14 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 827 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\phase{\\pi} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \phase{\pi} & \qw +\end{tikzcd} +\end{document} From 0cfb8d2a2d0caadd172c46da588e7c6850eb1961 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 10 Mar 2023 15:31:09 -0800 Subject: [PATCH 041/118] Initial docs. --- src/lib.rs | 1 + src/program/latex/mod.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index a60ee34f..1a6b4585 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ //! [constructor for timing graphs]: crate::program::graph::ScheduledProgram#method.get_dot_format //! [expressions]: crate::expression::Expression //! [instructions]: crate::instruction::Instruction +//! [latex]: crate::program::latex::Latex#method.to_latex //! [parser]: crate::program::Program#method.from_str //! [programs]: crate::program::Program //! [serializer]: crate::program::Program#method.to_string diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 38061bb9..f8049a30 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -15,11 +15,23 @@ //! control/target qubits, gates, etc. View [`Quantikz`] for the documentation //! on its usage and full set of commands. //! +//! +//! +//! TODO +//! Describe what this module does, how to use it, and what it offers and what +//! happens when it's used how it shouldn't be (What happens if they try to +//! generate programs using unimplemented gates). +//! +//! +//! +//! //! This module should be viewed as a self contained partial implementation of //! [`Quantikz`] with all available commands listed as variants in a Command //! enum. This feature provides the user variability in how they wish to render //! their Program circuits with metadata contained in a Settings struct. //! +//! View [`Quantikz`] for the documentation on its usage and full set of +//! commands. //! [`Quantikz`]: https://arxiv.org/pdf/1809.03842.pdf use std::collections::{BTreeMap, HashMap}; @@ -634,6 +646,10 @@ pub trait Latex { } impl Latex for Program { + /// + /// + /// + /// fn to_latex(self, settings: Settings) -> Result { // get a reference to the current program let instructions = Program::to_instructions(&self, false); From 6a20a5208bfddd6528b478180278226090ecd323 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 10 Mar 2023 17:23:19 -0800 Subject: [PATCH 042/118] Add DAGGER modifier. --- src/program/latex/mod.rs | 107 ++++++++++++++++-- ...latex__tests__commands__command_phase.snap | 6 + ...latex__tests__commands__command_super.snap | 6 + ...ex__tests__modifiers__modifier_dagger.snap | 14 +++ ..._tests__modifiers__modifier_dagger_cz.snap | 15 +++ ...ts__modifiers__modifier_dagger_dagger.snap | 14 +++ 6 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_phase.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_super.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_dagger.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_dagger_cz.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_dagger_dagger.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 38061bb9..c227e27a 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -43,6 +43,8 @@ pub enum Command { Gate(String), /// `\phase{symbol}`: Make a phase on the wire with a rotation Phase(String), + /// `^{\script}`: Add a superscript to a gate + Super(String), /// `\qw`: Connect the current cell to the previous cell i.e. "do nothing". Qw, /// `\\`: Start a new row @@ -79,6 +81,7 @@ impl Command { Self::Rstick(wire) => format(format_args!(r#"\rstick{{\ket{{q_{{{wire}}}}}}}"#)), Self::Gate(name) => format(format_args!(r#"\gate{{{name}}}"#)), Self::Phase(symbol) => format(format_args!(r#"\phase{{{symbol}}}"#)), + Self::Super(script) => format(format_args!(r#"^{{\{script}}}"#)), Self::Qw => r"\qw".to_string(), Self::Nr => r"\\".to_string(), Self::Meter(wire) => format(format_args!(r#"\meter{{{wire}}}"#)), @@ -440,6 +443,13 @@ impl Diagram { if let Some(gate) = wire.gates.get(&0) { // add gates to wire in circuit wire_in_circuit.gates.insert(self.column, gate.to_string()); + + // add modifiers to gate in circuit + if let Some(modifier) = wire.modifiers.get(&0) { + wire_in_circuit + .modifiers + .insert(self.column, modifier.to_vec()); + } } } // no wire found insert new wire @@ -493,6 +503,18 @@ impl Display for Diagram { if let Some(gate) = wire.gates.get(&c) { line.push_str(" & "); + let mut superscript = String::from(""); + // attach modifiers to gate name if any + if !wire.modifiers.is_empty() { + if let Some(modifiers) = wire.modifiers.get(&c) { + for modifier in modifiers { + superscript.push_str(&Command::get_command(Command::Super( + modifier.to_string(), + ))) + } + } + } + if gate.starts_with('C') { if let Some(targ) = wire.ctrl.get(&c) { line.push_str(&Command::get_command(Command::Ctrl( @@ -502,16 +524,21 @@ impl Display for Diagram { // if this is a target and has a PHASE gate display `\phase{param}` if gate.contains("PHASE") { // set the phase parameters - if let Some(param) = wire.parameter.get(&c) { + if let Some(param) = wire.parameters.get(&c) { line.push_str(&Command::get_command(Command::Phase( Symbol::get_symbol(¶m[0]), ))); } // if target has a Z gate display `gate{Z}` gate } else if gate.contains("Z") { - line.push_str(&Command::get_command(Command::Gate( - "Z".to_string(), - ))) + let mut gate = String::from("Z"); + + // concatenate superscripts + if !superscript.is_empty() { + gate.push_str(&superscript); + } + + line.push_str(&Command::get_command(Command::Gate(gate))) // if target has a CNOT gate, display as targ{} } else { line.push_str(&Command::get_command(Command::Targ)); @@ -520,14 +547,21 @@ impl Display for Diagram { // PHASE gates are displayed as `\phase{param}` } else if gate.contains("PHASE") { // set the phase parameters - if let Some(param) = wire.parameter.get(&c) { + if let Some(param) = wire.parameters.get(&c) { line.push_str(&Command::get_command(Command::Phase( Symbol::get_symbol(¶m[0]), ))); } // all other gates display as `\gate{name}` } else { - line.push_str(&Command::get_command(Command::Gate(gate.to_string()))); + let mut gate = String::from(gate); + + // concatenate superscripts + if !superscript.is_empty() { + gate.push_str(&superscript); + } + + line.push_str(&Command::get_command(Command::Gate(gate))); } } else { line.push_str(" & "); @@ -581,8 +615,10 @@ pub struct Wire { ctrl: HashMap, /// at this column is the wire a target? targ: HashMap, - /// any parameters that will be - parameter: HashMap>, + /// any parameters required at column on the wire for gates + parameters: HashMap>, + /// any modifiers added to the wire at column + modifiers: HashMap>, } impl Default for Wire { @@ -592,7 +628,8 @@ impl Default for Wire { gates: HashMap::new(), ctrl: HashMap::new(), targ: HashMap::new(), - parameter: HashMap::new(), + parameters: HashMap::new(), + modifiers: HashMap::new(), } } } @@ -615,7 +652,7 @@ impl Wire { } let param = vec![Parameter::Symbol(Symbol::match_symbol(text))]; - self.parameter.insert(column, param); + self.parameters.insert(column, param); } } @@ -665,6 +702,24 @@ impl Latex for Program { } } + // set modifers + if !gate.modifiers.is_empty() { + for modifer in &gate.modifiers { + match modifer { + instruction::GateModifier::Dagger => { + if let Some(modifiers) = wire.modifiers.get_mut(&0) + { + modifiers.push("dagger".to_string()) + } else { + wire.modifiers + .insert(0, vec!["dagger".to_string()]); + } + } + _ => (), + } + } + } + // TODO: reduce code duplication if let Some(_) = diagram.circuit.get(&qubit) { // add the gate to the wire at column 0 @@ -736,7 +791,8 @@ mod tests { #[test] /// Test functionality of to_latex using default settings. fn test_to_latex() { - let program = Program::from_str("PHASE(pi) 0").expect("Quil program should be returned"); + let program = + Program::from_str("DAGGER DAGGER X 0").expect("Quil program should be returned"); let settings = Settings { impute_missing_qubits: true, @@ -873,11 +929,26 @@ mod tests { assert_eq!(controlled, ccnot); } + + #[test] + fn test_modifier_dagger() { + insta::assert_snapshot!(get_latex("DAGGER X 0", Settings::default())); + } + + #[test] + fn test_modifier_dagger_dagger() { + insta::assert_snapshot!(get_latex("DAGGER DAGGER Y 0", Settings::default())); + } + + #[test] + fn test_modifier_dagger_cz() { + insta::assert_snapshot!(get_latex("DAGGER CZ 0 1", Settings::default())); + } } /// Test module for Quantikz Commands mod commands { - use crate::program::latex::Command; + use crate::program::latex::{Command, Parameter, Symbol}; #[test] fn test_command_left_ket() { @@ -894,6 +965,18 @@ mod tests { insta::assert_snapshot!(Command::get_command(Command::Gate("X".to_string()))); } + #[test] + fn test_command_phase() { + insta::assert_snapshot!(Command::get_command(Command::Phase(Symbol::get_symbol( + &Parameter::Symbol(Symbol::Pi) + )))); + } + + #[test] + fn test_command_super() { + insta::assert_snapshot!(Command::get_command(Command::Super("dagger".to_string()))); + } + #[test] fn test_command_qw() { insta::assert_snapshot!(Command::get_command(Command::Qw)); diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_phase.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_phase.snap new file mode 100644 index 00000000..c39e71bf --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_phase.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 903 +expression: "r#\"\\phase{\\pi}\"#" +--- +\phase{\pi} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_super.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_super.snap new file mode 100644 index 00000000..85b08907 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_super.snap @@ -0,0 +1,6 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 909 +expression: "r#\"^{\\dagger}\"#" +--- +^{\dagger} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_dagger.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_dagger.snap new file mode 100644 index 00000000..e1681412 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_dagger.snap @@ -0,0 +1,14 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 936 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{X^{\\dagger}} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \gate{X^{\dagger}} & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_dagger_cz.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_dagger_cz.snap new file mode 100644 index 00000000..70f79e41 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_dagger_cz.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 958 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\ctrl{1} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\gate{Z^{\\dagger}} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \ctrl{1} & \qw \\ +\lstick{\ket{q_{1}}} & \gate{Z^{\dagger}} & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_dagger_dagger.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_dagger_dagger.snap new file mode 100644 index 00000000..80614e75 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_dagger_dagger.snap @@ -0,0 +1,14 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 942 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{Y^{\\dagger}^{\\dagger}} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \gate{Y^{\dagger}^{\dagger}} & \qw +\end{tikzcd} +\end{document} From 5655d7e27d9a3acfab9e629b1c2c700962494139 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 13 Mar 2023 10:40:07 -0700 Subject: [PATCH 043/118] Add texify numerical constants setting. --- src/program/latex/mod.rs | 69 ++++++++++++++++--- ...ical_constants_false_supported_symbol.snap | 15 ++++ ...rical_constants_true_supported_symbol.snap | 15 ++++ 3 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_texify_numerical_constants_false_supported_symbol.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_texify_numerical_constants_true_supported_symbol.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index c227e27a..d5a66cf3 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -94,6 +94,12 @@ impl Command { } } +/// Types of parameters passed to commands. +#[derive(Debug)] +pub enum Parameter { + Symbol(Symbol), +} + #[derive(Debug)] pub enum Symbol { Alpha, @@ -591,11 +597,6 @@ impl Display for Diagram { } } -#[derive(Debug)] -pub enum Parameter { - Symbol(Symbol), -} - /// A Wire represents a single qubit. A wire only needs to keep track of all /// the elements it contains mapped to some arbitrary column. Diagram keeps /// track of where the Wire belongs in the larger circuit, its row, and knows @@ -638,12 +639,12 @@ impl Wire { /// Retrieves a gates parameters from Expression and matches them with its /// symbolic definition which is then stored into wire at the specific /// column. - pub fn set_param(&mut self, param: &Expression, column: u32) { + pub fn set_param(&mut self, param: &Expression, column: u32, texify: bool) { let text: String; match param { Expression::Address(mr) => { - text = mr.to_string(); + text = mr.name.to_string(); } Expression::Number(c) => { text = c.re.to_string(); @@ -651,7 +652,15 @@ impl Wire { expression => text = expression.to_string(), } - let param = vec![Parameter::Symbol(Symbol::match_symbol(text))]; + let param; + // if texify_numerical_constants + if texify { + // get the matching symbol from text + param = vec![Parameter::Symbol(Symbol::match_symbol(text))]; + } else { + // set the symbol as text + param = vec![Parameter::Symbol(Symbol::Text(text))]; + } self.parameters.insert(column, param); } } @@ -698,7 +707,11 @@ impl Latex for Program { // set parameters for phase gates if gate.name.contains("PHASE") { for param in &gate.parameters { - wire.set_param(param, diagram.column); + wire.set_param( + param, + diagram.column, + diagram.settings.texify_numerical_constants, + ); } } @@ -1022,6 +1035,44 @@ mod tests { mod settings { use crate::program::latex::{tests::get_latex, Settings}; + #[test] + fn test_settings_texify_numerical_constants_true_supported_symbol() { + // default texify_numerical_constants is true + let settings = Settings { + ..Default::default() + }; + insta::assert_snapshot!(get_latex("CPHASE(alpha) 0 1", settings)); + } + + #[test] + fn test_settings_texify_numerical_constants_false_supported_symbol() { + let settings = Settings { + texify_numerical_constants: false, + ..Default::default() + }; + insta::assert_snapshot!(get_latex("CPHASE(alpha) 0 1", settings)); + } + + #[test] + fn test_settings_texify_numerical_constants_unsupported_symbol() { + // default texify_numerical_constants is true + let settings_true = Settings { + ..Default::default() + }; + + let unsupported_true = get_latex("CPHASE(chi) 0 1", settings_true); + + let settings_false = Settings { + texify_numerical_constants: false, + ..Default::default() + }; + + let unsupported_false = get_latex("CPHASE(chi) 0 1", settings_false); + + // unsupported symbols are treated as text regardless of setting + assert_eq!(unsupported_true, unsupported_false); + } + #[test] fn test_settings_label_qubit_lines_false() { let settings = Settings { diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_texify_numerical_constants_false_supported_symbol.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_texify_numerical_constants_false_supported_symbol.snap new file mode 100644 index 00000000..9e8e6df9 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_texify_numerical_constants_false_supported_symbol.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 1064 +expression: "r\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\ctrl{1} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\phase{\\text{alpha}} & \\qw\n\\end{tikzcd}\n\\end{document}\"" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \ctrl{1} & \qw \\ +\lstick{\ket{q_{1}}} & \phase{\text{alpha}} & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_texify_numerical_constants_true_supported_symbol.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_texify_numerical_constants_true_supported_symbol.snap new file mode 100644 index 00000000..3e921b01 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__settings__settings_texify_numerical_constants_true_supported_symbol.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 1044 +expression: "r\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\ctrl{1} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\phase{\\alpha} & \\qw\n\\end{tikzcd}\n\\end{document}\"" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \ctrl{1} & \qw \\ +\lstick{\ket{q_{1}}} & \phase{\alpha} & \qw +\end{tikzcd} +\end{document} From 370c3ccf24a12227df420968ad118d8a7b866818 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 13 Mar 2023 11:18:14 -0700 Subject: [PATCH 044/118] Fix get_symbol doctest. --- src/program/latex/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index d5a66cf3..859de59e 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -133,8 +133,10 @@ impl Symbol { /// /// # Examples /// ``` - /// use quil_rs::program::latex::Symbol; - /// let alpha = Symbol::get_symbol(Symbol::Alpha); + /// use quil_rs::program::latex::{Parameter, Symbol}; + /// let alpha = Symbol::get_symbol( + /// &Parameter::Symbol(Symbol::Alpha) + /// ); /// ``` pub fn get_symbol(symbol: &Parameter) -> String { match symbol { From 5d9c138ddc79454343840e500f757c51bc1c81a6 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 13 Mar 2023 12:10:05 -0700 Subject: [PATCH 045/118] Initial good modifiers test. --- src/program/latex/mod.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 859de59e..b4895156 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -717,6 +717,7 @@ impl Latex for Program { } } + let mut modified_gate = None; // set modifers if !gate.modifiers.is_empty() { for modifer in &gate.modifiers { @@ -730,6 +731,11 @@ impl Latex for Program { .insert(0, vec!["dagger".to_string()]); } } + instruction::GateModifier::Controlled => { + let mut modified = String::from('C'); + modified.push_str(&gate.name); + modified_gate = Some(modified); + } _ => (), } } @@ -740,6 +746,8 @@ impl Latex for Program { // add the gate to the wire at column 0 wire.gates.insert(0, gate.name.clone()); } else { + // if let Some(modified) =` + if gate.name.starts_with('C') { wire.gates.insert(diagram.column, gate.name.clone()); @@ -959,6 +967,32 @@ mod tests { fn test_modifier_dagger_cz() { insta::assert_snapshot!(get_latex("DAGGER CZ 0 1", Settings::default())); } + + #[test] + fn test_program_good_modifiers() { + get_latex( + r#"Y 0 +CONTROLLED Y 0 1 +CONTROLLED CONTROLLED Y 0 1 2 +CONTROLLED CONTROLLED CONTROLLED Y 0 1 2 3 + +DAGGER Y 0 +DAGGER DAGGER Y 0 +DAGGER DAGGER DAGGER Y 0 + +CONTROLLED DAGGER Y 0 1 +CONTROLLED DAGGER CONTROLLED Y 0 1 2 +CONTROLLED DAGGER CONTROLLED DAGGER Y 0 1 2 + +DEFGATE G: + 1, 0 + 0, 1 + +CONTROLLED G 0 1 +DAGGER G 0"#, + Settings::default(), + ); + } } /// Test module for Quantikz Commands From a068411ef9663a06ad4b39d0813019c33d6f8f89 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 13 Mar 2023 15:34:01 -0700 Subject: [PATCH 046/118] Add multiple controlled modifiers to single qubit gates. --- src/program/latex/mod.rs | 47 ++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index b4895156..72626679 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -348,9 +348,18 @@ impl Diagram { if let Some(relationship) = self.relationships.get(&c) { // determine the control and target qubits for qubit in relationship { - // a relationship with one qubit is invalid + // check relationship validity between a gate and single qubit if relationship.len() < 2 { - return Err(LatexGenError::FoundCNOTWithNoTarget); + if let Some(wire) = self.circuit.get(qubit) { + if let Some(gate) = wire.gates.get(&c) { + // a CNOT with a single qubit is invalid + if *gate == "CNOT" { + return Err(LatexGenError::FoundCNOTWithNoTarget); + } + } else { + continue 'column; + } + } } // the last qubit is the targ @@ -537,9 +546,12 @@ impl Display for Diagram { Symbol::get_symbol(¶m[0]), ))); } - // if target has a Z gate display `gate{Z}` gate - } else if gate.contains("Z") { - let mut gate = String::from("Z"); + // if target has a CNOT gate, display as targ{} + } else if gate.contains("NOT") { + line.push_str(&Command::get_command(Command::Targ)); + // if target has a 'char' gate display `gate{char}` gate + } else { + let mut gate = String::from(gate.chars().last().unwrap()); // concatenate superscripts if !superscript.is_empty() { @@ -547,9 +559,6 @@ impl Display for Diagram { } line.push_str(&Command::get_command(Command::Gate(gate))) - // if target has a CNOT gate, display as targ{} - } else { - line.push_str(&Command::get_command(Command::Targ)); } } // PHASE gates are displayed as `\phase{param}` @@ -702,12 +711,13 @@ impl Latex for Program { instruction::Qubit::Fixed(qubit) => { // create a new wire let mut wire = Wire::default(); + let mut gate_name = gate.name.clone(); // set name of wire for any qubit variant as String wire.name = qubit; // set parameters for phase gates - if gate.name.contains("PHASE") { + if gate_name.contains("PHASE") { for param in &gate.parameters { wire.set_param( param, @@ -717,9 +727,9 @@ impl Latex for Program { } } - let mut modified_gate = None; // set modifers if !gate.modifiers.is_empty() { + let mut modified = gate_name; for modifer in &gate.modifiers { match modifer { instruction::GateModifier::Dagger => { @@ -732,31 +742,30 @@ impl Latex for Program { } } instruction::GateModifier::Controlled => { - let mut modified = String::from('C'); - modified.push_str(&gate.name); - modified_gate = Some(modified); + // prepend a C to the gate + modified.insert_str(0, "C"); } _ => (), } } + // update gate with modified name + gate_name = modified; } // TODO: reduce code duplication if let Some(_) = diagram.circuit.get(&qubit) { // add the gate to the wire at column 0 - wire.gates.insert(0, gate.name.clone()); + wire.gates.insert(0, gate_name); } else { - // if let Some(modified) =` - - if gate.name.starts_with('C') { - wire.gates.insert(diagram.column, gate.name.clone()); + if gate_name.starts_with('C') { + wire.gates.insert(diagram.column, gate_name); if !has_ctrl_targ { has_ctrl_targ = true; } } else { // add the gate to the wire at column 0 - wire.gates.insert(0, gate.name.clone()); + wire.gates.insert(0, gate_name); } } From 86a280ef04af6fa34c0e5aa29498fc5c072112d3 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 14 Mar 2023 10:27:38 -0700 Subject: [PATCH 047/118] Add good defgate and modifiers tests. --- src/program/latex/mod.rs | 35 ++++++++++++++----- ...ts__modifiers__program_good_modifiers.snap | 17 +++++++++ ...tex__tests__programs__program_defgate.snap | 15 ++++++++ 3 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__program_good_modifiers.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_defgate.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 72626679..f8556981 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -522,13 +522,11 @@ impl Display for Diagram { let mut superscript = String::from(""); // attach modifiers to gate name if any - if !wire.modifiers.is_empty() { - if let Some(modifiers) = wire.modifiers.get(&c) { - for modifier in modifiers { - superscript.push_str(&Command::get_command(Command::Super( - modifier.to_string(), - ))) - } + if let Some(modifiers) = wire.modifiers.get(&c) { + for modifier in modifiers { + superscript.push_str(&Command::get_command(Command::Super( + modifier.to_string(), + ))) } } @@ -979,7 +977,7 @@ mod tests { #[test] fn test_program_good_modifiers() { - get_latex( + let latex = get_latex( r#"Y 0 CONTROLLED Y 0 1 CONTROLLED CONTROLLED Y 0 1 2 @@ -1001,6 +999,8 @@ CONTROLLED G 0 1 DAGGER G 0"#, Settings::default(), ); + + insta::assert_snapshot!(latex); } } @@ -1165,5 +1165,24 @@ DAGGER G 0"#, }; insta::assert_snapshot!(get_latex("H 5\nCNOT 5 2\nY 2\nCNOT 2 3", settings)); } + + #[test] + fn test_program_defgate() { + let latex = get_latex( + r#"DEFGATE H0: + 0.707, 0.707 + 0.707, -0.707 + +DEFGATE H1: + 1/sqrt(2), 1/sqrt(2) + 1/sqrt(2), -1/sqrt(2) + +H0 0 +H1 1"#, + Settings::default(), + ); + + insta::assert_snapshot!(latex); + } } } diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__program_good_modifiers.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__program_good_modifiers.snap new file mode 100644 index 00000000..06daf2db --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__program_good_modifiers.snap @@ -0,0 +1,17 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 1005 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{Y} & \\ctrl{1} & \\ctrl{2} & \\ctrl{3} & \\gate{Y^{\\dagger}} & \\gate{Y^{\\dagger}^{\\dagger}} & \\gate{Y^{\\dagger}^{\\dagger}^{\\dagger}} & \\ctrl{1} & \\ctrl{2} & \\ctrl{2} & \\ctrl{1} & \\gate{G^{\\dagger}} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\qw & \\gate{Y} & \\ctrl{1} & \\ctrl{2} & \\qw & \\qw & \\qw & \\gate{Y^{\\dagger}} & \\ctrl{1} & \\ctrl{1} & \\gate{G} & \\qw & \\qw \\\\\n\\lstick{\\ket{q_{2}}} & \\qw & \\qw & \\gate{Y} & \\ctrl{1} & \\qw & \\qw & \\qw & \\qw & \\gate{Y^{\\dagger}} & \\gate{Y^{\\dagger}^{\\dagger}} & \\qw & \\qw & \\qw \\\\\n\\lstick{\\ket{q_{3}}} & \\qw & \\qw & \\qw & \\gate{Y} & \\qw & \\qw & \\qw & \\qw & \\qw & \\qw & \\qw & \\qw & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \gate{Y} & \ctrl{1} & \ctrl{2} & \ctrl{3} & \gate{Y^{\dagger}} & \gate{Y^{\dagger}^{\dagger}} & \gate{Y^{\dagger}^{\dagger}^{\dagger}} & \ctrl{1} & \ctrl{2} & \ctrl{2} & \ctrl{1} & \gate{G^{\dagger}} & \qw \\ +\lstick{\ket{q_{1}}} & \qw & \gate{Y} & \ctrl{1} & \ctrl{2} & \qw & \qw & \qw & \gate{Y^{\dagger}} & \ctrl{1} & \ctrl{1} & \gate{G} & \qw & \qw \\ +\lstick{\ket{q_{2}}} & \qw & \qw & \gate{Y} & \ctrl{1} & \qw & \qw & \qw & \qw & \gate{Y^{\dagger}} & \gate{Y^{\dagger}^{\dagger}} & \qw & \qw & \qw \\ +\lstick{\ket{q_{3}}} & \qw & \qw & \qw & \gate{Y} & \qw & \qw & \qw & \qw & \qw & \qw & \qw & \qw & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_defgate.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_defgate.snap new file mode 100644 index 00000000..dbf6ec5c --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_defgate.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 1185 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{H0} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\gate{H1} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \gate{H0} & \qw \\ +\lstick{\ket{q_{1}}} & \gate{H1} & \qw +\end{tikzcd} +\end{document} From 2338b2e45d026ef69d49b1b87efd624b04fce2d7 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 14 Mar 2023 16:56:25 -0700 Subject: [PATCH 048/118] Refactor to_latex with initial circuit of used qubits. --- src/program/latex/mod.rs | 243 ++++++++++++------ ...ests__gates__gates_x_and_y_two_qubits.snap | 9 +- ...tex__tests__programs__program_defgate.snap | 8 +- 3 files changed, 175 insertions(+), 85 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index f8556981..be882131 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -34,6 +34,7 @@ use crate::Program; /// inside `backticks`. /// Single wire commands: lstick, rstick, qw, meter /// Multi-wire commands: ctrl, targ, control, (swap, targx) +#[derive(Debug)] pub enum Command { /// `\lstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the left. Lstick(String), @@ -100,6 +101,21 @@ pub enum Parameter { Symbol(Symbol), } +impl Clone for Parameter { + fn clone(&self) -> Parameter { + match self { + Parameter::Symbol(symbol) => match symbol { + Symbol::Alpha => Parameter::Symbol(Symbol::Alpha), + Symbol::Beta => Parameter::Symbol(Symbol::Beta), + Symbol::Gamma => Parameter::Symbol(Symbol::Gamma), + Symbol::Phi => Parameter::Symbol(Symbol::Phi), + Symbol::Pi => Parameter::Symbol(Symbol::Pi), + Symbol::Text(text) => Parameter::Symbol(Symbol::Text(text.to_string())), + }, + } + } +} + #[derive(Debug)] pub enum Symbol { Alpha, @@ -212,7 +228,7 @@ impl Settings { /// }; /// program.to_latex(settings).expect(""); /// ``` - pub fn impute_missing_qubits(&self, circuit: &mut BTreeMap>) { + pub fn impute_missing_qubits(&self, column: u32, circuit: &mut BTreeMap>) { // requires at least two qubits to impute missing qubits if circuit.len() < 2 { return; @@ -236,10 +252,16 @@ impl Settings { match circuit.get(&qubit) { Some(_) => (), None => { - let wire = Wire { + let mut wire = Wire { name: qubit, ..Default::default() }; + + // insert empties based on total number of columns + for c in 0..column { + wire.empty.insert(c, Command::Qw); + } + circuit.insert(qubit, Box::new(wire)); } } @@ -348,18 +370,9 @@ impl Diagram { if let Some(relationship) = self.relationships.get(&c) { // determine the control and target qubits for qubit in relationship { - // check relationship validity between a gate and single qubit + // relationships require at least two qubits if relationship.len() < 2 { - if let Some(wire) = self.circuit.get(qubit) { - if let Some(gate) = wire.gates.get(&c) { - // a CNOT with a single qubit is invalid - if *gate == "CNOT" { - return Err(LatexGenError::FoundCNOTWithNoTarget); - } - } else { - continue 'column; - } - } + continue 'column; } // the last qubit is the targ @@ -446,33 +459,33 @@ impl Diagram { /// # Arguments /// `&mut self` - exposes HashMap> /// `wire` - the wire to be pushed or updated to in circuits - fn push_wire(&mut self, wire: Wire) { + fn push_wire(&mut self, wire: Wire) -> Result<(), LatexGenError> { let qubit = wire.name; // find wire in circuit collection match self.circuit.get_mut(&wire.name) { - // wire found, push to existing wire Some(wire_in_circuit) => { - // indicate a new item to be added by incrementing column - self.column += 1; - // get the new gate from the wire and insert into existing wire - if let Some(gate) = wire.gates.get(&0) { + if let Some(gate) = wire.gates.get(&self.column) { // add gates to wire in circuit wire_in_circuit.gates.insert(self.column, gate.to_string()); + } - // add modifiers to gate in circuit - if let Some(modifier) = wire.modifiers.get(&0) { - wire_in_circuit - .modifiers - .insert(self.column, modifier.to_vec()); - } + // add modifiers to gate in circuit + if let Some(modifier) = wire.modifiers.get(&self.column) { + wire_in_circuit + .modifiers + .insert(self.column, modifier.to_vec()); + } + + // add modifiers to gate in circuit + if let Some(parameters) = wire.parameters.get(&self.column) { + wire_in_circuit + .parameters + .insert(self.column, parameters.to_vec()); } } - // no wire found insert new wire - None => { - self.circuit.insert(wire.name, Box::new(wire)); - } + _ => (), } // initalize relationships between multi qubit gates @@ -483,6 +496,14 @@ impl Diagram { if gate.starts_with('C') { // add the qubits to the set of related qubits in the current column if let Some(qubits) = self.relationships.get_mut(&self.column) { + // ensure relationships are valid + for _self in qubits.iter() { + // qubit cannot control and target itself + if *_self == qubit { + return Err(LatexGenError::FoundCNOTWithNoTarget); + } + } + qubits.push(qubit); } else { self.relationships.insert(self.column, vec![qubit]); @@ -490,6 +511,8 @@ impl Diagram { } } } + + Ok(()) } } @@ -500,7 +523,8 @@ impl Display for Diagram { let mut body = String::from('\n'); let mut i = 0; // used to omit trailing Nr - // write the LaTeX string for each wire in the circuit + + // write the LaTeX string for each wire in the circuit for key in self.circuit.keys() { // a single line of LaTeX representing a wire from the circuit let mut line = String::from(""); @@ -516,7 +540,7 @@ impl Display for Diagram { // convert each column in the wire to string if let Some(wire) = self.circuit.get(key) { - for c in 0..=self.column { + for c in 0..self.column { if let Some(gate) = wire.gates.get(&c) { line.push_str(" & "); @@ -531,6 +555,7 @@ impl Display for Diagram { } if gate.starts_with('C') { + // set qubit at this column as the control if let Some(targ) = wire.ctrl.get(&c) { line.push_str(&Command::get_command(Command::Ctrl( targ.to_string(), @@ -539,10 +564,12 @@ impl Display for Diagram { // if this is a target and has a PHASE gate display `\phase{param}` if gate.contains("PHASE") { // set the phase parameters - if let Some(param) = wire.parameters.get(&c) { - line.push_str(&Command::get_command(Command::Phase( - Symbol::get_symbol(¶m[0]), - ))); + if let Some(parameters) = wire.parameters.get(&c) { + for param in parameters { + line.push_str(&Command::get_command(Command::Phase( + Symbol::get_symbol(param), + ))); + } } // if target has a CNOT gate, display as targ{} } else if gate.contains("NOT") { @@ -562,10 +589,12 @@ impl Display for Diagram { // PHASE gates are displayed as `\phase{param}` } else if gate.contains("PHASE") { // set the phase parameters - if let Some(param) = wire.parameters.get(&c) { - line.push_str(&Command::get_command(Command::Phase( - Symbol::get_symbol(¶m[0]), - ))); + if let Some(parameters) = wire.parameters.get(&c) { + for param in parameters { + line.push_str(&Command::get_command(Command::Phase( + Symbol::get_symbol(param), + ))); + } } // all other gates display as `\gate{name}` } else { @@ -578,7 +607,8 @@ impl Display for Diagram { line.push_str(&Command::get_command(Command::Gate(gate))); } - } else { + } else if let Some(_) = wire.empty.get(&c) { + // chain an empty column qw to the end of the line line.push_str(" & "); line.push_str(&Command::get_command(Command::Qw)); } @@ -629,6 +659,8 @@ pub struct Wire { parameters: HashMap>, /// any modifiers added to the wire at column modifiers: HashMap>, + /// empty column + empty: HashMap, } impl Default for Wire { @@ -640,6 +672,7 @@ impl Default for Wire { targ: HashMap::new(), parameters: HashMap::new(), modifiers: HashMap::new(), + empty: HashMap::new(), } } } @@ -648,10 +681,10 @@ impl Wire { /// Retrieves a gates parameters from Expression and matches them with its /// symbolic definition which is then stored into wire at the specific /// column. - pub fn set_param(&mut self, param: &Expression, column: u32, texify: bool) { + pub fn set_param(&mut self, expression: &Expression, column: u32, texify: bool) { let text: String; - match param { + match expression { Expression::Address(mr) => { text = mr.name.to_string(); } @@ -693,50 +726,100 @@ impl Latex for Program { // get a reference to the current program let instructions = Program::to_instructions(&self, false); - // store circuit strings + // initialize a new diagram let mut diagram = Diagram { settings, ..Default::default() }; + + // initialize circuit with empty wires of all qubits in program + let qubits = Program::get_used_qubits(&self); + for qubit in &qubits { + match qubit { + instruction::Qubit::Fixed(name) => { + let wire = Wire { + name: *name, + ..Default::default() + }; + diagram.circuit.insert(*name, Box::new(wire)); + } + _ => (), + } + } + + // used to left-shift wires to align columns without relationships let mut has_ctrl_targ = false; for instruction in instructions { + // TODO: Make separate function for this + // set QW for any unused qubits in this instruction + for program_qubit in &qubits { + let mut found = false; + match &instruction { + instruction::Instruction::Gate(gate) => { + for gate_qubit in &gate.qubits { + if program_qubit == gate_qubit { + found = true; + } + } + + if !found { + match program_qubit { + instruction::Qubit::Fixed(q) => { + diagram.circuit.get_mut(&q).and_then(|wire| { + wire.empty.insert(diagram.column, Command::Qw) + }); + } + _ => (), + } + } + } + _ => (), + } + } + match instruction { // parse gate instructions into a new circuit instruction::Instruction::Gate(gate) => { // for each qubit in a single gate instruction - for qubit in gate.qubits { + for qubit in &gate.qubits { match qubit { instruction::Qubit::Fixed(qubit) => { // create a new wire - let mut wire = Wire::default(); - let mut gate_name = gate.name.clone(); + let mut wire = Wire { + name: *qubit, + ..Default::default() + }; - // set name of wire for any qubit variant as String - wire.name = qubit; + // used to reformat a gate name + let mut gate_name = gate.name.clone(); // set parameters for phase gates if gate_name.contains("PHASE") { - for param in &gate.parameters { + for expression in &gate.parameters { wire.set_param( - param, + expression, diagram.column, diagram.settings.texify_numerical_constants, ); } } + // TODO: Make separate function for this // set modifers if !gate.modifiers.is_empty() { let mut modified = gate_name; for modifer in &gate.modifiers { match modifer { instruction::GateModifier::Dagger => { - if let Some(modifiers) = wire.modifiers.get_mut(&0) + if let Some(modifiers) = + wire.modifiers.get_mut(&diagram.column) { modifiers.push("dagger".to_string()) } else { - wire.modifiers - .insert(0, vec!["dagger".to_string()]); + wire.modifiers.insert( + diagram.column, + vec!["dagger".to_string()], + ); } } instruction::GateModifier::Controlled => { @@ -750,29 +833,24 @@ impl Latex for Program { gate_name = modified; } - // TODO: reduce code duplication if let Some(_) = diagram.circuit.get(&qubit) { - // add the gate to the wire at column 0 - wire.gates.insert(0, gate_name); - } else { + // has ctrl gate must identify ctrls and targ if gate_name.starts_with('C') { - wire.gates.insert(diagram.column, gate_name); - - if !has_ctrl_targ { - has_ctrl_targ = true; - } - } else { - // add the gate to the wire at column 0 - wire.gates.insert(0, gate_name); + has_ctrl_targ = true; } + + // add the gate to the wire at column 0 + wire.gates.insert(diagram.column, gate_name); } // push wire to diagram circuit - diagram.push_wire(wire); + diagram.push_wire(wire)?; } _ => (), } } + + diagram.column += 1; } // do nothing for all other instructions _ => (), @@ -782,7 +860,9 @@ impl Latex for Program { // are implicit qubits required in settings and are there at least two or more qubits in the diagram? if diagram.settings.impute_missing_qubits { // add implicit qubits to circuit - diagram.settings.impute_missing_qubits(&mut diagram.circuit); + diagram + .settings + .impute_missing_qubits(diagram.column, &mut diagram.circuit); } // only call method for programs with control and target gates @@ -821,17 +901,28 @@ mod tests { #[test] /// Test functionality of to_latex using default settings. fn test_to_latex() { - let program = - Program::from_str("DAGGER DAGGER X 0").expect("Quil program should be returned"); + get_latex( + r#"Y 0 +CONTROLLED Y 0 1 +CONTROLLED CONTROLLED Y 0 1 2 +CONTROLLED CONTROLLED CONTROLLED Y 0 1 2 3 - let settings = Settings { - impute_missing_qubits: true, - ..Default::default() - }; +DAGGER Y 0 +DAGGER DAGGER Y 0 +DAGGER DAGGER DAGGER Y 0 - program - .to_latex(settings) - .expect("LaTeX should generate for Quil program"); +CONTROLLED DAGGER Y 0 1 +CONTROLLED DAGGER CONTROLLED Y 0 1 2 +CONTROLLED DAGGER CONTROLLED DAGGER Y 0 1 2 + +DEFGATE G: + 1, 0 + 0, 1 + +CONTROLLED G 0 1 +DAGGER G 0"#, + Settings::default(), + ); } /// Test module for the Document diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_x_and_y_two_qubits.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_x_and_y_two_qubits.snap index 74cac101..f8fc7c46 100644 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_x_and_y_two_qubits.snap +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__gates__gates_x_and_y_two_qubits.snap @@ -1,7 +1,7 @@ --- source: src/program/latex/mod.rs -assertion_line: 442 -expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{X} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\gate{Y} & \\qw\n\\end{tikzcd}\n\\end{document}\n\"#" +assertion_line: 978 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{X} & \\qw & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\qw & \\gate{Y} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" --- \documentclass[convert={density=300,outext=.png}]{standalone} \usepackage[margin=1in]{geometry} @@ -9,8 +9,7 @@ expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\ \usetikzlibrary{quantikz} \begin{document} \begin{tikzcd} -\lstick{\ket{q_{0}}} & \gate{X} & \qw \\ -\lstick{\ket{q_{1}}} & \gate{Y} & \qw +\lstick{\ket{q_{0}}} & \gate{X} & \qw & \qw \\ +\lstick{\ket{q_{1}}} & \qw & \gate{Y} & \qw \end{tikzcd} \end{document} - diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_defgate.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_defgate.snap index dbf6ec5c..01fcbbcf 100644 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_defgate.snap +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_defgate.snap @@ -1,7 +1,7 @@ --- source: src/program/latex/mod.rs -assertion_line: 1185 -expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{H0} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\gate{H1} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +assertion_line: 1276 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{H0} & \\qw & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\qw & \\gate{H1} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" --- \documentclass[convert={density=300,outext=.png}]{standalone} \usepackage[margin=1in]{geometry} @@ -9,7 +9,7 @@ expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\ \usetikzlibrary{quantikz} \begin{document} \begin{tikzcd} -\lstick{\ket{q_{0}}} & \gate{H0} & \qw \\ -\lstick{\ket{q_{1}}} & \gate{H1} & \qw +\lstick{\ket{q_{0}}} & \gate{H0} & \qw & \qw \\ +\lstick{\ket{q_{1}}} & \qw & \gate{H1} & \qw \end{tikzcd} \end{document} From d31b23153be24d58946f54a3679223abe614a9ac Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 14 Mar 2023 17:59:51 -0700 Subject: [PATCH 049/118] Move to_latex code blocks to new functions and remove outdated TODOs. --- src/program/latex/mod.rs | 150 +++++++++++++++++++++++---------------- 1 file changed, 88 insertions(+), 62 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index be882131..7cfcb52f 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -22,11 +22,11 @@ //! //! [`Quantikz`]: https://arxiv.org/pdf/1809.03842.pdf -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::fmt::{format, Display}; use crate::expression::Expression; -use crate::instruction; +use crate::instruction::{self, Gate, Instruction, Qubit}; use crate::Program; /// Available commands used for building circuits with the same names taken @@ -204,7 +204,6 @@ impl Default for Settings { } } -// TODO: Implement functions to update the settings that allows the user customzie the rendering of the circuit. impl Settings { pub fn label_qubit_lines(&self, name: u64) -> String { Command::get_command(Command::Lstick(name.to_string())) @@ -278,7 +277,6 @@ struct Document { footer: String, } -// TODO: Move TikZ/Quantikz into a separate struct. Keep Document abstract enough to represent any variant of LaTeX Documents. impl Default for Document { fn default() -> Self { Self { @@ -336,6 +334,87 @@ impl Default for Diagram { } impl Diagram { + /// Compares qubits from a single instruction associated with a column on + /// the circuit to all of the qubits used in the quil program. If a qubit + /// from the quil program is not found in the qubits in the single + /// instruction line, then an empty slot is added to that column on the + /// qubit wire of the circuit. + /// + /// # Arguments + /// `&mut self` - exposes the Circuit + /// `qubits` - qubits used in the quil program + /// `instruction` - exposes qubits in a single instruction + fn set_qw(&mut self, qubits: &HashSet, instruction: &Instruction) { + for program_qubit in qubits { + let mut found = false; + match &instruction { + instruction::Instruction::Gate(gate) => { + for gate_qubit in &gate.qubits { + if program_qubit == gate_qubit { + found = true; + } + } + + if !found { + match program_qubit { + instruction::Qubit::Fixed(q) => { + self.circuit + .get_mut(&q) + .and_then(|wire| wire.empty.insert(self.column, Command::Qw)); + } + _ => (), + } + } + } + _ => (), + } + } + } + + /// Returns a reformatted gate name based on the modifiers used in a single + /// instruction line of a quil program or the original name. Gates with + /// CONTROLLED modifiers are reformatted such that each CONTROLLED modifier + /// prepends a `C` to the beginning of the gate name. For other modifiers + /// such as DAGGER, no special reformatting is required. + /// + /// For example, for an instruction line `CONTROLLED CONTROLLED Y 0 1 2` the + /// gate name is reformatted to `CCY` where each C is mapped to an + /// associated qubit with the last qubit to the original gate name. For an + /// instruction line `DAGGER DAGGER Y 0`, the gate name remains `Y`, + /// instead each of the modifiers are added to the wire at the current + /// column where it is to be applied using the Command::Super variant. + /// + /// # Arguments + /// `&mut self` - exposes the current column of the Circuit + /// `gate` - exposes the modifiers associated with the instruction gate + /// `wire` - exposes the wire to be pushed to in Circuit + fn set_modifiers(&mut self, gate: &Gate, wire: &mut Wire) -> String { + let mut gate_name = gate.name.clone(); + + // set modifers + if !gate.modifiers.is_empty() { + for modifer in &gate.modifiers { + match modifer { + instruction::GateModifier::Dagger => { + if let Some(modifiers) = wire.modifiers.get_mut(&self.column) { + modifiers.push("dagger".to_string()) + } else { + wire.modifiers + .insert(self.column, vec!["dagger".to_string()]); + } + } + instruction::GateModifier::Controlled => { + // prepend a C to the gate + gate_name.insert_str(0, "C"); + } + _ => (), + } + } + } + + gate_name + } + /// For every instruction containing control and target qubits this method /// identifies which qubit is the target and which qubit is controlling it. /// The logic of this function is visualized using a physical vector with @@ -750,32 +829,8 @@ impl Latex for Program { // used to left-shift wires to align columns without relationships let mut has_ctrl_targ = false; for instruction in instructions { - // TODO: Make separate function for this // set QW for any unused qubits in this instruction - for program_qubit in &qubits { - let mut found = false; - match &instruction { - instruction::Instruction::Gate(gate) => { - for gate_qubit in &gate.qubits { - if program_qubit == gate_qubit { - found = true; - } - } - - if !found { - match program_qubit { - instruction::Qubit::Fixed(q) => { - diagram.circuit.get_mut(&q).and_then(|wire| { - wire.empty.insert(diagram.column, Command::Qw) - }); - } - _ => (), - } - } - } - _ => (), - } - } + diagram.set_qw(&qubits, &instruction); match instruction { // parse gate instructions into a new circuit @@ -790,11 +845,8 @@ impl Latex for Program { ..Default::default() }; - // used to reformat a gate name - let mut gate_name = gate.name.clone(); - // set parameters for phase gates - if gate_name.contains("PHASE") { + if gate.name.contains("PHASE") { for expression in &gate.parameters { wire.set_param( expression, @@ -804,37 +856,11 @@ impl Latex for Program { } } - // TODO: Make separate function for this - // set modifers - if !gate.modifiers.is_empty() { - let mut modified = gate_name; - for modifer in &gate.modifiers { - match modifer { - instruction::GateModifier::Dagger => { - if let Some(modifiers) = - wire.modifiers.get_mut(&diagram.column) - { - modifiers.push("dagger".to_string()) - } else { - wire.modifiers.insert( - diagram.column, - vec!["dagger".to_string()], - ); - } - } - instruction::GateModifier::Controlled => { - // prepend a C to the gate - modified.insert_str(0, "C"); - } - _ => (), - } - } - // update gate with modified name - gate_name = modified; - } + // update the gate name based on the modifiers + let gate_name = diagram.set_modifiers(&gate, &mut wire); if let Some(_) = diagram.circuit.get(&qubit) { - // has ctrl gate must identify ctrls and targ + // has ctrl gate, must identify ctrls and targs after filling circuit if gate_name.starts_with('C') { has_ctrl_targ = true; } From 6cd43dc79238c6e6d13302aa9065d656cae69da1 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 15 Mar 2023 10:20:50 -0700 Subject: [PATCH 050/118] Test five good programs from quilc good-test-files. --- src/program/latex/mod.rs | 89 +++++++++++++------ ...programs__program_good_basic_defgate.snap} | 0 ...programs__program_good_complex_params.snap | 17 ++++ ...__program_good_defgate_with_long_name.snap | 15 ++++ ...ts__programs__program_good_modifiers.snap} | 0 ..._programs__program_good_simple_params.snap | 15 ++++ 6 files changed, 107 insertions(+), 29 deletions(-) rename src/program/latex/snapshots/{quil_rs__program__latex__tests__programs__program_defgate.snap => quil_rs__program__latex__tests__programs__program_good_basic_defgate.snap} (100%) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_complex_params.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_defgate_with_long_name.snap rename src/program/latex/snapshots/{quil_rs__program__latex__tests__modifiers__program_good_modifiers.snap => quil_rs__program__latex__tests__programs__program_good_modifiers.snap} (100%) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_simple_params.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 7cfcb52f..c5ed2c17 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -1091,34 +1091,6 @@ DAGGER G 0"#, fn test_modifier_dagger_cz() { insta::assert_snapshot!(get_latex("DAGGER CZ 0 1", Settings::default())); } - - #[test] - fn test_program_good_modifiers() { - let latex = get_latex( - r#"Y 0 -CONTROLLED Y 0 1 -CONTROLLED CONTROLLED Y 0 1 2 -CONTROLLED CONTROLLED CONTROLLED Y 0 1 2 3 - -DAGGER Y 0 -DAGGER DAGGER Y 0 -DAGGER DAGGER DAGGER Y 0 - -CONTROLLED DAGGER Y 0 1 -CONTROLLED DAGGER CONTROLLED Y 0 1 2 -CONTROLLED DAGGER CONTROLLED DAGGER Y 0 1 2 - -DEFGATE G: - 1, 0 - 0, 1 - -CONTROLLED G 0 1 -DAGGER G 0"#, - Settings::default(), - ); - - insta::assert_snapshot!(latex); - } } /// Test module for Quantikz Commands @@ -1284,7 +1256,51 @@ DAGGER G 0"#, } #[test] - fn test_program_defgate() { + fn test_program_good_modifiers() { + let latex = get_latex( + r#"Y 0 +CONTROLLED Y 0 1 +CONTROLLED CONTROLLED Y 0 1 2 +CONTROLLED CONTROLLED CONTROLLED Y 0 1 2 3 + +DAGGER Y 0 +DAGGER DAGGER Y 0 +DAGGER DAGGER DAGGER Y 0 + +CONTROLLED DAGGER Y 0 1 +CONTROLLED DAGGER CONTROLLED Y 0 1 2 +CONTROLLED DAGGER CONTROLLED DAGGER Y 0 1 2 + +DEFGATE G: + 1, 0 + 0, 1 + +CONTROLLED G 0 1 +DAGGER G 0"#, + Settings::default(), + ); + + insta::assert_snapshot!(latex); + } + + #[test] + fn test_program_good_simple_params() { + insta::assert_snapshot!(get_latex( + "CPHASE(1.0) 0 1\nCPHASE(1.0-2.0i) 1 0", + Settings::default() + )); + } + + #[test] + fn test_program_good_complex_params() { + insta::assert_snapshot!(get_latex( + "CPHASE(pi/2) 1 0\nCPHASE(cos(sin(2*pi/3))*cis(-1)*exp(i*pi)) 3 4", + Settings::default() + )); + } + + #[test] + fn test_program_good_basic_defgate() { let latex = get_latex( r#"DEFGATE H0: 0.707, 0.707 @@ -1301,5 +1317,20 @@ H1 1"#, insta::assert_snapshot!(latex); } + + #[test] + fn test_program_good_defgate_with_long_name() { + let latex = get_latex( + r#"DEFGATE ______________________________________ugly-python-convention______________________________________: + 1, 0 + 0, 1 + +______________________________________ugly-python-convention______________________________________ 0 +______________________________________ugly-python-convention______________________________________ 1"#, + Settings::default(), + ); + + insta::assert_snapshot!(latex); + } } } diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_defgate.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_basic_defgate.snap similarity index 100% rename from src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_defgate.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_basic_defgate.snap diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_complex_params.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_complex_params.snap new file mode 100644 index 00000000..15244560 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_complex_params.snap @@ -0,0 +1,17 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 1301 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\phase{\\text{(pi/2)}} & \\qw & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\ctrl{-1} & \\qw & \\qw \\\\\n\\lstick{\\ket{q_{3}}} & \\qw & \\ctrl{1} & \\qw \\\\\n\\lstick{\\ket{q_{4}}} & \\qw & \\phase{\\text{(cos(sin((2*(pi/3))))*(cis((-1))*exp((1i*pi))))}} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \phase{\text{(pi/2)}} & \qw & \qw \\ +\lstick{\ket{q_{1}}} & \ctrl{-1} & \qw & \qw \\ +\lstick{\ket{q_{3}}} & \qw & \ctrl{1} & \qw \\ +\lstick{\ket{q_{4}}} & \qw & \phase{\text{(cos(sin((2*(pi/3))))*(cis((-1))*exp((1i*pi))))}} & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_defgate_with_long_name.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_defgate_with_long_name.snap new file mode 100644 index 00000000..1d20ddad --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_defgate_with_long_name.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 1318 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\gate{______________________________________ugly-python-convention______________________________________} & \\qw & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\qw & \\gate{______________________________________ugly-python-convention______________________________________} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \gate{______________________________________ugly-python-convention______________________________________} & \qw & \qw \\ +\lstick{\ket{q_{1}}} & \qw & \gate{______________________________________ugly-python-convention______________________________________} & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__program_good_modifiers.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_modifiers.snap similarity index 100% rename from src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__program_good_modifiers.snap rename to src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_modifiers.snap diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_simple_params.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_simple_params.snap new file mode 100644 index 00000000..c80db06e --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__programs__program_good_simple_params.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 1312 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\ctrl{1} & \\phase{\\text{(1-2i)}} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\phase{\\text{1}} & \\ctrl{-1} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \ctrl{1} & \phase{\text{(1-2i)}} & \qw \\ +\lstick{\ket{q_{1}}} & \phase{\text{1}} & \ctrl{-1} & \qw +\end{tikzcd} +\end{document} From 5dfafec5581c0d6f67ab5c80dc94809a1ff067c7 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 15 Mar 2023 15:18:17 -0700 Subject: [PATCH 051/118] Add error handling for unsupported programs. --- src/program/latex/mod.rs | 221 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 216 insertions(+), 5 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index c5ed2c17..b7569dc4 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -788,10 +788,157 @@ impl Wire { #[derive(thiserror::Error, Debug)] pub enum LatexGenError { + #[error("Cannot parse LaTeX for unsupported program: {program}")] + UnsupportedProgram { program: String }, + #[error("Cannot parse LaTeX for unsupported gate: {gate}")] + UnsupportedGate { gate: String }, #[error("Tried to parse CNOT and found a control qubit without a target.")] FoundCNOTWithNoTarget, } +/// Supported types +pub enum Supported { + Gate(SupportedGate), + New, // New initialization of a non-None variant + None, // () +} + +/// Set of all Gates that can be parsed to LaTeX +#[derive(PartialEq)] +pub enum SupportedGate { + Pauli(String), + Hadamard(String), + Phase(String), + ControlledPhase(String), + ControlledX(String), + DefGate(String), + Modifiers(String), + Unsupported(String), // for error handling +} + +impl SupportedGate { + /// Returns a variant of self for any supported standard gate. + /// + /// # Arguments + /// `name` - the name of the gate from instruction + fn get_supported_standard_gate(name: String) -> Self { + if name == "I" || name == "X" || name == "Y" || name == "Z" { + return Self::Pauli(name); + } else if name == "H" { + return Self::Hadamard(name); + } else if name == "PHASE" || name == "S" || name == "T" { + return Self::Phase(name); + } else if name == "CZ" || name == "CPHASE" { + return Self::ControlledPhase(name); + } else if name == "CNOT" || name == "CCNOT" { + return Self::ControlledX(name); + } + + return Self::Unsupported(name); + } + + /// Returns a variant of self for any defined gate. + /// + /// # Arguments + /// `name` - the name of the defined gate from instruction + /// `defgate` - a vector of all previously defined gates + fn get_supported_defgate(name: String, defgate: &Vec) -> Self { + // check all previously defined DEFGATES + for defgate in defgate { + // return supported if gate name is of DEFGATE + if defgate == &name { + return SupportedGate::DefGate(name.to_string()); + } + } + + return Self::Unsupported(name); + } + + /// Returns a variant of self for any supported modifier. + /// + /// # Arguments + /// `name` - the name of the defined gate from instruction + fn get_supported_modifier(name: String) -> Self { + if name == "CONTROLLED" || name == "DAGGER" { + return Self::Modifiers(name); + } + + return Self::Unsupported(name); + } +} + +impl Supported { + /// Returns new variant of self as variant of supported gate. + /// + /// # Arguments + /// `name` - the name of the defined gate from instruction + /// `defgate` - a vector of all previously defined gates + fn new(&self, gate: &Gate, defgate: &Vec) -> Self { + // check is standard gate + let mut is_supported = SupportedGate::get_supported_standard_gate(gate.name.to_string()); + + // check if defgate if not already identified as a standard gate + if is_supported == SupportedGate::Unsupported(gate.name.to_string()) { + is_supported = SupportedGate::get_supported_defgate(gate.name.to_string(), defgate); + } + + // check supported modifers + for modifier in &gate.modifiers { + is_supported = SupportedGate::get_supported_modifier(modifier.to_string()) + } + + match self { + Supported::New => Self::Gate(is_supported), + _ => Supported::None, // same as () + } + } + + /// Returns a result indicating whether or not the LaTeX feature can parse + /// a given Program to LaTeX. + /// + /// # Arguments + /// `self` - exposes variants of a Supported program + /// `program` - the Program to be validated before parsing to LaTeX + fn is_supported(&self, program: &Program) -> Result, LatexGenError> { + let instructions = program.to_instructions(false); + + // store DEFGATE names for reference + let mut defgate: Vec = vec![]; + + // check each instruction to determine if it is supported + for instruction in &instructions { + match instruction { + // check is gate is supported + instruction::Instruction::Gate(gate) => match Self::new(self, gate, &defgate) { + // new Supported checks if this instruction is supported + Supported::Gate(is_supported) => match is_supported { + // unsupported if SupportedGate is not returned + SupportedGate::Unsupported(gate) => { + return Err(LatexGenError::UnsupportedGate { gate }) + } + // SupportedGate returned so instruction is supported + _ => (), + }, + // do nothing for non-New Self variants + _ => (), + }, + // DEFGATE is supported + instruction::Instruction::GateDefinition(gate) => { + defgate.push(gate.name.to_string()) + } + // unless explicitly matched, program is unsupported + _ => { + return Err(LatexGenError::UnsupportedProgram { + program: program.to_string(false), + }) + } + } + } + + Ok(instructions) + } +} + pub trait Latex { /// Returns a Result containing a quil Program as a LaTeX string. /// @@ -802,8 +949,8 @@ pub trait Latex { impl Latex for Program { fn to_latex(self, settings: Settings) -> Result { - // get a reference to the current program - let instructions = Program::to_instructions(&self, false); + // get a reference to the current supported program + let instructions = Supported::is_supported(&Supported::New, &self)?; // initialize a new diagram let mut diagram = Diagram { @@ -917,11 +1064,10 @@ mod tests { /// Helper function takes instructions and return the LaTeX using the /// Latex::to_latex method. pub fn get_latex(instructions: &str, settings: Settings) -> String { - let program = - Program::from_str(instructions).expect("program `{instructions}` should be returned"); + let program = Program::from_str(instructions).expect("Program should be returned"); program .to_latex(settings) - .expect("LaTeX should generate for program `{instructions}`") + .expect("LatexGenError should return for Program") } #[test] @@ -979,6 +1125,71 @@ DAGGER G 0"#, } } + /// Test module for Supported (remove #[should_panic] when supported) + mod supported { + use crate::program::latex::{tests::get_latex, Settings}; + + #[test] + #[should_panic] + fn test_supported_misc_instructions() { + get_latex("NOP\nWAIT\nRESET\nHALT", Settings::default()); + } + + #[test] + #[should_panic] + fn test_supported_measure() { + get_latex( + "DECLARE ro BIT\nMEASURE 0\nMEASURE 1 ro[0]", + Settings::default(), + ); + } + + #[test] + #[should_panic] + fn test_supported_program_defcircuit() { + get_latex( + r#"DEFCIRCUIT EULER(%alpha, %beta, %gamma) q: + RX(%alpha) q + RY(%beta) q + RZ(%gamma) q + +EULER(pi, 2*pi, 3*pi/2) 0"#, + Settings::default(), + ); + } + + #[test] + #[should_panic] + fn test_supported_gate_rotation() { + get_latex( + r#"DECLARE ro BIT[1] +DECLARE theta REAL[1] +RX(pi/2) 0 +RZ(theta) 0 +RY(-pi/2) 0"#, + Settings::default(), + ); + } + + #[test] + #[should_panic] + fn test_supported_arithmetic_instruction() { + get_latex( + "DECLARE b BIT\nDECLARE theta REAL\nMOVE theta -3.14\nLT b theta -3.14", + Settings::default(), + ); + } + + #[test] + #[should_panic] + fn test_supported_modifier_forked() { + get_latex( + "FORKED CONTROLLED FORKED RX(a,b,c,d) 0 1 2 3", + Settings::default(), + ); + } + } + /// Test module for gates mod gates { use crate::program::latex::{tests::get_latex, Settings}; From 37dbb7f0666b729b574bf113bc12eadd1cbe2e0b Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 15 Mar 2023 17:36:01 -0700 Subject: [PATCH 052/118] Update docs including supported programs. --- src/program/latex/mod.rs | 123 +++++++++++++++++++++++++-------------- 1 file changed, 80 insertions(+), 43 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index a00e9a46..f0132b89 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -1,37 +1,30 @@ -//! LaTeX diagram generation for quil programs. -//! -//! Provides a feature to generate diagrams using the LaTeX subpackage TikZ/ -//! Quantikz for a given quil Program. -//! -//! - Usage: `Program.to_latex(settings: Settings);` +//! LaTeX circuit generation for quil programs. //! //! - Description: -//! [`Quantikz`] is a subpackage in the TikZ package used to generate qubit -//! circuits. A qubit is represented as a wire separated into multiple columns. -//! Each column contains a symbol of an operation on the qubit. Multiple qubits -//! can be stacked into rows with interactions between any number of them drawn -//! as a connecting bar to each involved qubit wire. Commands are used to -//! control what is rendered on a circuit, e.g. names of qubits, identifying -//! control/target qubits, gates, etc. View [`Quantikz`] for the documentation -//! on its usage and full set of commands. -//! -//! -//! -//! TODO -//! Describe what this module does, how to use it, and what it offers and what -//! happens when it's used how it shouldn't be (What happens if they try to -//! generate programs using unimplemented gates). -//! +//! Provides a feature to generate quantum circuits using the LaTeX subpackage +//! TikZ/[`Quantikz`] for a given quil Program. This feature is callable on +//! Program (see usage below) and returns LaTeX string which can be rendered in +//! a LaTeX visualization tool. Be aware that not all Programs can be parsed to +//! LaTeX. If a Program contains a gate or modifier that has not been +//! implemented in the Supported Gates and Modifiers section below, an error +//! message will be returned detailing whether the entire Program or which line +//! of instruction containing the gate or modifier is unsupported. //! +//! Supported Gates and Modifiers +//! Pauli Gates: I, X, Y, Z +//! Hadamard Gate: H +//! Phase Gate: PHASE, S, T +//! Controlled Phase Gate: CZ, CPHASE +//! Controlled X Gates: CNOT, CCNOT +//! New Gates: DEFGATE +//! Modifiers: CONTROLLED, DAGGER //! +//! - Usage: `Program.to_latex(settings: Settings);` //! -//! This module should be viewed as a self contained partial implementation of +//! This module can be viewed as a self-contained partial implementation of //! [`Quantikz`] with all available commands listed as variants in a Command -//! enum. This feature provides the user variability in how they wish to render -//! their Program circuits with metadata contained in a Settings struct. +//! enum. View [`Quantikz`] documentation for more information. //! -//! View [`Quantikz`] for the documentation on its usage and full set of -//! commands. //! [`Quantikz`]: https://arxiv.org/pdf/1809.03842.pdf use std::collections::{BTreeMap, HashMap, HashSet}; @@ -110,6 +103,7 @@ impl Command { /// Types of parameters passed to commands. #[derive(Debug)] pub enum Parameter { + /// Symbolic parameters Symbol(Symbol), } @@ -128,6 +122,7 @@ impl Clone for Parameter { } } +/// Supported Greek and alphanumeric symbols. #[derive(Debug)] pub enum Symbol { Alpha, @@ -139,6 +134,11 @@ pub enum Symbol { } impl Symbol { + /// Returns the supported Symbol variant from text otherwise stores the + /// unsupported symbol as text in the Text variant. + /// + /// # Arguments + /// `text` - a String representing a greek or alaphanumeric symbol pub fn match_symbol(text: String) -> Symbol { if text == "alpha" { Symbol::Alpha @@ -217,6 +217,10 @@ impl Default for Settings { } impl Settings { + /// Returns a label as the qubit name to the left side of the wire. + /// + /// # Arguments + /// `name` - name of the qubit pub fn label_qubit_lines(&self, name: u64) -> String { Command::get_command(Command::Lstick(name.to_string())) } @@ -308,6 +312,7 @@ impl Default for Document { } impl Display for Document { + /// Returns the entire document in LaTeX string. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}{}{}", self.header, self.body, self.footer) } @@ -446,8 +451,7 @@ impl Diagram { /// decides to impute missing qubits or some number of other instructions /// are added containing qubits between them) for a custom body diagram, /// this method can only be run after all wires are inserted into the - /// cicuit. In particular, only run this method if a Quil program contains - /// multi qubit gates. + /// cicuit. Only run this method if a program contains multi qubit gates. /// /// # Arguments /// `&mut self` - self as mutible allowing to update the circuit qubits @@ -472,16 +476,20 @@ impl Diagram { // insert as target at this column wire.targ.insert(c, true); - // set 'column loop targ variable to this targ that control qubits will find distance from on their respective wires + // set 'column loop targ variable to this targ that + // control qubits will find distance from on their + // respective wires targ = Some(wire.name) } // all other qubits are the controls } else { if let Some(wire) = self.circuit.get_mut(qubit) { - // insert as control at this column with initial value 0, targeting themselves + // insert as control at this column with initial + // value 0, targeting themselves wire.ctrl.insert(c, 0); - // push ctrl to 'column loop ctrl variables with initial value requiring update based on targ + // push ctrl to 'column loop ctrl variables with + // initial value requiring update based on targ ctrls.push(wire.name); } } @@ -491,9 +499,13 @@ impl Diagram { continue 'column; } - // determine the physical vector where a positive vector points from control to target, negative, from target to control. The magnitude of the vector is the absolute value of the distance between them + // determine the physical vector where a positive vector points + // from control to target, negative, from target to control. The + // magnitude of the vector is the absolute value of the distance + // between them if let Some(targ) = targ { - // distance between qubits is the space between the ctrl and targ qubits in the circuit + // distance between qubits is the space between the ctrl and + // targ qubits in the circuit for ctrl in ctrls { // represent inclusive [open, close] brackets of a range let mut open = None; // opening qubit in range @@ -530,7 +542,8 @@ impl Diagram { } } } - // set wire at column as the control qubit of target qubit computed as the distance from the control qubit + // set wire at column as the control qubit of target qubit + // computed as the distance from the control qubit self.circuit .get_mut(&ctrl) .and_then(|wire| wire.ctrl.insert(c, vector)); @@ -608,7 +621,8 @@ impl Diagram { } impl Display for Diagram { - /// Converts the Diagram Circuit to LaTeX string. Returns a Result. + /// Returns a result containing the Diagram Circuit as LaTeX string which + /// can be input into the body of the Document. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // add a newline between the first line and the header let mut body = String::from('\n'); @@ -769,12 +783,19 @@ impl Default for Wire { } impl Wire { - /// Retrieves a gates parameters from Expression and matches them with its + /// Retrieves a gate's parameters from Expression and matches them with its /// symbolic definition which is then stored into wire at the specific /// column. + /// + /// # Arguments + /// `&mut self` - exposes the Wire's parameters at this column + /// `expression` - expression from Program to get name of parameter + /// `column` - the column taking the parameters + /// `texify` - is texify_numerical_constants setting on? pub fn set_param(&mut self, expression: &Expression, column: u32, texify: bool) { let text: String; + // get the name of the supported expression match expression { Expression::Address(mr) => { text = mr.name.to_string(); @@ -952,18 +973,34 @@ impl Supported { } pub trait Latex { - /// Returns a Result containing a quil Program as a LaTeX string. - /// - /// # Arguments - /// `settings` - Customizes the rendering of a circuit. fn to_latex(self, settings: Settings) -> Result; } impl Latex for Program { + /// Main function of LaTeX feature, returns a Result containing a quil + /// Program as a LaTeX string or an Error. Called on a Program, the + /// function starts with a check to ensure the Program contains gates and + /// modifers that are implemented and can therefore be parsed to LaTeX. /// + /// # Arguments + /// `settings` - Customizes the rendering of a circuit. /// + /// # Examples + /// ``` + /// // To LaTeX for the Bell State Program. + /// use quil_rs::{Program, program::latex::{Settings, Latex}}; + /// use std::str::FromStr; + /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); + /// let latex = program.to_latex(Settings::default()).expect(""); + /// ``` /// - /// + /// ``` + /// // To LaTeX for the Toffoli Gate Program. + /// use quil_rs::{Program, program::latex::{Settings, Latex}}; + /// use std::str::FromStr; + /// let program = Program::from_str("CONTROLLED CNOT 2 1 0").expect(""); + /// let latex = program.to_latex(Settings::default()).expect(""); + /// ``` fn to_latex(self, settings: Settings) -> Result { // get a reference to the current supported program let instructions = Supported::is_supported(&Supported::New, &self)?; @@ -989,7 +1026,7 @@ impl Latex for Program { } } - // used to left-shift wires to align columns without relationships + // ensures set_ctrl_targ is called only if program has controlled gates let mut has_ctrl_targ = false; for instruction in instructions { // set QW for any unused qubits in this instruction From 645b7878fd3e620f356d40160e0efcd957f2533d Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 16 Mar 2023 10:13:39 -0700 Subject: [PATCH 053/118] Tighten visibility of crate. --- src/program/latex/mod.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index f0132b89..5893852d 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -40,7 +40,7 @@ use crate::Program; /// Single wire commands: lstick, rstick, qw, meter /// Multi-wire commands: ctrl, targ, control, (swap, targx) #[derive(Debug)] -pub enum Command { +enum Command { /// `\lstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the left. Lstick(String), /// `\rstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the right. @@ -81,7 +81,7 @@ impl Command { /// let ket_0 = "0".to_string(); /// let lstick_ket_0 = Command::get_command(Command::Lstick(ket_0)); /// ``` - pub fn get_command(command: Self) -> String { + fn get_command(command: Self) -> String { match command { Self::Lstick(wire) => format(format_args!(r#"\lstick{{\ket{{q_{{{wire}}}}}}}"#)), Self::Rstick(wire) => format(format_args!(r#"\rstick{{\ket{{q_{{{wire}}}}}}}"#)), @@ -102,7 +102,7 @@ impl Command { /// Types of parameters passed to commands. #[derive(Debug)] -pub enum Parameter { +enum Parameter { /// Symbolic parameters Symbol(Symbol), } @@ -124,7 +124,7 @@ impl Clone for Parameter { /// Supported Greek and alphanumeric symbols. #[derive(Debug)] -pub enum Symbol { +enum Symbol { Alpha, Beta, Gamma, @@ -139,7 +139,7 @@ impl Symbol { /// /// # Arguments /// `text` - a String representing a greek or alaphanumeric symbol - pub fn match_symbol(text: String) -> Symbol { + fn match_symbol(text: String) -> Symbol { if text == "alpha" { Symbol::Alpha } else if text == "beta" { @@ -166,7 +166,7 @@ impl Symbol { /// &Parameter::Symbol(Symbol::Alpha) /// ); /// ``` - pub fn get_symbol(symbol: &Parameter) -> String { + fn get_symbol(symbol: &Parameter) -> String { match symbol { Parameter::Symbol(Symbol::Alpha) => r"\alpha".to_string(), Parameter::Symbol(Symbol::Beta) => r"\beta".to_string(), @@ -221,7 +221,7 @@ impl Settings { /// /// # Arguments /// `name` - name of the qubit - pub fn label_qubit_lines(&self, name: u64) -> String { + fn label_qubit_lines(&self, name: u64) -> String { Command::get_command(Command::Lstick(name.to_string())) } @@ -243,7 +243,7 @@ impl Settings { /// }; /// program.to_latex(settings).expect(""); /// ``` - pub fn impute_missing_qubits(&self, column: u32, circuit: &mut BTreeMap>) { + fn impute_missing_qubits(&self, column: u32, circuit: &mut BTreeMap>) { // requires at least two qubits to impute missing qubits if circuit.len() < 2 { return; @@ -751,7 +751,7 @@ impl Display for Diagram { /// does not explicitly define which qubit it relates to, but a digit that /// describes how far away it is from the related qubit based on [`Quantikz`]. #[derive(Debug)] -pub struct Wire { +struct Wire { /// the name of ket(qubit) placed using the Lstick or Rstick commands name: u64, /// gate elements placed at column on wire using the Gate command @@ -792,7 +792,7 @@ impl Wire { /// `expression` - expression from Program to get name of parameter /// `column` - the column taking the parameters /// `texify` - is texify_numerical_constants setting on? - pub fn set_param(&mut self, expression: &Expression, column: u32, texify: bool) { + fn set_param(&mut self, expression: &Expression, column: u32, texify: bool) { let text: String; // get the name of the supported expression @@ -830,7 +830,7 @@ pub enum LatexGenError { } /// Supported types -pub enum Supported { +enum Supported { Gate(SupportedGate), New, // New initialization of a non-None variant None, // () @@ -838,7 +838,7 @@ pub enum Supported { /// Set of all Gates that can be parsed to LaTeX #[derive(PartialEq)] -pub enum SupportedGate { +enum SupportedGate { Pauli(String), Hadamard(String), Phase(String), @@ -1116,7 +1116,7 @@ mod tests { /// Helper function takes instructions and return the LaTeX using the /// Latex::to_latex method. - pub fn get_latex(instructions: &str, settings: Settings) -> String { + fn get_latex(instructions: &str, settings: Settings) -> String { let program = Program::from_str(instructions).expect("Program should be returned"); program .to_latex(settings) From 5d7940c8f579f005859058554799ed4abd4e5738 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 16 Mar 2023 10:57:58 -0700 Subject: [PATCH 054/118] Fix docs and remove deadcode. --- src/program/latex/mod.rs | 57 +------------------ ...ests__commands__command_cphase_target.snap | 6 -- ...tex__tests__commands__command_measure.snap | 6 -- ...x__tests__commands__command_right_ket.snap | 6 -- ..._latex__tests__commands__command_swap.snap | 6 -- ..._tests__commands__command_swap_target.snap | 6 -- 6 files changed, 1 insertion(+), 86 deletions(-) delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_cphase_target.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_measure.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_right_ket.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap.snap delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap_target.snap diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 5893852d..5c4f7b61 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -43,9 +43,7 @@ use crate::Program; enum Command { /// `\lstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the left. Lstick(String), - /// `\rstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the right. - Rstick(String), - /// ` \gate{name}`: Make a gate on the wire. + /// `\gate{name}`: Make a gate on the wire. Gate(String), /// `\phase{symbol}`: Make a phase on the wire with a rotation Phase(String), @@ -55,18 +53,10 @@ enum Command { Qw, /// `\\`: Start a new row Nr, - /// `\meter{wire}`: Measure a qubit. - Meter(String), /// `\ctrl{wire}`: Make a control qubit--different from Control. Ctrl(String), /// `\targ{}`: Make a controlled-not gate. Targ, - /// `\control{}`: Make a controlled-phase gate--different from Ctrl. - Control, - /// `\swap{wire}`: Make a swap gate--used with TargX. - Swap(String), - /// `\targX{}`: Make a qubit the target for a swap--used with Swap. - TargX, } impl Command { @@ -74,28 +64,16 @@ impl Command { /// /// # Arguments /// `command` - A Command variant. - /// - /// # Examples - /// ``` - /// use quil_rs::program::latex::Command; - /// let ket_0 = "0".to_string(); - /// let lstick_ket_0 = Command::get_command(Command::Lstick(ket_0)); - /// ``` fn get_command(command: Self) -> String { match command { Self::Lstick(wire) => format(format_args!(r#"\lstick{{\ket{{q_{{{wire}}}}}}}"#)), - Self::Rstick(wire) => format(format_args!(r#"\rstick{{\ket{{q_{{{wire}}}}}}}"#)), Self::Gate(name) => format(format_args!(r#"\gate{{{name}}}"#)), Self::Phase(symbol) => format(format_args!(r#"\phase{{{symbol}}}"#)), Self::Super(script) => format(format_args!(r#"^{{\{script}}}"#)), Self::Qw => r"\qw".to_string(), Self::Nr => r"\\".to_string(), - Self::Meter(wire) => format(format_args!(r#"\meter{{{wire}}}"#)), Self::Ctrl(wire) => format(format_args!(r#"\ctrl{{{wire}}}"#)), Self::Targ => r"\targ{}".to_string(), - Self::Control => r"\control{}".to_string(), - Self::Swap(wire) => format(format_args!(r#"\swap{{{wire}}}"#)), - Self::TargX => r"\targX{}".to_string(), } } } @@ -158,14 +136,6 @@ impl Symbol { /// /// # Arguments /// `symbol` - A Symbol variant. - /// - /// # Examples - /// ``` - /// use quil_rs::program::latex::{Parameter, Symbol}; - /// let alpha = Symbol::get_symbol( - /// &Parameter::Symbol(Symbol::Alpha) - /// ); - /// ``` fn get_symbol(symbol: &Parameter) -> String { match symbol { Parameter::Symbol(Symbol::Alpha) => r"\alpha".to_string(), @@ -1366,11 +1336,6 @@ RY(-pi/2) 0"#, insta::assert_snapshot!(Command::get_command(Command::Lstick("0".to_string()))); } - #[test] - fn test_command_right_ket() { - insta::assert_snapshot!(Command::get_command(Command::Rstick("0".to_string()))); - } - #[test] fn test_command_gate() { insta::assert_snapshot!(Command::get_command(Command::Gate("X".to_string()))); @@ -1398,11 +1363,6 @@ RY(-pi/2) 0"#, insta::assert_snapshot!(Command::get_command(Command::Nr)); } - #[test] - fn test_command_measure() { - insta::assert_snapshot!(Command::get_command(Command::Meter("0".to_string()))); - } - #[test] fn test_command_control() { insta::assert_snapshot!(Command::get_command(Command::Ctrl("0".to_string()))); @@ -1412,21 +1372,6 @@ RY(-pi/2) 0"#, fn test_command_cnot_target() { insta::assert_snapshot!(Command::get_command(Command::Targ)); } - - #[test] - fn test_command_cphase_target() { - insta::assert_snapshot!(Command::get_command(Command::Control)); - } - - #[test] - fn test_command_swap() { - insta::assert_snapshot!(Command::get_command(Command::Swap("0".to_string()))); - } - - #[test] - fn test_command_swap_target() { - insta::assert_snapshot!(Command::get_command(Command::TargX)); - } } /// Test module for Settings diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_cphase_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_cphase_target.snap deleted file mode 100644 index bad633b8..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_cphase_target.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/mod.rs -assertion_line: 218 -expression: "Command::get_command(Command::Control)" ---- -\control{} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_measure.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_measure.snap deleted file mode 100644 index de386033..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_measure.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/mod.rs -assertion_line: 203 -expression: "Command::get_command(Command::Meter(0))" ---- -\meter{0} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_right_ket.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_right_ket.snap deleted file mode 100644 index a7d2e849..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_right_ket.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/mod.rs -assertion_line: 193 -expression: "Command::get_command(Command::Rstick(0))" ---- -\rstick{\ket{q_{0}}} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap.snap deleted file mode 100644 index 35ba9abb..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/mod.rs -assertion_line: 223 -expression: "Command::get_command(Command::Swap(0))" ---- -\swap{0} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap_target.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap_target.snap deleted file mode 100644 index a45cb22a..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__commands__command_swap_target.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/program/latex/mod.rs -assertion_line: 228 -expression: "Command::get_command(Command::TargX)" ---- -\targX{} From 11cd5c57a441525fc284ef2bdb134736b28af872 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 16 Mar 2023 11:03:34 -0700 Subject: [PATCH 055/118] Replace explicit Clone impl for Parameter with derived trait. --- src/program/latex/mod.rs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 5c4f7b61..d7052a6a 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -79,29 +79,14 @@ impl Command { } /// Types of parameters passed to commands. -#[derive(Debug)] +#[derive(Debug, Clone)] enum Parameter { /// Symbolic parameters Symbol(Symbol), } -impl Clone for Parameter { - fn clone(&self) -> Parameter { - match self { - Parameter::Symbol(symbol) => match symbol { - Symbol::Alpha => Parameter::Symbol(Symbol::Alpha), - Symbol::Beta => Parameter::Symbol(Symbol::Beta), - Symbol::Gamma => Parameter::Symbol(Symbol::Gamma), - Symbol::Phi => Parameter::Symbol(Symbol::Phi), - Symbol::Pi => Parameter::Symbol(Symbol::Pi), - Symbol::Text(text) => Parameter::Symbol(Symbol::Text(text.to_string())), - }, - } - } -} - /// Supported Greek and alphanumeric symbols. -#[derive(Debug)] +#[derive(Debug, Clone)] enum Symbol { Alpha, Beta, From 5dbaaad359b8e057d7abe823c178b3bb527db289 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 16 Mar 2023 11:52:12 -0700 Subject: [PATCH 056/118] Refactor get_symbol into ToString for Symbol and Parameter. --- src/program/latex/mod.rs | 64 ++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index d7052a6a..03941a42 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -85,6 +85,14 @@ enum Parameter { Symbol(Symbol), } +impl ToString for Parameter { + fn to_string(&self) -> String { + match self { + Parameter::Symbol(symbol) => Symbol::to_string(symbol), + } + } +} + /// Supported Greek and alphanumeric symbols. #[derive(Debug, Clone)] enum Symbol { @@ -96,6 +104,19 @@ enum Symbol { Text(String), } +impl ToString for Symbol { + fn to_string(&self) -> String { + match self { + Symbol::Alpha => r"\alpha".to_string(), + Symbol::Beta => r"\beta".to_string(), + Symbol::Gamma => r"\gamma".to_string(), + Symbol::Phi => r"\phi".to_string(), + Symbol::Pi => r"\pi".to_string(), + Symbol::Text(text) => format(format_args!(r#"\text{{{text}}}"#)), + } + } +} + impl Symbol { /// Returns the supported Symbol variant from text otherwise stores the /// unsupported symbol as text in the Text variant. @@ -103,32 +124,13 @@ impl Symbol { /// # Arguments /// `text` - a String representing a greek or alaphanumeric symbol fn match_symbol(text: String) -> Symbol { - if text == "alpha" { - Symbol::Alpha - } else if text == "beta" { - Symbol::Beta - } else if text == "gamma" { - Symbol::Gamma - } else if text == "phi" { - Symbol::Phi - } else if text == "pi" { - Symbol::Pi - } else { - Symbol::Text(text) - } - } - /// Returns the LaTeX String for a given Symbol variant. - /// - /// # Arguments - /// `symbol` - A Symbol variant. - fn get_symbol(symbol: &Parameter) -> String { - match symbol { - Parameter::Symbol(Symbol::Alpha) => r"\alpha".to_string(), - Parameter::Symbol(Symbol::Beta) => r"\beta".to_string(), - Parameter::Symbol(Symbol::Gamma) => r"\gamma".to_string(), - Parameter::Symbol(Symbol::Phi) => r"\phi".to_string(), - Parameter::Symbol(Symbol::Pi) => r"\pi".to_string(), - Parameter::Symbol(Symbol::Text(text)) => format(format_args!(r#"\text{{{text}}}"#)), + match text.as_str() { + "alpha" => Symbol::Alpha, + "beta" => Symbol::Beta, + "gamma" => Symbol::Gamma, + "phi" => Symbol::Phi, + "pi" => Symbol::Pi, + _ => Symbol::Text(text), } } } @@ -627,7 +629,7 @@ impl Display for Diagram { if let Some(parameters) = wire.parameters.get(&c) { for param in parameters { line.push_str(&Command::get_command(Command::Phase( - Symbol::get_symbol(param), + param.to_string(), ))); } } @@ -652,7 +654,7 @@ impl Display for Diagram { if let Some(parameters) = wire.parameters.get(&c) { for param in parameters { line.push_str(&Command::get_command(Command::Phase( - Symbol::get_symbol(param), + param.to_string(), ))); } } @@ -1314,7 +1316,7 @@ RY(-pi/2) 0"#, /// Test module for Quantikz Commands mod commands { - use crate::program::latex::{Command, Parameter, Symbol}; + use crate::program::latex::{Command, Symbol}; #[test] fn test_command_left_ket() { @@ -1328,9 +1330,7 @@ RY(-pi/2) 0"#, #[test] fn test_command_phase() { - insta::assert_snapshot!(Command::get_command(Command::Phase(Symbol::get_symbol( - &Parameter::Symbol(Symbol::Pi) - )))); + insta::assert_snapshot!(Command::get_command(Command::Phase(Symbol::Pi.to_string()))); } #[test] From 0b9213d43fb92ab02c979c7001a4d704e199b6e5 Mon Sep 17 00:00:00 2001 From: Ryan Meneses <58452495+hiyaryan@users.noreply.github.com> Date: Thu, 16 Mar 2023 13:15:01 -0700 Subject: [PATCH 057/118] Apply docs suggestions from code review. Co-authored-by: Michael Bryant --- src/program/latex/mod.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 03941a42..d84a4d7e 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -1,23 +1,23 @@ //! LaTeX circuit generation for quil programs. //! -//! - Description: -//! Provides a feature to generate quantum circuits using the LaTeX subpackage -//! TikZ/[`Quantikz`] for a given quil Program. This feature is callable on -//! Program (see usage below) and returns LaTeX string which can be rendered in -//! a LaTeX visualization tool. Be aware that not all Programs can be parsed to -//! LaTeX. If a Program contains a gate or modifier that has not been -//! implemented in the Supported Gates and Modifiers section below, an error -//! message will be returned detailing whether the entire Program or which line +//! This module enables generating quantum circuits using the LaTeX subpackage +//! TikZ/[`Quantikz`] for a given quil [`Program`]. This feature is callable on +//! [`Program`] (see usage below) and returns a LaTeX string which can be rendered in +//! a LaTeX visualization tool. Be aware that not all Programs can be serialized as +//! LaTeX. If a [`Program`] contains a gate or modifier that has not been +//! implemented in the [Supported Gates and Modifiers](#supported-gates-and-modifiers) +//! section below, an error will be returned detailing whether the entire [`Program`] or which line //! of instruction containing the gate or modifier is unsupported. //! -//! Supported Gates and Modifiers -//! Pauli Gates: I, X, Y, Z -//! Hadamard Gate: H -//! Phase Gate: PHASE, S, T -//! Controlled Phase Gate: CZ, CPHASE -//! Controlled X Gates: CNOT, CCNOT -//! New Gates: DEFGATE -//! Modifiers: CONTROLLED, DAGGER +//! # Supported Gates and Modifiers +//! +//! - Pauli Gates: `I`, `X`, `Y`, `Z` +//! - Hadamard Gate: `H` +//! - Phase Gate: `PHASE`, `S`, `T` +//! - Controlled Phase Gate: `CZ`, `CPHASE` +//! - Controlled X Gates: `CNOT`, `CCNOT` +//! - User-Defined Gates: `DEFGATE` +//! - Modifiers: `CONTROLLED`, `DAGGER` //! //! - Usage: `Program.to_latex(settings: Settings);` //! @@ -1077,7 +1077,7 @@ mod tests { let program = Program::from_str(instructions).expect("Program should be returned"); program .to_latex(settings) - .expect("LatexGenError should return for Program") + .expect("Program conversion to LaTeX should succeed") } #[test] From 10fe4f3a9dc2d083405d39284839c9d9b3fea8f4 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 16 Mar 2023 14:04:31 -0700 Subject: [PATCH 058/118] Fix Quantikz link in documentation. --- src/program/latex/mod.rs | 72 +++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index d84a4d7e..b75f43db 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -2,28 +2,23 @@ //! //! This module enables generating quantum circuits using the LaTeX subpackage //! TikZ/[`Quantikz`] for a given quil [`Program`]. This feature is callable on -//! [`Program`] (see usage below) and returns a LaTeX string which can be rendered in -//! a LaTeX visualization tool. Be aware that not all Programs can be serialized as -//! LaTeX. If a [`Program`] contains a gate or modifier that has not been -//! implemented in the [Supported Gates and Modifiers](#supported-gates-and-modifiers) -//! section below, an error will be returned detailing whether the entire [`Program`] or which line -//! of instruction containing the gate or modifier is unsupported. +//! [`Program`] (see usage below) and returns a LaTeX string which can be +//! rendered in a LaTeX visualization tool. Be aware that not all Programs can +//! be serialized as LaTeX. If a [`Program`] contains a gate or modifier that +//! has not been implemented in the [Supported Gates and Modifiers] +//! (#supported-gates-and-modifiers) section below, an error will be returned +//! detailing whether the entire [`Program`] or which line of instruction +//! containing the gate or modifier is unsupported. //! //! # Supported Gates and Modifiers //! -//! - Pauli Gates: `I`, `X`, `Y`, `Z` -//! - Hadamard Gate: `H` -//! - Phase Gate: `PHASE`, `S`, `T` -//! - Controlled Phase Gate: `CZ`, `CPHASE` -//! - Controlled X Gates: `CNOT`, `CCNOT` -//! - User-Defined Gates: `DEFGATE` -//! - Modifiers: `CONTROLLED`, `DAGGER` -//! -//! - Usage: `Program.to_latex(settings: Settings);` -//! -//! This module can be viewed as a self-contained partial implementation of -//! [`Quantikz`] with all available commands listed as variants in a Command -//! enum. View [`Quantikz`] documentation for more information. +//! - Pauli Gates: `I`, `X`, `Y`, `Z` +//! - Hadamard Gate: `H` +//! - Phase Gate: `PHASE`, `S`, `T` +//! - Controlled Phase Gate: `CZ`, `CPHASE` +//! - Controlled X Gates: `CNOT`, `CCNOT` +//! - User-Defined Gates: `DEFGATE` +//! - Modifiers: `CONTROLLED`, `DAGGER` //! //! [`Quantikz`]: https://arxiv.org/pdf/1809.03842.pdf @@ -283,7 +278,7 @@ impl Display for Document { /// through a multi qubit gate 'e.g. CNOT'. The size of the diagram can be /// measured by multiplying the column with the length of the circuit. This is /// an [m x n] matrix where each element in the matrix represents an item to be -/// rendered onto the diagram using one of the [`Quantikz`] commands. +/// rendered onto the diagram using one of the Quantikz commands. #[derive(Debug)] struct Diagram { /// customizes how the diagram renders the circuit @@ -396,19 +391,19 @@ impl Diagram { /// qubit. The distance between the qubits represents the number of wires /// between them, i.e the space that the vector needs to traverse. If the /// control qubit comes before the target qubit the direction is positive, - /// otherwise, it is negative. See [`Quantikz`] documentation on CNOT for - /// some background that helps justify this approach. + /// otherwise, it is negative. See Quantikz documentation on CNOT for some + /// background that helps justify this approach. /// /// This function is expensive with a time complexity of O(n^2). In the /// worst case scenario every column contains a multi qubit gate with every - /// qubit as either a target or control. [`Quantikz`] uses the space - /// between wires to determine how long a line should stretch between - /// control and target qubits. Since it is impossible to determine how many - /// wires will be inserted between control and target qubits (e.g. a user - /// decides to impute missing qubits or some number of other instructions - /// are added containing qubits between them) for a custom body diagram, - /// this method can only be run after all wires are inserted into the - /// cicuit. Only run this method if a program contains multi qubit gates. + /// qubit as either a target or control. Quantikz uses the space between + /// wires to determine how long a line should stretch between control and + /// target qubits. Since it is impossible to determine how many wires will + /// be inserted between control and target qubits (e.g. a user decides to + /// impute missing qubits or some number of other instructions are added + /// containing qubits between them) for a custom body diagram, this method + /// can only be run after all wires are inserted into the cicuit. Only run + /// this method if a program contains multi qubit gates. /// /// # Arguments /// `&mut self` - self as mutible allowing to update the circuit qubits @@ -706,7 +701,7 @@ impl Display for Diagram { /// its field will be updated at that column based on the knowledge Diagram has /// about this connection. This updated value also looks arbitrary to Wire, it /// does not explicitly define which qubit it relates to, but a digit that -/// describes how far away it is from the related qubit based on [`Quantikz`]. +/// describes how far away it is from the related qubit based on Quantikz. #[derive(Debug)] struct Wire { /// the name of ket(qubit) placed using the Lstick or Rstick commands @@ -934,10 +929,15 @@ pub trait Latex { } impl Latex for Program { - /// Main function of LaTeX feature, returns a Result containing a quil - /// Program as a LaTeX string or an Error. Called on a Program, the - /// function starts with a check to ensure the Program contains gates and - /// modifers that are implemented and can therefore be parsed to LaTeX. + /// This implementation of Latex can be viewed as a self-contained partial + /// implementation of ``Quantikz`` with all available commands listed as + /// variants in a Command enum. View ``Quantikz`` documentation for more + /// information. + /// + /// This function returns a Result containing a quil [`Program`] as a LaTeX + /// string or a [`LatexGenError`] defined using thiserror. Called on a + /// Program, the function starts with a check to ensure the [`Program`] + /// contains supported gates and modifers that can be serialized to LaTeX. /// /// # Arguments /// `settings` - Customizes the rendering of a circuit. @@ -1059,8 +1059,6 @@ impl Latex for Program { body: body, ..Default::default() }; - println!("{}", document.to_string()); - Ok(document.to_string()) } } From 025f7fa9f3f24a362b3fcb708c576ffd9a8f1896 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 16 Mar 2023 16:45:14 -0700 Subject: [PATCH 059/118] Add clippy fixes. --- src/program/latex/mod.rs | 96 ++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 63 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index d84a4d7e..30b4e13c 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -28,7 +28,7 @@ //! [`Quantikz`]: https://arxiv.org/pdf/1809.03842.pdf use std::collections::{BTreeMap, HashMap, HashSet}; -use std::fmt::{format, Display}; +use std::fmt::Display; use crate::expression::Expression; use crate::instruction::{self, Gate, Instruction, Qubit}; @@ -66,13 +66,13 @@ impl Command { /// `command` - A Command variant. fn get_command(command: Self) -> String { match command { - Self::Lstick(wire) => format(format_args!(r#"\lstick{{\ket{{q_{{{wire}}}}}}}"#)), - Self::Gate(name) => format(format_args!(r#"\gate{{{name}}}"#)), - Self::Phase(symbol) => format(format_args!(r#"\phase{{{symbol}}}"#)), - Self::Super(script) => format(format_args!(r#"^{{\{script}}}"#)), + Self::Lstick(wire) => format!(r#"\lstick{{\ket{{q_{{{wire}}}}}}}"#), + Self::Gate(name) => format!(r#"\gate{{{name}}}"#), + Self::Phase(symbol) => format!(r#"\phase{{{symbol}}}"#), + Self::Super(script) => format!(r#"^{{\{script}}}"#), Self::Qw => r"\qw".to_string(), Self::Nr => r"\\".to_string(), - Self::Ctrl(wire) => format(format_args!(r#"\ctrl{{{wire}}}"#)), + Self::Ctrl(wire) => format!(r#"\ctrl{{{wire}}}"#), Self::Targ => r"\targ{}".to_string(), } } @@ -112,7 +112,7 @@ impl ToString for Symbol { Symbol::Gamma => r"\gamma".to_string(), Symbol::Phi => r"\phi".to_string(), Symbol::Pi => r"\pi".to_string(), - Symbol::Text(text) => format(format_args!(r#"\text{{{text}}}"#)), + Symbol::Text(text) => format!(r#"\text{{{text}}}"#), } } } @@ -284,7 +284,7 @@ impl Display for Document { /// measured by multiplying the column with the length of the circuit. This is /// an [m x n] matrix where each element in the matrix represents an item to be /// rendered onto the diagram using one of the [`Quantikz`] commands. -#[derive(Debug)] +#[derive(Debug, Default)] struct Diagram { /// customizes how the diagram renders the circuit settings: Settings, @@ -296,17 +296,6 @@ struct Diagram { circuit: BTreeMap>, } -impl Default for Diagram { - fn default() -> Self { - Self { - settings: Settings::default(), - column: 0, - relationships: HashMap::new(), - circuit: BTreeMap::new(), - } - } -} - impl Diagram { /// Compares qubits from a single instruction associated with a column on /// the circuit to all of the qubits used in the quil program. If a qubit @@ -333,7 +322,7 @@ impl Diagram { match program_qubit { instruction::Qubit::Fixed(q) => { self.circuit - .get_mut(&q) + .get_mut(q) .and_then(|wire| wire.empty.insert(self.column, Command::Qw)); } _ => (), @@ -379,7 +368,7 @@ impl Diagram { } instruction::GateModifier::Controlled => { // prepend a C to the gate - gate_name.insert_str(0, "C"); + gate_name.insert(0, 'C'); } _ => (), } @@ -439,16 +428,14 @@ impl Diagram { targ = Some(wire.name) } // all other qubits are the controls - } else { - if let Some(wire) = self.circuit.get_mut(qubit) { - // insert as control at this column with initial - // value 0, targeting themselves - wire.ctrl.insert(c, 0); - - // push ctrl to 'column loop ctrl variables with - // initial value requiring update based on targ - ctrls.push(wire.name); - } + } else if let Some(wire) = self.circuit.get_mut(qubit) { + // insert as control at this column with initial + // value 0, targeting themselves + wire.ctrl.insert(c, 0); + + // push ctrl to 'column loop ctrl variables with + // initial value requiring update based on targ + ctrls.push(wire.name); } } } else { @@ -469,12 +456,11 @@ impl Diagram { let mut close = None; // closing qubit in range // find the range between the qubits - let mut i = 0; - for wire in &self.circuit { + for (i, wire) in self.circuit.iter().enumerate() { // get each existing qubit in the circuit if *wire.0 == ctrl || *wire.0 == targ { // if the qubit is the ctrl or target - if let Some(_) = open { + if open.is_some() { close = Some(i); break; @@ -483,8 +469,6 @@ impl Diagram { open = Some(i) } } - - i += 1; } let mut vector: i64 = 0; @@ -492,10 +476,10 @@ impl Diagram { if let Some(close) = close { if ctrl < targ { // a vector with a head from the ctrl to the targ - vector = 1 * (close - open); + vector = (close as i64) - (open as i64); } else { // a vector with a head from the targ to the ctrl - vector = -1 * (close - open); + vector = -((close as i64) - (open as i64)); } } } @@ -622,7 +606,7 @@ impl Display for Diagram { line.push_str(&Command::get_command(Command::Ctrl( targ.to_string(), ))); - } else if let Some(_) = wire.targ.get(&c) { + } else if wire.targ.get(&c).is_some() { // if this is a target and has a PHASE gate display `\phase{param}` if gate.contains("PHASE") { // set the phase parameters @@ -669,7 +653,7 @@ impl Display for Diagram { line.push_str(&Command::get_command(Command::Gate(gate))); } - } else if let Some(_) = wire.empty.get(&c) { + } else if wire.empty.get(&c).is_some() { // chain an empty column qw to the end of the line line.push_str(" & "); line.push_str(&Command::get_command(Command::Qw)); @@ -694,7 +678,7 @@ impl Display for Diagram { body.push_str(&line); } - write!(f, "{}", body) + write!(f, "{body}") } } @@ -707,7 +691,7 @@ impl Display for Diagram { /// about this connection. This updated value also looks arbitrary to Wire, it /// does not explicitly define which qubit it relates to, but a digit that /// describes how far away it is from the related qubit based on [`Quantikz`]. -#[derive(Debug)] +#[derive(Debug, Default)] struct Wire { /// the name of ket(qubit) placed using the Lstick or Rstick commands name: u64, @@ -725,20 +709,6 @@ struct Wire { empty: HashMap, } -impl Default for Wire { - fn default() -> Self { - Self { - name: 0, - gates: HashMap::new(), - ctrl: HashMap::new(), - targ: HashMap::new(), - parameters: HashMap::new(), - modifiers: HashMap::new(), - empty: HashMap::new(), - } - } -} - impl Wire { /// Retrieves a gate's parameters from Expression and matches them with its /// symbolic definition which is then stored into wire at the specific @@ -824,7 +794,7 @@ impl SupportedGate { return Self::ControlledX(name); } - return Self::Unsupported(name); + Self::Unsupported(name) } /// Returns a variant of self for any defined gate. @@ -837,11 +807,11 @@ impl SupportedGate { for defgate in defgate { // return supported if gate name is of DEFGATE if defgate == &name { - return SupportedGate::DefGate(name.to_string()); + return SupportedGate::DefGate(name); } } - return Self::Unsupported(name); + Self::Unsupported(name) } /// Returns a variant of self for any supported modifier. @@ -853,7 +823,7 @@ impl SupportedGate { return Self::Modifiers(name); } - return Self::Unsupported(name); + Self::Unsupported(name) } } @@ -1016,7 +986,7 @@ impl Latex for Program { // update the gate name based on the modifiers let gate_name = diagram.set_modifiers(&gate, &mut wire); - if let Some(_) = diagram.circuit.get(&qubit) { + if diagram.circuit.get(qubit).is_some() { // has ctrl gate, must identify ctrls and targs after filling circuit if gate_name.starts_with('C') { has_ctrl_targ = true; @@ -1056,10 +1026,10 @@ impl Latex for Program { let body = diagram.to_string(); let document = Document { - body: body, + body, ..Default::default() }; - println!("{}", document.to_string()); + println!("{document}"); Ok(document.to_string()) } From 63e13ea3159537afc5265fc8d5e924bc13d59a64 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 17 Mar 2023 10:37:31 -0700 Subject: [PATCH 060/118] Add manual clippy fixes and Clone or Copy to structs and make more idiomatic. --- src/program/latex/mod.rs | 220 +++++++++++++++++---------------------- 1 file changed, 95 insertions(+), 125 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 31131453..f737b90b 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -34,7 +34,7 @@ use crate::Program; /// inside `backticks`. /// Single wire commands: lstick, rstick, qw, meter /// Multi-wire commands: ctrl, targ, control, (swap, targx) -#[derive(Debug)] +#[derive(Clone, Debug)] enum Command { /// `\lstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the left. Lstick(String), @@ -74,7 +74,7 @@ impl Command { } /// Types of parameters passed to commands. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] enum Parameter { /// Symbolic parameters Symbol(Symbol), @@ -89,7 +89,7 @@ impl ToString for Parameter { } /// Supported Greek and alphanumeric symbols. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] enum Symbol { Alpha, Beta, @@ -132,7 +132,7 @@ impl Symbol { /// RenderSettings contains the metadata that allows the user to customize how /// the circuit is rendered or use the default implementation. -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub struct RenderSettings { /// Convert numerical constants, e.g. pi, to LaTeX form. pub texify_numerical_constants: bool, @@ -202,36 +202,35 @@ impl RenderSettings { } // get the first qubit in the BTreeMap - let mut first = 0; - if let Some(f) = circuit.first_key_value() { - first = *f.0 + 1; - } + let first = circuit + .first_key_value() + .expect("previously checked that circuit is not empty") + .0 + + 1; // get the last qubit in the BTreeMap - let mut last = 0; - if let Some(l) = circuit.last_key_value() { - last = *l.0 - 1; - } + let last = circuit + .last_key_value() + .expect("previously checked that circuit has at least two wires") + .0 + - 1; // search through the range of qubits for qubit in first..=last { // if the qubit is not found impute it - match circuit.get(&qubit) { - Some(_) => (), - None => { - let mut wire = Wire { - name: qubit, - ..Default::default() - }; - - // insert empties based on total number of columns - for c in 0..column { - wire.empty.insert(c, Command::Qw); - } - - circuit.insert(qubit, Box::new(wire)); + circuit.entry(qubit).or_insert_with(|| { + let mut wire = Wire { + name: qubit, + ..Default::default() + }; + + // insert empties based on total number of columns + for c in 0..column { + wire.empty.insert(c, Command::Qw); } - } + + Box::new(wire) + }); } } } @@ -255,7 +254,7 @@ impl Default for Document { \begin{document} \begin{tikzcd}" .to_string(), - body: "".to_string(), + body: String::new(), footer: r"\end{tikzcd} \end{document}" .to_string(), @@ -279,7 +278,7 @@ impl Display for Document { /// measured by multiplying the column with the length of the circuit. This is /// an [m x n] matrix where each element in the matrix represents an item to be /// rendered onto the diagram using one of the Quantikz commands. -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] struct Diagram { /// customizes how the diagram renders the circuit settings: RenderSettings, @@ -303,28 +302,19 @@ impl Diagram { /// `qubits` - qubits used in the quil program /// `instruction` - exposes qubits in a single instruction fn set_qw(&mut self, qubits: &HashSet, instruction: &Instruction) { - for program_qubit in qubits { - let mut found = false; - match &instruction { - instruction::Instruction::Gate(gate) => { - for gate_qubit in &gate.qubits { - if program_qubit == gate_qubit { - found = true; - } + 'program_loop: for program_qubit in qubits { + if let Instruction::Gate(gate) = instruction { + for gate_qubit in &gate.qubits { + if program_qubit == gate_qubit { + continue 'program_loop; } + } - if !found { - match program_qubit { - instruction::Qubit::Fixed(q) => { - self.circuit - .get_mut(q) - .and_then(|wire| wire.empty.insert(self.column, Command::Qw)); - } - _ => (), - } + if let Qubit::Fixed(q) = program_qubit { + if let Some(wire) = self.circuit.get_mut(q) { + wire.empty.insert(self.column, Command::Qw); } } - _ => (), } } } @@ -503,13 +493,11 @@ impl Diagram { let qubit = wire.name; // find wire in circuit collection - match self.circuit.get_mut(&wire.name) { - Some(wire_in_circuit) => { - // get the new gate from the wire and insert into existing wire - if let Some(gate) = wire.gates.get(&self.column) { - // add gates to wire in circuit - wire_in_circuit.gates.insert(self.column, gate.to_string()); - } + if let Some(wire_in_circuit) = self.circuit.get_mut(&wire.name) { + // get the new gate from the wire and insert into existing wire + if let Some(gate) = wire.gates.get(&self.column) { + // add gates to wire in circuit + wire_in_circuit.gates.insert(self.column, gate.to_string()); // add modifiers to gate in circuit if let Some(modifier) = wire.modifiers.get(&self.column) { @@ -525,7 +513,6 @@ impl Diagram { .insert(self.column, parameters.to_vec()); } } - _ => (), } // initalize relationships between multi qubit gates @@ -686,7 +673,7 @@ impl Display for Diagram { /// about this connection. This updated value also looks arbitrary to Wire, it /// does not explicitly define which qubit it relates to, but a digit that /// describes how far away it is from the related qubit based on Quantikz. -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] struct Wire { /// the name of ket(qubit) placed using the Lstick or Rstick commands name: u64, @@ -715,33 +702,26 @@ impl Wire { /// `column` - the column taking the parameters /// `texify` - is texify_numerical_constants setting on? fn set_param(&mut self, expression: &Expression, column: u32, texify: bool) { - let text: String; - // get the name of the supported expression - match expression { - Expression::Address(mr) => { - text = mr.name.to_string(); - } - Expression::Number(c) => { - text = c.re.to_string(); - } - expression => text = expression.to_string(), - } + let text = match expression { + Expression::Address(mr) => mr.name.to_string(), + Expression::Number(c) => c.re.to_string(), + expression => expression.to_string(), + }; - let param; // if texify_numerical_constants - if texify { + let param = if texify { // get the matching symbol from text - param = vec![Parameter::Symbol(Symbol::match_symbol(text))]; + vec![Parameter::Symbol(Symbol::match_symbol(text))] } else { // set the symbol as text - param = vec![Parameter::Symbol(Symbol::Text(text))]; - } + vec![Parameter::Symbol(Symbol::Text(text))] + }; self.parameters.insert(column, param); } } -#[derive(thiserror::Error, Debug)] +#[derive(Clone, Debug, thiserror::Error)] pub enum LatexGenError { #[error("Cannot parse LaTeX for unsupported program: {program}")] UnsupportedProgram { program: String }, @@ -759,7 +739,7 @@ enum Supported { } /// Set of all Gates that can be parsed to LaTeX -#[derive(PartialEq)] +#[derive(Clone, PartialEq)] enum SupportedGate { Pauli(String), Hadamard(String), @@ -941,15 +921,12 @@ impl Latex for Program { // initialize circuit with empty wires of all qubits in program let qubits = Program::get_used_qubits(&self); for qubit in &qubits { - match qubit { - instruction::Qubit::Fixed(name) => { - let wire = Wire { - name: *name, - ..Default::default() - }; - diagram.circuit.insert(*name, Box::new(wire)); - } - _ => (), + if let Qubit::Fixed(name) = qubit { + let wire = Wire { + name: *name, + ..Default::default() + }; + diagram.circuit.insert(*name, Box::new(wire)); } } @@ -959,54 +936,47 @@ impl Latex for Program { // set QW for any unused qubits in this instruction diagram.set_qw(&qubits, &instruction); - match instruction { - // parse gate instructions into a new circuit - instruction::Instruction::Gate(gate) => { - // for each qubit in a single gate instruction - for qubit in &gate.qubits { - match qubit { - instruction::Qubit::Fixed(qubit) => { - // create a new wire - let mut wire = Wire { - name: *qubit, - ..Default::default() - }; - - // set parameters for phase gates - if gate.name.contains("PHASE") { - for expression in &gate.parameters { - wire.set_param( - expression, - diagram.column, - diagram.settings.texify_numerical_constants, - ); - } - } - - // update the gate name based on the modifiers - let gate_name = diagram.set_modifiers(&gate, &mut wire); - - if diagram.circuit.get(qubit).is_some() { - // has ctrl gate, must identify ctrls and targs after filling circuit - if gate_name.starts_with('C') { - has_ctrl_targ = true; - } + // parse gate instructions into a new circuit + if let Instruction::Gate(gate) = instruction { + // for each qubit in a single gate instruction + for qubit in &gate.qubits { + if let Qubit::Fixed(qubit) = qubit { + // create a new wire + let mut wire = Wire { + name: *qubit, + ..Default::default() + }; + + // set parameters for phase gates + if gate.name.contains("PHASE") { + for expression in &gate.parameters { + wire.set_param( + expression, + diagram.column, + diagram.settings.texify_numerical_constants, + ); + } + } - // add the gate to the wire at column 0 - wire.gates.insert(diagram.column, gate_name); - } + // update the gate name based on the modifiers + let gate_name = diagram.set_modifiers(&gate, &mut wire); - // push wire to diagram circuit - diagram.push_wire(wire)?; + if diagram.circuit.get(qubit).is_some() { + // has ctrl gate, must identify ctrls and targs after filling circuit + if gate_name.starts_with('C') { + has_ctrl_targ = true; } - _ => (), + + // add the gate to the wire at column 0 + wire.gates.insert(diagram.column, gate_name); } - } - diagram.column += 1; + // push wire to diagram circuit + diagram.push_wire(wire)?; + } } - // do nothing for all other instructions - _ => (), + + diagram.column += 1; } } From 3677651dc7083765abe0d67ca181255623471a32 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 17 Mar 2023 11:18:33 -0700 Subject: [PATCH 061/118] Remove supported program checking. --- src/program/latex/mod.rs | 221 +-------------------------------------- 1 file changed, 4 insertions(+), 217 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index f737b90b..b621ba9f 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -6,9 +6,8 @@ //! rendered in a LaTeX visualization tool. Be aware that not all Programs can //! be serialized as LaTeX. If a [`Program`] contains a gate or modifier that //! has not been implemented in the [Supported Gates and Modifiers] -//! (#supported-gates-and-modifiers) section below, an error will be returned -//! detailing whether the entire [`Program`] or which line of instruction -//! containing the gate or modifier is unsupported. +//! (#supported-gates-and-modifiers) section below, unexpected results may +//! occur, one of which includes producing incorrect quantum circuits. //! //! # Supported Gates and Modifiers //! @@ -723,157 +722,10 @@ impl Wire { #[derive(Clone, Debug, thiserror::Error)] pub enum LatexGenError { - #[error("Cannot parse LaTeX for unsupported program: {program}")] - UnsupportedProgram { program: String }, - #[error("Cannot parse LaTeX for unsupported gate: {gate}")] - UnsupportedGate { gate: String }, #[error("Tried to parse CNOT and found a control qubit without a target.")] FoundCNOTWithNoTarget, } -/// Supported types -enum Supported { - Gate(SupportedGate), - New, // New initialization of a non-None variant - None, // () -} - -/// Set of all Gates that can be parsed to LaTeX -#[derive(Clone, PartialEq)] -enum SupportedGate { - Pauli(String), - Hadamard(String), - Phase(String), - ControlledPhase(String), - ControlledX(String), - DefGate(String), - Modifiers(String), - Unsupported(String), // for error handling -} - -impl SupportedGate { - /// Returns a variant of self for any supported standard gate. - /// - /// # Arguments - /// `name` - the name of the gate from instruction - fn get_supported_standard_gate(name: String) -> Self { - if name == "I" || name == "X" || name == "Y" || name == "Z" { - return Self::Pauli(name); - } else if name == "H" { - return Self::Hadamard(name); - } else if name == "PHASE" || name == "S" || name == "T" { - return Self::Phase(name); - } else if name == "CZ" || name == "CPHASE" { - return Self::ControlledPhase(name); - } else if name == "CNOT" || name == "CCNOT" { - return Self::ControlledX(name); - } - - Self::Unsupported(name) - } - - /// Returns a variant of self for any defined gate. - /// - /// # Arguments - /// `name` - the name of the defined gate from instruction - /// `defgate` - a vector of all previously defined gates - fn get_supported_defgate(name: String, defgate: &Vec) -> Self { - // check all previously defined DEFGATES - for defgate in defgate { - // return supported if gate name is of DEFGATE - if defgate == &name { - return SupportedGate::DefGate(name); - } - } - - Self::Unsupported(name) - } - - /// Returns a variant of self for any supported modifier. - /// - /// # Arguments - /// `name` - the name of the defined gate from instruction - fn get_supported_modifier(name: String) -> Self { - if name == "CONTROLLED" || name == "DAGGER" { - return Self::Modifiers(name); - } - - Self::Unsupported(name) - } -} - -impl Supported { - /// Returns new variant of self as variant of supported gate. - /// - /// # Arguments - /// `name` - the name of the defined gate from instruction - /// `defgate` - a vector of all previously defined gates - fn new(&self, gate: &Gate, defgate: &Vec) -> Self { - // check is standard gate - let mut is_supported = SupportedGate::get_supported_standard_gate(gate.name.to_string()); - - // check if defgate if not already identified as a standard gate - if is_supported == SupportedGate::Unsupported(gate.name.to_string()) { - is_supported = SupportedGate::get_supported_defgate(gate.name.to_string(), defgate); - } - - // check supported modifers - for modifier in &gate.modifiers { - is_supported = SupportedGate::get_supported_modifier(modifier.to_string()) - } - - match self { - Supported::New => Self::Gate(is_supported), - _ => Supported::None, // same as () - } - } - - /// Returns a result indicating whether or not the LaTeX feature can parse - /// a given Program to LaTeX. - /// - /// # Arguments - /// `self` - exposes variants of a Supported program - /// `program` - the Program to be validated before parsing to LaTeX - fn is_supported(&self, program: &Program) -> Result, LatexGenError> { - let instructions = program.to_instructions(false); - - // store DEFGATE names for reference - let mut defgate: Vec = vec![]; - - // check each instruction to determine if it is supported - for instruction in &instructions { - match instruction { - // check is gate is supported - instruction::Instruction::Gate(gate) => match Self::new(self, gate, &defgate) { - // new Supported checks if this instruction is supported - Supported::Gate(is_supported) => match is_supported { - // unsupported if SupportedGate is not returned - SupportedGate::Unsupported(gate) => { - return Err(LatexGenError::UnsupportedGate { gate }) - } - // SupportedGate returned so instruction is supported - _ => (), - }, - // do nothing for non-New Self variants - _ => (), - }, - // DEFGATE is supported - instruction::Instruction::GateDefinition(gate) => { - defgate.push(gate.name.to_string()) - } - // unless explicitly matched, program is unsupported - _ => { - return Err(LatexGenError::UnsupportedProgram { - program: program.to_string(false), - }) - } - } - } - - Ok(instructions) - } -} - pub trait Latex { fn to_latex(self, settings: RenderSettings) -> Result; } @@ -909,8 +761,8 @@ impl Latex for Program { /// let latex = program.to_latex(RenderSettings::default()).expect(""); /// ``` fn to_latex(self, settings: RenderSettings) -> Result { - // get a reference to the current supported program - let instructions = Supported::is_supported(&Supported::New, &self)?; + // get a reference to the current program + let instructions = self.to_instructions(false); // initialize a new diagram let mut diagram = Diagram { @@ -1073,71 +925,6 @@ DAGGER G 0"#, } } - /// Test module for Supported (remove #[should_panic] when supported) - mod supported { - use crate::program::latex::{tests::get_latex, RenderSettings}; - - #[test] - #[should_panic] - fn test_supported_misc_instructions() { - get_latex("NOP\nWAIT\nRESET\nHALT", RenderSettings::default()); - } - - #[test] - #[should_panic] - fn test_supported_measure() { - get_latex( - "DECLARE ro BIT\nMEASURE 0\nMEASURE 1 ro[0]", - RenderSettings::default(), - ); - } - - #[test] - #[should_panic] - fn test_supported_program_defcircuit() { - get_latex( - r#"DEFCIRCUIT EULER(%alpha, %beta, %gamma) q: - RX(%alpha) q - RY(%beta) q - RZ(%gamma) q - -EULER(pi, 2*pi, 3*pi/2) 0"#, - RenderSettings::default(), - ); - } - - #[test] - #[should_panic] - fn test_supported_gate_rotation() { - get_latex( - r#"DECLARE ro BIT[1] -DECLARE theta REAL[1] -RX(pi/2) 0 -RZ(theta) 0 -RY(-pi/2) 0"#, - RenderSettings::default(), - ); - } - - #[test] - #[should_panic] - fn test_supported_arithmetic_instruction() { - get_latex( - "DECLARE b BIT\nDECLARE theta REAL\nMOVE theta -3.14\nLT b theta -3.14", - RenderSettings::default(), - ); - } - - #[test] - #[should_panic] - fn test_supported_modifier_forked() { - get_latex( - "FORKED CONTROLLED FORKED RX(a,b,c,d) 0 1 2 3", - RenderSettings::default(), - ); - } - } - /// Test module for gates mod gates { use crate::program::latex::{tests::get_latex, RenderSettings}; From 6a2fd9e508c61782156ac7e9e3829320153c0df8 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 20 Mar 2023 09:45:59 -0700 Subject: [PATCH 062/118] Use write! in Diagram fmt, reduce/reformat docs. --- src/program/latex/mod.rs | 102 ++++++++++++++------------------------- 1 file changed, 37 insertions(+), 65 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index b621ba9f..190efbce 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -31,8 +31,8 @@ use crate::Program; /// Available commands used for building circuits with the same names taken /// from the Quantikz documentation for easy reference. LaTeX string denoted /// inside `backticks`. -/// Single wire commands: lstick, rstick, qw, meter -/// Multi-wire commands: ctrl, targ, control, (swap, targx) +/// Single wire commands: lstick, rstick, qw, meter +/// Multi-wire commands: ctrl, targ, control, (swap, targx) #[derive(Clone, Debug)] enum Command { /// `\lstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the left. @@ -54,10 +54,6 @@ enum Command { } impl Command { - /// Returns the LaTeX String for a given Command variant. - /// - /// # Arguments - /// `command` - A Command variant. fn get_command(command: Self) -> String { match command { Self::Lstick(wire) => format!(r#"\lstick{{\ket{{q_{{{wire}}}}}}}"#), @@ -112,11 +108,11 @@ impl ToString for Symbol { } impl Symbol { - /// Returns the supported Symbol variant from text otherwise stores the + /// Returns the supported Symbol variant from text, otherwise, stores the /// unsupported symbol as text in the Text variant. /// /// # Arguments - /// `text` - a String representing a greek or alaphanumeric symbol + /// `text` - a String representing a Greek or alphanumeric symbol fn match_symbol(text: String) -> Symbol { match text.as_str() { "alpha" => Symbol::Alpha, @@ -324,13 +320,6 @@ impl Diagram { /// prepends a `C` to the beginning of the gate name. For other modifiers /// such as DAGGER, no special reformatting is required. /// - /// For example, for an instruction line `CONTROLLED CONTROLLED Y 0 1 2` the - /// gate name is reformatted to `CCY` where each C is mapped to an - /// associated qubit with the last qubit to the original gate name. For an - /// instruction line `DAGGER DAGGER Y 0`, the gate name remains `Y`, - /// instead each of the modifiers are added to the wire at the current - /// column where it is to be applied using the Command::Super variant. - /// /// # Arguments /// `&mut self` - exposes the current column of the Circuit /// `gate` - exposes the modifiers associated with the instruction gate @@ -547,29 +536,26 @@ impl Display for Diagram { /// can be input into the body of the Document. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // add a newline between the first line and the header - let mut body = String::from('\n'); + writeln!(f)?; let mut i = 0; // used to omit trailing Nr // write the LaTeX string for each wire in the circuit for key in self.circuit.keys() { - // a single line of LaTeX representing a wire from the circuit - let mut line = String::from(""); - // are labels on in settings? if self.settings.label_qubit_lines { // add label to left side of wire - line.push_str(&self.settings.label_qubit_lines(*key)); + write!(f, "{}", &self.settings.label_qubit_lines(*key))?; } else { // add qw buffer to first column - line.push_str(&Command::get_command(Command::Qw)); + write!(f, "{}", &Command::get_command(Command::Qw))?; } // convert each column in the wire to string if let Some(wire) = self.circuit.get(key) { for c in 0..self.column { if let Some(gate) = wire.gates.get(&c) { - line.push_str(" & "); + write!(f, " & ")?; let mut superscript = String::from(""); // attach modifiers to gate name if any @@ -584,23 +570,29 @@ impl Display for Diagram { if gate.starts_with('C') { // set qubit at this column as the control if let Some(targ) = wire.ctrl.get(&c) { - line.push_str(&Command::get_command(Command::Ctrl( - targ.to_string(), - ))); + write!( + f, + "{}", + &Command::get_command(Command::Ctrl(targ.to_string())) + )?; } else if wire.targ.get(&c).is_some() { // if this is a target and has a PHASE gate display `\phase{param}` if gate.contains("PHASE") { // set the phase parameters if let Some(parameters) = wire.parameters.get(&c) { for param in parameters { - line.push_str(&Command::get_command(Command::Phase( - param.to_string(), - ))); + write!( + f, + "{}", + &Command::get_command(Command::Phase( + param.to_string(), + )) + )?; } } // if target has a CNOT gate, display as targ{} } else if gate.contains("NOT") { - line.push_str(&Command::get_command(Command::Targ)); + write!(f, "{}", &Command::get_command(Command::Targ))?; // if target has a 'char' gate display `gate{char}` gate } else { let mut gate = String::from(gate.chars().last().unwrap()); @@ -610,7 +602,7 @@ impl Display for Diagram { gate.push_str(&superscript); } - line.push_str(&Command::get_command(Command::Gate(gate))) + write!(f, "{}", &Command::get_command(Command::Gate(gate)))?; } } // PHASE gates are displayed as `\phase{param}` @@ -618,9 +610,11 @@ impl Display for Diagram { // set the phase parameters if let Some(parameters) = wire.parameters.get(&c) { for param in parameters { - line.push_str(&Command::get_command(Command::Phase( - param.to_string(), - ))); + write!( + f, + "{}", + &Command::get_command(Command::Phase(param.to_string())) + )?; } } // all other gates display as `\gate{name}` @@ -632,34 +626,33 @@ impl Display for Diagram { gate.push_str(&superscript); } - line.push_str(&Command::get_command(Command::Gate(gate))); + write!(f, "{}", &Command::get_command(Command::Gate(gate)))?; } } else if wire.empty.get(&c).is_some() { // chain an empty column qw to the end of the line - line.push_str(" & "); - line.push_str(&Command::get_command(Command::Qw)); + write!(f, " & ")?; + write!(f, "{}", &Command::get_command(Command::Qw))?; } } } // chain an empty column qw to the end of the line - line.push_str(" & "); - line.push_str(&Command::get_command(Command::Qw)); + write!(f, " & ")?; + write!(f, "{}", &Command::get_command(Command::Qw))?; // if this is the last key iteration, omit Nr from end of line if i < self.circuit.len() - 1 { // indicate a new row - line.push(' '); - line.push_str(&Command::get_command(Command::Nr)); + write!(f, " ")?; + write!(f, "{}", &Command::get_command(Command::Nr))?; i += 1; } // add a newline between each new line or the footer - line.push('\n'); - body.push_str(&line); + writeln!(f)?; } - write!(f, "{body}") + Ok(()) } } @@ -873,28 +866,7 @@ mod tests { #[test] /// Test functionality of to_latex using default settings. fn test_to_latex() { - get_latex( - r#"Y 0 -CONTROLLED Y 0 1 -CONTROLLED CONTROLLED Y 0 1 2 -CONTROLLED CONTROLLED CONTROLLED Y 0 1 2 3 - -DAGGER Y 0 -DAGGER DAGGER Y 0 -DAGGER DAGGER DAGGER Y 0 - -CONTROLLED DAGGER Y 0 1 -CONTROLLED DAGGER CONTROLLED Y 0 1 2 -CONTROLLED DAGGER CONTROLLED DAGGER Y 0 1 2 - -DEFGATE G: - 1, 0 - 0, 1 - -CONTROLLED G 0 1 -DAGGER G 0"#, - RenderSettings::default(), - ); + get_latex(r#"RX 0"#, RenderSettings::default()); } /// Test module for the Document From 28f16e70780fabc695004e8668c4302169e9d303 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 21 Mar 2023 15:15:04 -0700 Subject: [PATCH 063/118] Decompose gates to canonical form. --- src/program/latex/mod.rs | 368 +++++++++++++++++++++++---------------- 1 file changed, 220 insertions(+), 148 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 190efbce..ae75cf1d 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -23,9 +23,10 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::fmt::Display; +use std::str::FromStr; use crate::expression::Expression; -use crate::instruction::{self, Gate, Instruction, Qubit}; +use crate::instruction::{Gate, GateModifier, Instruction, Qubit}; use crate::Program; /// Available commands used for building circuits with the same names taken @@ -125,6 +126,34 @@ impl Symbol { } } +/// Gates written in shorthand notation that may be decomposed into modifiers +/// and single gate instructions. + +enum CanonicalGate { + /// `CNOT` is `CONTROLLED X` + Cnot(String), + /// `CCNOT` is `CONTROLLED CONTROLLED X` + Ccnot(String), + /// `CPHASE` is `CONTROLLED PHASE` + Cphase(String), + /// `CZ` is `CONTROLLED Z` + Cz(String), + /// gate is in canonical form or is unsupported + None, +} + +impl CanonicalGate { + fn get_canonical(gate_name: &str) -> CanonicalGate { + match gate_name { + "CNOT" => CanonicalGate::Cnot(String::from("CONTROLLED X")), + "CCNOT" => CanonicalGate::Ccnot(String::from("CONTROLLED CONTROLLED X")), + "CPHASE" => CanonicalGate::Cphase(String::from("CONTROLLED PHASE")), + "CZ" => CanonicalGate::Cz(String::from("CONTROLLED Z")), + _ => CanonicalGate::None, + } + } +} + /// RenderSettings contains the metadata that allows the user to customize how /// the circuit is rendered or use the default implementation. #[derive(Clone, Copy, Debug)] @@ -280,7 +309,7 @@ struct Diagram { /// total number of elements on each wire column: u32, /// column at which qubits in positional order form relationships - relationships: HashMap>, + relationships: HashMap>, /// a BTreeMap of wires with the name of the wire as the key circuit: BTreeMap>, } @@ -314,41 +343,36 @@ impl Diagram { } } - /// Returns a reformatted gate name based on the modifiers used in a single - /// instruction line of a quil program or the original name. Gates with - /// CONTROLLED modifiers are reformatted such that each CONTROLLED modifier - /// prepends a `C` to the beginning of the gate name. For other modifiers - /// such as DAGGER, no special reformatting is required. + /// Utility function to insert modifiers of wires in this Circuit at the + /// current column. Returns an Err for unsupported modifiers. /// /// # Arguments - /// `&mut self` - exposes the current column of the Circuit - /// `gate` - exposes the modifiers associated with the instruction gate - /// `wire` - exposes the wire to be pushed to in Circuit - fn set_modifiers(&mut self, gate: &Gate, wire: &mut Wire) -> String { - let mut gate_name = gate.name.clone(); - + /// `column` - the current column of the Circuit + /// `wire` - a wire on the Circuit + /// `modifiers` - the modifiers from the Gate + fn set_modifiers( + wire: &mut Wire, + column: &u32, + modifiers: &Vec, + ) -> Result<(), LatexGenError> { // set modifers - if !gate.modifiers.is_empty() { - for modifer in &gate.modifiers { - match modifer { - instruction::GateModifier::Dagger => { - if let Some(modifiers) = wire.modifiers.get_mut(&self.column) { - modifiers.push("dagger".to_string()) - } else { - wire.modifiers - .insert(self.column, vec!["dagger".to_string()]); - } - } - instruction::GateModifier::Controlled => { - // prepend a C to the gate - gate_name.insert(0, 'C'); - } - _ => (), + for modifier in modifiers { + match modifier { + // return error for FORKED + GateModifier::Forked => { + return Err(LatexGenError::UnsupportedModifierForked); + } + // insert for CONTROLLED and DAGGER + _ => { + wire.modifiers + .entry(*column) + .and_modify(|m| m.push(modifier.clone())) + .or_insert_with(|| vec![modifier.clone()]); } } } - gate_name + Ok(()) } /// For every instruction containing control and target qubits this method @@ -389,27 +413,28 @@ impl Diagram { continue 'column; } - // the last qubit is the targ - if *qubit == relationship[relationship.len() - 1] { - if let Some(wire) = self.circuit.get_mut(qubit) { - // insert as target at this column - wire.targ.insert(c, true); - - // set 'column loop targ variable to this targ that - // control qubits will find distance from on their - // respective wires - targ = Some(wire.name) - } - // all other qubits are the controls - } else if let Some(wire) = self.circuit.get_mut(qubit) { - // insert as control at this column with initial - // value 0, targeting themselves - wire.ctrl.insert(c, 0); - - // push ctrl to 'column loop ctrl variables with - // initial value requiring update based on targ - ctrls.push(wire.name); - } + // FIXME + // // the last qubit is the targ + // if *qubit == relationship[relationship.len() - 1] { + // if let Some(wire) = self.circuit.get_mut(qubit) { + // // insert as target at this column + // wire.targ.insert(c, true); + + // // set 'column loop targ variable to this targ that + // // control qubits will find distance from on their + // // respective wires + // targ = Some(wire.name) + // } + // // all other qubits are the controls + // } else if let Some(wire) = self.circuit.get_mut(qubit) { + // // insert as control at this column with initial + // // value 0, targeting themselves + // wire.ctrl.insert(c, 0); + + // // push ctrl to 'column loop ctrl variables with + // // initial value requiring update based on targ + // ctrls.push(wire.name); + // } } } else { // no relationships found on this column, go to next @@ -468,60 +493,70 @@ impl Diagram { Ok(()) } - /// Takes a new or existing wire and adds or updates it using the name - /// (String) as the key. If a wire exists with the same name, then the - /// contents of the new wire are added to it by updating the next column - /// using the Quantikz command associated with its attributes (e.g. gate, - /// do_nothing, etc). + /// Analyzes a Gate from an instruction and sets the gate at this column on + /// the wire. If the gate name is a composite gate, the gate name is + /// decomposed into canonical form. For example, CNOT is a composite gate + /// that can be decomposed into the equivalent canonical form, CONTROLLED X. /// /// # Arguments - /// `&mut self` - exposes HashMap> - /// `wire` - the wire to be pushed or updated to in circuits - fn push_wire(&mut self, wire: Wire) -> Result<(), LatexGenError> { - let qubit = wire.name; - - // find wire in circuit collection - if let Some(wire_in_circuit) = self.circuit.get_mut(&wire.name) { - // get the new gate from the wire and insert into existing wire - if let Some(gate) = wire.gates.get(&self.column) { - // add gates to wire in circuit - wire_in_circuit.gates.insert(self.column, gate.to_string()); - - // add modifiers to gate in circuit - if let Some(modifier) = wire.modifiers.get(&self.column) { - wire_in_circuit - .modifiers - .insert(self.column, modifier.to_vec()); + /// `column` - the current empty column to set the gate at + /// `gate` - the gate of the instruction being parsed in to_latex + fn parse_gate(&mut self, gate: &Gate) -> Result<(), LatexGenError> { + // preserve qubit order in instruction + self.relationships.insert(self.column, gate.qubits.clone()); + + // set modifiers from gate instruction + for qubit in &gate.qubits { + if let Qubit::Fixed(qubit) = qubit { + if let Some(wire) = self.circuit.get_mut(qubit) { + // set modifiers at this column for all qubits + Self::set_modifiers(wire, &self.column, &gate.modifiers)?; } + } + } - // add modifiers to gate in circuit - if let Some(parameters) = wire.parameters.get(&self.column) { - wire_in_circuit - .parameters - .insert(self.column, parameters.to_vec()); + // parse the gate to a canonical gate if supported + let canonical_gate = match CanonicalGate::get_canonical(&gate.name) { + CanonicalGate::Cnot(inst) => Some(inst), + CanonicalGate::Ccnot(inst) => Some(inst), + CanonicalGate::Cphase(inst) => Some(inst), + CanonicalGate::Cz(inst) => Some(inst), + CanonicalGate::None => None, + }; + + // add the qubits to the canonical gate to form an instruction + let instruction = if let Some(mut canonical_gate) = canonical_gate { + for qubit in &gate.qubits { + if let Qubit::Fixed(qubit) = qubit { + canonical_gate.push(' '); + canonical_gate.push_str(&qubit.to_string()); } } - } + Some(canonical_gate) + } else { + None + }; - // initalize relationships between multi qubit gates - if let Some(wire) = self.circuit.get(&qubit) { - // get the newly added gate if any at the column it was added - if let Some(gate) = wire.gates.get(&self.column) { - // tag relationships for multi qubit gates - if gate.starts_with('C') { - // add the qubits to the set of related qubits in the current column - if let Some(qubits) = self.relationships.get_mut(&self.column) { - // ensure relationships are valid - for _self in qubits.iter() { - // qubit cannot control and target itself - if *_self == qubit { - return Err(LatexGenError::FoundCNOTWithNoTarget); - } - } + // get gate from new program of canonical instruction + if let Some(instruction) = instruction { + let instructions = Program::from_str(&instruction) + .expect("should return program {instruction}") + .to_instructions(false); - qubits.push(qubit); - } else { - self.relationships.insert(self.column, vec![qubit]); + for instruction in instructions { + if let Instruction::Gate(gate) = instruction { + // call until all composite gates are in canonical form + self.parse_gate(&gate)?; + } + } + // gate is in canonical form, build wire + } else { + // set gates + for qubit in &gate.qubits { + if let Qubit::Fixed(qubit) = qubit { + if let Some(wire) = self.circuit.get_mut(qubit) { + // set modifiers at this column for all qubits + wire.gates.insert(self.column, gate.name.clone()); } } } @@ -561,9 +596,10 @@ impl Display for Diagram { // attach modifiers to gate name if any if let Some(modifiers) = wire.modifiers.get(&c) { for modifier in modifiers { - superscript.push_str(&Command::get_command(Command::Super( - modifier.to_string(), - ))) + // FIXME: Changed data structure + // superscript.push_str(&Command::get_command(Command::Super( + // modifier.to_string(), + // ))) } } @@ -677,8 +713,8 @@ struct Wire { targ: HashMap, /// any parameters required at column on the wire for gates parameters: HashMap>, - /// any modifiers added to the wire at column - modifiers: HashMap>, + /// total number of controlled modifiers added to the wire at this column + modifiers: HashMap>, /// empty column empty: HashMap, } @@ -717,6 +753,8 @@ impl Wire { pub enum LatexGenError { #[error("Tried to parse CNOT and found a control qubit without a target.")] FoundCNOTWithNoTarget, + #[error("The FORKED modifier is unsupported.")] + UnsupportedModifierForked, } pub trait Latex { @@ -775,6 +813,14 @@ impl Latex for Program { } } + // are implicit qubits required in settings and are there at least two or more qubits in the diagram? + if diagram.settings.impute_missing_qubits { + // add implicit qubits to circuit + diagram + .settings + .impute_missing_qubits(diagram.column, &mut diagram.circuit); + } + // ensures set_ctrl_targ is called only if program has controlled gates let mut has_ctrl_targ = false; for instruction in instructions { @@ -783,56 +829,74 @@ impl Latex for Program { // parse gate instructions into a new circuit if let Instruction::Gate(gate) = instruction { - // for each qubit in a single gate instruction - for qubit in &gate.qubits { - if let Qubit::Fixed(qubit) = qubit { - // create a new wire - let mut wire = Wire { - name: *qubit, - ..Default::default() - }; - - // set parameters for phase gates - if gate.name.contains("PHASE") { - for expression in &gate.parameters { - wire.set_param( - expression, - diagram.column, - diagram.settings.texify_numerical_constants, - ); - } - } - - // update the gate name based on the modifiers - let gate_name = diagram.set_modifiers(&gate, &mut wire); - - if diagram.circuit.get(qubit).is_some() { - // has ctrl gate, must identify ctrls and targs after filling circuit - if gate_name.starts_with('C') { - has_ctrl_targ = true; - } - - // add the gate to the wire at column 0 - wire.gates.insert(diagram.column, gate_name); - } - - // push wire to diagram circuit - diagram.push_wire(wire)?; - } - } + // create a new wire + // let mut wire = Wire { + // name: *qubit, + // ..Default::default() + // }; + + println!("{gate:?}"); + + diagram.parse_gate(&gate)?; + + println!("{diagram:?}"); + + // find wire in circuit collection + // if let Some(wire) = diagram.circuit.get_mut(qubit) { + // // insert the gate into the wire at the current column + // wire.parse_gate(diagram.column, &gate); + + // // // add modifiers to gate in circuit + // // if let Some(modifier) = wire.modifiers.get(&self.column) { + // // wire_in_circuit + // // .modifiers + // // .insert(self.column, modifier.to_vec()); + // // } + + // // // add modifiers to gate in circuit + // // if let Some(parameters) = wire.parameters.get(&self.column) { + // // wire_in_circuit + // // .parameters + // // .insert(self.column, parameters.to_vec()); + // // } + // // } + // } + + // TODO: Do this in parse_gate() + // set parameters for phase gates + // if gate.name.contains("PHASE") { + // for expression in &gate.parameters { + // wire.set_param( + // expression, + // diagram.column, + // diagram.settings.texify_numerical_constants, + // ); + // } + // } + + // TODO: DELETE BELOW--No renaming needed with parse_gate() + // update the gate name based on the modifiers + // let gate_name = diagram.set_modifiers(&gate, &mut wire); + + // if diagram.circuit.get(qubit).is_some() { + // // has ctrl gate, must identify ctrls and targs after filling circuit + // if gate_name.starts_with('C') { + // has_ctrl_targ = true; + // } + + // // add the gate to the wire at column 0 + // wire.gates.insert(diagram.column, gate_name); + // } + // TODO: DELETE ABOVE + + // TODO: Change push_wire to set_relationships + // push wire to diagram circuit + // diagram.push_wire(wire)?; diagram.column += 1; } } - // are implicit qubits required in settings and are there at least two or more qubits in the diagram? - if diagram.settings.impute_missing_qubits { - // add implicit qubits to circuit - diagram - .settings - .impute_missing_qubits(diagram.column, &mut diagram.circuit); - } - // only call method for programs with control and target gates if has_ctrl_targ { // identify control and target qubits @@ -866,7 +930,15 @@ mod tests { #[test] /// Test functionality of to_latex using default settings. fn test_to_latex() { - get_latex(r#"RX 0"#, RenderSettings::default()); + let latex = get_latex( + r#"CONTROLLED CNOT 0 3"#, + RenderSettings { + impute_missing_qubits: true, + ..Default::default() + }, + ); + + println!("{latex}"); } /// Test module for the Document From 7ab4b5a5f9a6c2866082308745a1fea50f34abdc Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 21 Mar 2023 21:44:43 -0700 Subject: [PATCH 064/118] Fill circuit using instruction in canonical form. --- src/program/latex/mod.rs | 280 +++++++++++++-------------------------- 1 file changed, 95 insertions(+), 185 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index ae75cf1d..4ade3961 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -206,6 +206,7 @@ impl RenderSettings { /// and pushed to the diagram's circuit. /// /// # Arguments + /// `column` - the length of instructions from Program /// `&mut BTreeMap> circuit` - the circuit of the diagram /// /// # Examples @@ -244,12 +245,11 @@ impl RenderSettings { // if the qubit is not found impute it circuit.entry(qubit).or_insert_with(|| { let mut wire = Wire { - name: qubit, ..Default::default() }; // insert empties based on total number of columns - for c in 0..column { + for c in 0..=column { wire.empty.insert(c, Command::Qw); } @@ -399,93 +399,65 @@ impl Diagram { /// # Arguments /// `&mut self` - self as mutible allowing to update the circuit qubits fn set_ctrl_targ(&mut self) -> Result<(), LatexGenError> { - // ensure every column preserves the connection between ctrl and targ - 'column: for c in 0..=self.column { - let mut ctrls = vec![]; // the control qubits - let mut targ = None; // the targ qubit - - // determine if a relationship exists at this column - if let Some(relationship) = self.relationships.get(&c) { - // determine the control and target qubits - for qubit in relationship { - // relationships require at least two qubits - if relationship.len() < 2 { - continue 'column; - } - - // FIXME - // // the last qubit is the targ - // if *qubit == relationship[relationship.len() - 1] { - // if let Some(wire) = self.circuit.get_mut(qubit) { - // // insert as target at this column - // wire.targ.insert(c, true); - - // // set 'column loop targ variable to this targ that - // // control qubits will find distance from on their - // // respective wires - // targ = Some(wire.name) - // } - // // all other qubits are the controls - // } else if let Some(wire) = self.circuit.get_mut(qubit) { - // // insert as control at this column with initial - // // value 0, targeting themselves - // wire.ctrl.insert(c, 0); - - // // push ctrl to 'column loop ctrl variables with - // // initial value requiring update based on targ - // ctrls.push(wire.name); - // } - } - } else { - // no relationships found on this column, go to next - continue 'column; + if let Some(relationship) = self.relationships.get(&self.column) { + // requires at least two qubits + if relationship.len() < 2 { + return Ok(()); } - // determine the physical vector where a positive vector points // from control to target, negative, from target to control. The // magnitude of the vector is the absolute value of the distance // between them - if let Some(targ) = targ { - // distance between qubits is the space between the ctrl and - // targ qubits in the circuit - for ctrl in ctrls { - // represent inclusive [open, close] brackets of a range - let mut open = None; // opening qubit in range - let mut close = None; // closing qubit in range - - // find the range between the qubits - for (i, wire) in self.circuit.iter().enumerate() { - // get each existing qubit in the circuit - if *wire.0 == ctrl || *wire.0 == targ { - // if the qubit is the ctrl or target - if open.is_some() { - close = Some(i); + if let Some(last_qubit) = relationship.last() { + if let Qubit::Fixed(targ) = last_qubit { + // distance between qubits is the space between the ctrl and + // targ qubits in the circuit + for qubit in relationship { + if let Qubit::Fixed(ctrl) = qubit { + // found target qubit break loop + if qubit == last_qubit { break; + } - // open qubit in range not found, set open qubit - } else { - open = Some(i) + // represent inclusive [open, close] brackets of a range + let mut open = None; // opening qubit in range + let mut close = None; // closing qubit in range + + // find the range between the qubits + for (i, wire) in self.circuit.iter().enumerate() { + // get each existing qubit in the circuit + if *wire.0 == *ctrl || *wire.0 == *targ { + // if the qubit is the ctrl or target + if open.is_some() { + close = Some(i); + break; + + // open qubit in range not found, set open qubit + } else { + open = Some(i) + } + } } - } - } - let mut vector: i64 = 0; - if let Some(open) = open { - if let Some(close) = close { - if ctrl < targ { - // a vector with a head from the ctrl to the targ - vector = (close as i64) - (open as i64); - } else { - // a vector with a head from the targ to the ctrl - vector = -((close as i64) - (open as i64)); + let mut vector: i64 = 0; + if let Some(open) = open { + if let Some(close) = close { + if ctrl < targ { + // a vector with a head from the ctrl to the targ + vector = (close as i64) - (open as i64); + } else { + // a vector with a head from the targ to the ctrl + vector = -((close as i64) - (open as i64)); + } + } } + // set wire at column as the control qubit of target qubit + // computed as the distance from the control qubit + self.circuit + .get_mut(ctrl) + .and_then(|wire| wire.ctrl.insert(self.column, vector)); } } - // set wire at column as the control qubit of target qubit - // computed as the distance from the control qubit - self.circuit - .get_mut(&ctrl) - .and_then(|wire| wire.ctrl.insert(c, vector)); } } } @@ -511,6 +483,15 @@ impl Diagram { if let Some(wire) = self.circuit.get_mut(qubit) { // set modifiers at this column for all qubits Self::set_modifiers(wire, &self.column, &gate.modifiers)?; + + // set parameters at this column for all qubits + for expression in &gate.parameters { + wire.set_param( + expression, + self.column, + self.settings.texify_numerical_constants, + ); + } } } } @@ -552,9 +533,25 @@ impl Diagram { // gate is in canonical form, build wire } else { // set gates - for qubit in &gate.qubits { - if let Qubit::Fixed(qubit) = qubit { + for fixed_qubit in &gate.qubits { + if let Qubit::Fixed(qubit) = fixed_qubit { if let Some(wire) = self.circuit.get_mut(qubit) { + // set the control and target qubits + if let Some(relationship) = self.relationships.get(&self.column) { + // requires at least 2 qubits + if relationship.len() > 1 { + // target is the last qubit in the instruction + if let Some(target) = relationship.last() { + if fixed_qubit == target { + wire.targ.insert(self.column, true); + // all other qubits are controls + } else { + wire.ctrl.insert(self.column, 0); + } + } + } + } + // set modifiers at this column for all qubits wire.gates.insert(self.column, gate.name.clone()); } @@ -596,14 +593,16 @@ impl Display for Diagram { // attach modifiers to gate name if any if let Some(modifiers) = wire.modifiers.get(&c) { for modifier in modifiers { - // FIXME: Changed data structure - // superscript.push_str(&Command::get_command(Command::Super( - // modifier.to_string(), - // ))) + if let GateModifier::Dagger = modifier { + superscript.push_str(&Command::get_command(Command::Super( + String::from("dagger"), + ))) + } } } - if gate.starts_with('C') { + // is this a controlled X gate? + if gate == "X" && !wire.ctrl.is_empty() || !wire.targ.is_empty() { // set qubit at this column as the control if let Some(targ) = wire.ctrl.get(&c) { write!( @@ -611,34 +610,17 @@ impl Display for Diagram { "{}", &Command::get_command(Command::Ctrl(targ.to_string())) )?; + // set the qubit at this column as the target } else if wire.targ.get(&c).is_some() { - // if this is a target and has a PHASE gate display `\phase{param}` - if gate.contains("PHASE") { - // set the phase parameters - if let Some(parameters) = wire.parameters.get(&c) { - for param in parameters { - write!( - f, - "{}", - &Command::get_command(Command::Phase( - param.to_string(), - )) - )?; - } - } - // if target has a CNOT gate, display as targ{} - } else if gate.contains("NOT") { - write!(f, "{}", &Command::get_command(Command::Targ))?; - // if target has a 'char' gate display `gate{char}` gate - } else { - let mut gate = String::from(gate.chars().last().unwrap()); - - // concatenate superscripts - if !superscript.is_empty() { - gate.push_str(&superscript); - } + let mut x_gate = String::from("X"); - write!(f, "{}", &Command::get_command(Command::Gate(gate)))?; + // if the gate contains daggers, display target as X gate with dagger superscripts + if !superscript.is_empty() { + x_gate.push_str(&superscript); + write!(f, "{}", &Command::get_command(Command::Gate(x_gate)))?; + // else display target as an open dot + } else { + write!(f, "{}", &Command::get_command(Command::Targ))?; } } // PHASE gates are displayed as `\phase{param}` @@ -703,8 +685,6 @@ impl Display for Diagram { /// describes how far away it is from the related qubit based on Quantikz. #[derive(Clone, Debug, Default)] struct Wire { - /// the name of ket(qubit) placed using the Lstick or Rstick commands - name: u64, /// gate elements placed at column on wire using the Gate command gates: HashMap, /// control at column with distance from targ wire @@ -745,6 +725,7 @@ impl Wire { // set the symbol as text vec![Parameter::Symbol(Symbol::Text(text))] }; + self.parameters.insert(column, param); } } @@ -806,7 +787,6 @@ impl Latex for Program { for qubit in &qubits { if let Qubit::Fixed(name) = qubit { let wire = Wire { - name: *name, ..Default::default() }; diagram.circuit.insert(*name, Box::new(wire)); @@ -818,91 +798,21 @@ impl Latex for Program { // add implicit qubits to circuit diagram .settings - .impute_missing_qubits(diagram.column, &mut diagram.circuit); + .impute_missing_qubits(instructions.len() as u32, &mut diagram.circuit); } - // ensures set_ctrl_targ is called only if program has controlled gates - let mut has_ctrl_targ = false; for instruction in instructions { // set QW for any unused qubits in this instruction diagram.set_qw(&qubits, &instruction); // parse gate instructions into a new circuit if let Instruction::Gate(gate) = instruction { - // create a new wire - // let mut wire = Wire { - // name: *qubit, - // ..Default::default() - // }; - - println!("{gate:?}"); - diagram.parse_gate(&gate)?; - - println!("{diagram:?}"); - - // find wire in circuit collection - // if let Some(wire) = diagram.circuit.get_mut(qubit) { - // // insert the gate into the wire at the current column - // wire.parse_gate(diagram.column, &gate); - - // // // add modifiers to gate in circuit - // // if let Some(modifier) = wire.modifiers.get(&self.column) { - // // wire_in_circuit - // // .modifiers - // // .insert(self.column, modifier.to_vec()); - // // } - - // // // add modifiers to gate in circuit - // // if let Some(parameters) = wire.parameters.get(&self.column) { - // // wire_in_circuit - // // .parameters - // // .insert(self.column, parameters.to_vec()); - // // } - // // } - // } - - // TODO: Do this in parse_gate() - // set parameters for phase gates - // if gate.name.contains("PHASE") { - // for expression in &gate.parameters { - // wire.set_param( - // expression, - // diagram.column, - // diagram.settings.texify_numerical_constants, - // ); - // } - // } - - // TODO: DELETE BELOW--No renaming needed with parse_gate() - // update the gate name based on the modifiers - // let gate_name = diagram.set_modifiers(&gate, &mut wire); - - // if diagram.circuit.get(qubit).is_some() { - // // has ctrl gate, must identify ctrls and targs after filling circuit - // if gate_name.starts_with('C') { - // has_ctrl_targ = true; - // } - - // // add the gate to the wire at column 0 - // wire.gates.insert(diagram.column, gate_name); - // } - // TODO: DELETE ABOVE - - // TODO: Change push_wire to set_relationships - // push wire to diagram circuit - // diagram.push_wire(wire)?; - + diagram.set_ctrl_targ()?; diagram.column += 1; } } - // only call method for programs with control and target gates - if has_ctrl_targ { - // identify control and target qubits - diagram.set_ctrl_targ()?; - } - let body = diagram.to_string(); let document = Document { body, From e37d83e4d163ee7ef11fac968a81344cee3f3c36 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 22 Mar 2023 13:37:02 -0700 Subject: [PATCH 065/118] Debug PHASE, multiline, and CNOT 0 0 tests. --- src/program/latex/mod.rs | 111 +++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 57 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 4ade3961..bcea5af1 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -126,9 +126,8 @@ impl Symbol { } } -/// Gates written in shorthand notation that may be decomposed into modifiers -/// and single gate instructions. - +/// Gates written in shorthand notation, i.e. composite form, that may be +/// decomposed into modifiers and single gate instructions, i.e. canonical form. enum CanonicalGate { /// `CNOT` is `CONTROLLED X` Cnot(String), @@ -375,8 +374,6 @@ impl Diagram { Ok(()) } - /// For every instruction containing control and target qubits this method - /// identifies which qubit is the target and which qubit is controlling it. /// The logic of this function is visualized using a physical vector with /// the tail at the control qubit and the head pointing to the target /// qubit. The distance between the qubits represents the number of wires @@ -385,17 +382,6 @@ impl Diagram { /// otherwise, it is negative. See Quantikz documentation on CNOT for some /// background that helps justify this approach. /// - /// This function is expensive with a time complexity of O(n^2). In the - /// worst case scenario every column contains a multi qubit gate with every - /// qubit as either a target or control. Quantikz uses the space between - /// wires to determine how long a line should stretch between control and - /// target qubits. Since it is impossible to determine how many wires will - /// be inserted between control and target qubits (e.g. a user decides to - /// impute missing qubits or some number of other instructions are added - /// containing qubits between them) for a custom body diagram, this method - /// can only be run after all wires are inserted into the cicuit. Only run - /// this method if a program contains multi qubit gates. - /// /// # Arguments /// `&mut self` - self as mutible allowing to update the circuit qubits fn set_ctrl_targ(&mut self) -> Result<(), LatexGenError> { @@ -408,16 +394,16 @@ impl Diagram { // from control to target, negative, from target to control. The // magnitude of the vector is the absolute value of the distance // between them - if let Some(last_qubit) = relationship.last() { - if let Qubit::Fixed(targ) = last_qubit { + if let Some(last) = relationship.last() { + if let Qubit::Fixed(targ) = last { + // any qubit before last in the relationship + let mut pred = None; + // distance between qubits is the space between the ctrl and // targ qubits in the circuit - for qubit in relationship { + for qubit in relationship.split(|q| q == last).next().unwrap() { if let Qubit::Fixed(ctrl) = qubit { - // found target qubit break loop - if qubit == last_qubit { - break; - } + pred = Some(ctrl); // represent inclusive [open, close] brackets of a range let mut open = None; // opening qubit in range @@ -458,6 +444,10 @@ impl Diagram { .and_then(|wire| wire.ctrl.insert(self.column, vector)); } } + // pred is None if relationship is split and no iterators are returned indicating this erroneous instruction + if pred.is_none() { + return Err(LatexGenError::FoundCNOTWithNoTarget); + } } } } @@ -538,9 +528,9 @@ impl Diagram { if let Some(wire) = self.circuit.get_mut(qubit) { // set the control and target qubits if let Some(relationship) = self.relationships.get(&self.column) { - // requires at least 2 qubits - if relationship.len() > 1 { - // target is the last qubit in the instruction + // requires at least 2 qubits or is a PHASE gate + if relationship.len() > 1 || gate.name == "PHASE" { + // target is the last qubit in the instruction or the qubit in PHASE if let Some(target) = relationship.last() { if fixed_qubit == target { wire.targ.insert(self.column, true); @@ -553,7 +543,7 @@ impl Diagram { } // set modifiers at this column for all qubits - wire.gates.insert(self.column, gate.name.clone()); + wire.gates.insert(self.column, gate.clone()); } } } @@ -601,51 +591,58 @@ impl Display for Diagram { } } - // is this a controlled X gate? - if gate == "X" && !wire.ctrl.is_empty() || !wire.targ.is_empty() { - // set qubit at this column as the control + if wire.ctrl.get(&c).is_some() { + // CONTROLLED qubits are displayed as `\ctrl{targ}` if let Some(targ) = wire.ctrl.get(&c) { write!( f, "{}", &Command::get_command(Command::Ctrl(targ.to_string())) )?; - // set the qubit at this column as the target - } else if wire.targ.get(&c).is_some() { - let mut x_gate = String::from("X"); + } + continue; + } else if wire.targ.get(&c).is_some() { + // CONTROLLED X gates are displayed as `\targ{}` + if gate.name == "X" { + // set the qubit at this column as the target + + let mut _gate = gate.name.clone(); // if the gate contains daggers, display target as X gate with dagger superscripts if !superscript.is_empty() { - x_gate.push_str(&superscript); - write!(f, "{}", &Command::get_command(Command::Gate(x_gate)))?; - // else display target as an open dot + _gate.push_str(&superscript); + write!(f, "{}", &Command::get_command(Command::Gate(_gate)))?; + // else display X target as an open dot } else { write!(f, "{}", &Command::get_command(Command::Targ))?; } - } - // PHASE gates are displayed as `\phase{param}` - } else if gate.contains("PHASE") { - // set the phase parameters - if let Some(parameters) = wire.parameters.get(&c) { - for param in parameters { - write!( - f, - "{}", - &Command::get_command(Command::Phase(param.to_string())) - )?; + continue; + // PHASE gates are displayed as `\phase{param}` + } else if gate.name == "PHASE" { + // set the phase parameters + if let Some(parameters) = wire.parameters.get(&c) { + for param in parameters { + write!( + f, + "{}", + &Command::get_command(Command::Phase( + param.to_string() + )) + )?; + } } + continue; } + } // all other gates display as `\gate{name}` - } else { - let mut gate = String::from(gate); + let mut _gate = gate.name.clone(); - // concatenate superscripts - if !superscript.is_empty() { - gate.push_str(&superscript); - } - - write!(f, "{}", &Command::get_command(Command::Gate(gate)))?; + // concatenate superscripts + if !superscript.is_empty() { + _gate.push_str(&superscript); } + + write!(f, "{}", &Command::get_command(Command::Gate(_gate)))?; } else if wire.empty.get(&c).is_some() { // chain an empty column qw to the end of the line write!(f, " & ")?; @@ -686,7 +683,7 @@ impl Display for Diagram { #[derive(Clone, Debug, Default)] struct Wire { /// gate elements placed at column on wire using the Gate command - gates: HashMap, + gates: HashMap, /// control at column with distance from targ wire ctrl: HashMap, /// at this column is the wire a target? @@ -841,7 +838,7 @@ mod tests { /// Test functionality of to_latex using default settings. fn test_to_latex() { let latex = get_latex( - r#"CONTROLLED CNOT 0 3"#, + "H 0\nCNOT 0 1", RenderSettings { impute_missing_qubits: true, ..Default::default() From 61f990ac6c90adb89eb3c014e60b5c590826b53a Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 22 Mar 2023 14:41:05 -0700 Subject: [PATCH 066/118] Handle unsupported instructions and gates. --- src/program/latex/mod.rs | 80 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index bcea5af1..dfeb0baa 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -539,6 +539,11 @@ impl Diagram { wire.ctrl.insert(self.column, 0); } } + } else if wire.parameters.get(&self.column).is_some() { + // parameterized single qubit gates are unsupported + return Err(LatexGenError::UnsupportedGate { + gate: gate.name.clone(), + }); } } @@ -733,6 +738,10 @@ pub enum LatexGenError { FoundCNOTWithNoTarget, #[error("The FORKED modifier is unsupported.")] UnsupportedModifierForked, + #[error("This instruction is unsupported: {instruction}.")] + UnsupportedInstruction { instruction: String }, + #[error("This gate is unsupported: {gate}.")] + UnsupportedGate { gate: String }, } pub trait Latex { @@ -807,6 +816,12 @@ impl Latex for Program { diagram.parse_gate(&gate)?; diagram.set_ctrl_targ()?; diagram.column += 1; + } else if let Instruction::GateDefinition(_) = instruction { + // GateDefinition is supported and parsed in Gate + } else { + return Err(LatexGenError::UnsupportedInstruction { + instruction: instruction.to_string(), + }); } } @@ -1206,4 +1221,69 @@ ______________________________________ugly-python-convention____________________ insta::assert_snapshot!(latex); } } + + /// Test module for unsupported programs (remove #[should_panic] and move + /// unit test to programs test module when supported) + mod unsupported { + use crate::program::latex::{tests::get_latex, RenderSettings}; + + #[test] + #[should_panic] + fn test_supported_misc_instructions() { + get_latex("NOP\nWAIT\nRESET\nHALT", RenderSettings::default()); + } + + #[test] + #[should_panic] + fn test_supported_measure() { + get_latex( + "DECLARE ro BIT\nMEASURE 0\nMEASURE 1 ro[0]", + RenderSettings::default(), + ); + } + + #[test] + #[should_panic] + fn test_supported_program_defcircuit() { + get_latex( + r#"DEFCIRCUIT EULER(%alpha, %beta, %gamma) q: + RX(%alpha) q + RY(%beta) q + RZ(%gamma) q +EULER(pi, 2*pi, 3*pi/2) 0"#, + RenderSettings::default(), + ); + } + + #[test] + #[should_panic] + fn test_supported_gate_rotation() { + get_latex( + r#"DECLARE ro BIT[1] +DECLARE theta REAL[1] +RX(pi/2) 0 +RZ(theta) 0 +RY(-pi/2) 0"#, + RenderSettings::default(), + ); + } + + #[test] + #[should_panic] + fn test_supported_arithmetic_instruction() { + get_latex( + "DECLARE b BIT\nDECLARE theta REAL\nMOVE theta -3.14\nLT b theta -3.14", + RenderSettings::default(), + ); + } + + #[test] + #[should_panic] + fn test_supported_modifier_forked() { + get_latex( + "FORKED CONTROLLED FORKED RX(a,b,c,d) 0 1 2 3", + RenderSettings::default(), + ); + } + } } From 1af1a9feb960ee26c61ed50a1d572a9aab1f4e10 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 22 Mar 2023 15:57:18 -0700 Subject: [PATCH 067/118] Update docs and readability. --- src/program/latex/mod.rs | 118 ++++++++++++++++++++------------------- 1 file changed, 60 insertions(+), 58 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index dfeb0baa..dd68d1ea 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -2,12 +2,13 @@ //! //! This module enables generating quantum circuits using the LaTeX subpackage //! TikZ/[`Quantikz`] for a given quil [`Program`]. This feature is callable on -//! [`Program`] (see usage below) and returns a LaTeX string which can be -//! rendered in a LaTeX visualization tool. Be aware that not all Programs can -//! be serialized as LaTeX. If a [`Program`] contains a gate or modifier that -//! has not been implemented in the [Supported Gates and Modifiers] -//! (#supported-gates-and-modifiers) section below, unexpected results may -//! occur, one of which includes producing incorrect quantum circuits. +//! [`Program`] and returns a LaTeX string which can be rendered in a LaTeX +//! visualization tool. Be aware that not all Programs can be serialized as +//! LaTeX. If a [`Program`] contains a gate or modifier not mentioned in the +//! [Supported Gates and Modifiers](#supported-gates-and-modifiers) section +//! below, unexpected results may occur, one of which includes producing +//! incorrect quantum circuits, or an error will be returned detailing which +//! instruction or gate is unsupported in the Program being processed. //! //! # Supported Gates and Modifiers //! @@ -32,8 +33,11 @@ use crate::Program; /// Available commands used for building circuits with the same names taken /// from the Quantikz documentation for easy reference. LaTeX string denoted /// inside `backticks`. -/// Single wire commands: lstick, rstick, qw, meter -/// Multi-wire commands: ctrl, targ, control, (swap, targx) +/// +/// # Available Commands +/// +/// Single wire commands: lstick, gate, phase, super, qw, nr +/// Multi-wire commands: ctrl, targ #[derive(Clone, Debug)] enum Command { /// `\lstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the left. @@ -137,11 +141,16 @@ enum CanonicalGate { Cphase(String), /// `CZ` is `CONTROLLED Z` Cz(String), - /// gate is in canonical form or is unsupported + /// gate is in canonical form None, } impl CanonicalGate { + /// Get the canonical form of a composite gate from a gate name within an + /// Instruction. + /// + /// # Arguments + /// `gate_name` - reference to a gate name from an Instruction fn get_canonical(gate_name: &str) -> CanonicalGate { match gate_name { "CNOT" => CanonicalGate::Cnot(String::from("CONTROLLED X")), @@ -192,21 +201,13 @@ impl Default for RenderSettings { } impl RenderSettings { - /// Returns a label as the qubit name to the left side of the wire. - /// - /// # Arguments - /// `name` - name of the qubit - fn label_qubit_lines(&self, name: u64) -> String { - Command::get_command(Command::Lstick(name.to_string())) - } - /// Adds missing qubits between the first qubit and last qubit in a /// diagram's circuit. If a missing qubit is found, a new wire is created /// and pushed to the diagram's circuit. /// /// # Arguments - /// `column` - the length of instructions from Program - /// `&mut BTreeMap> circuit` - the circuit of the diagram + /// `last_column` - total number of instructions from Program + /// `circuit` - the circuit of the diagram /// /// # Examples /// ``` @@ -219,7 +220,7 @@ impl RenderSettings { /// }; /// program.to_latex(settings).expect(""); /// ``` - fn impute_missing_qubits(&self, column: u32, circuit: &mut BTreeMap>) { + fn impute_missing_qubits(last_column: u32, circuit: &mut BTreeMap>) { // requires at least two qubits to impute missing qubits if circuit.len() < 2 { return; @@ -248,7 +249,7 @@ impl RenderSettings { }; // insert empties based on total number of columns - for c in 0..=column { + for c in 0..=last_column { wire.empty.insert(c, Command::Qw); } @@ -297,7 +298,7 @@ impl Display for Document { /// of the wires in the circuit BTreeMap. Diagram tracks relationships between /// wires with two pieces of information--1. the wires row (its order in the /// BTreeMap), and 2. the column that spreads between all wires that pass -/// through a multi qubit gate 'e.g. CNOT'. The size of the diagram can be +/// through a multi qubit gate, e.g. CNOT. The size of the diagram can be /// measured by multiplying the column with the length of the circuit. This is /// an [m x n] matrix where each element in the matrix represents an item to be /// rendered onto the diagram using one of the Quantikz commands. @@ -321,9 +322,9 @@ impl Diagram { /// qubit wire of the circuit. /// /// # Arguments - /// `&mut self` - exposes the Circuit - /// `qubits` - qubits used in the quil program - /// `instruction` - exposes qubits in a single instruction + /// `&mut self` - exposes the wires on the Circuit + /// `qubits` - exposes the qubits used in the Program + /// `instruction` - exposes the qubits in a single Instruction fn set_qw(&mut self, qubits: &HashSet, instruction: &Instruction) { 'program_loop: for program_qubit in qubits { if let Instruction::Gate(gate) = instruction { @@ -346,8 +347,8 @@ impl Diagram { /// current column. Returns an Err for unsupported modifiers. /// /// # Arguments + /// `wire` - an exposed wire on the Circuit /// `column` - the current column of the Circuit - /// `wire` - a wire on the Circuit /// `modifiers` - the modifiers from the Gate fn set_modifiers( wire: &mut Wire, @@ -357,7 +358,7 @@ impl Diagram { // set modifers for modifier in modifiers { match modifier { - // return error for FORKED + // return error for unsupported modifier FORKED GateModifier::Forked => { return Err(LatexGenError::UnsupportedModifierForked); } @@ -374,17 +375,18 @@ impl Diagram { Ok(()) } - /// The logic of this function is visualized using a physical vector with - /// the tail at the control qubit and the head pointing to the target - /// qubit. The distance between the qubits represents the number of wires - /// between them, i.e the space that the vector needs to traverse. If the - /// control qubit comes before the target qubit the direction is positive, - /// otherwise, it is negative. See Quantikz documentation on CNOT for some - /// background that helps justify this approach. + /// Locates the target qubit for the control qubits. The location of a + /// target qubit is found using its displacement from the control qubits + /// which is a physical vector with the tail at the control qubit and the + /// head pointing to the target qubit. The distance between the qubits + /// represents the number of wires between them, i.e the space that the + /// vector needs to traverse. If the control qubit comes before the target + /// qubit the direction is positive, otherwise, it is negative. See + /// Quantikz documentation on CNOT for justification on this approach. /// /// # Arguments - /// `&mut self` - self as mutible allowing to update the circuit qubits - fn set_ctrl_targ(&mut self) -> Result<(), LatexGenError> { + /// `&mut self` - exposes diagram relationships and the circuit + fn locate_targ(&mut self) -> Result<(), LatexGenError> { if let Some(relationship) = self.relationships.get(&self.column) { // requires at least two qubits if relationship.len() < 2 { @@ -455,14 +457,15 @@ impl Diagram { Ok(()) } - /// Analyzes a Gate from an instruction and sets the gate at this column on + /// Analyzes a Gate from an Instruction and sets the gate at this column on /// the wire. If the gate name is a composite gate, the gate name is - /// decomposed into canonical form. For example, CNOT is a composite gate - /// that can be decomposed into the equivalent canonical form, CONTROLLED X. + /// decomposed into canonical form before being set in the circuit. For + /// example, CNOT is a composite gate that can be decomposed into the + /// equivalent canonical form, CONTROLLED X. /// /// # Arguments - /// `column` - the current empty column to set the gate at - /// `gate` - the gate of the instruction being parsed in to_latex + /// `self` - exposes all attributes in the diagram + /// `gate` - the Gate of the Instruction from `to_latex`. fn parse_gate(&mut self, gate: &Gate) -> Result<(), LatexGenError> { // preserve qubit order in instruction self.relationships.insert(self.column, gate.qubits.clone()); @@ -572,7 +575,11 @@ impl Display for Diagram { // are labels on in settings? if self.settings.label_qubit_lines { // add label to left side of wire - write!(f, "{}", &self.settings.label_qubit_lines(*key))?; + write!( + f, + "{}", + Command::get_command(Command::Lstick(key.to_string())) + )?; } else { // add qw buffer to first column write!(f, "{}", &Command::get_command(Command::Qw))?; @@ -687,15 +694,15 @@ impl Display for Diagram { /// describes how far away it is from the related qubit based on Quantikz. #[derive(Clone, Debug, Default)] struct Wire { - /// gate elements placed at column on wire using the Gate command + /// the Gates on the wire callable by the column gates: HashMap, - /// control at column with distance from targ wire + /// at this column the wire is a control ctrl: HashMap, /// at this column is the wire a target? targ: HashMap, - /// any parameters required at column on the wire for gates + /// the Parameters on the wire callable by the column parameters: HashMap>, - /// total number of controlled modifiers added to the wire at this column + /// the Modifiers on the wire callable by the column modifiers: HashMap>, /// empty column empty: HashMap, @@ -708,7 +715,7 @@ impl Wire { /// /// # Arguments /// `&mut self` - exposes the Wire's parameters at this column - /// `expression` - expression from Program to get name of parameter + /// `expression` - expression from Program to get name of Parameter /// `column` - the column taking the parameters /// `texify` - is texify_numerical_constants setting on? fn set_param(&mut self, expression: &Expression, column: u32, texify: bool) { @@ -749,16 +756,14 @@ pub trait Latex { } impl Latex for Program { + /// Returns a Result containing a quil [`Program`] as a LaTeX string or a + /// [`LatexGenError`] defined using thiserror. + /// /// This implementation of Latex can be viewed as a self-contained partial /// implementation of ``Quantikz`` with all available commands listed as /// variants in a Command enum. View ``Quantikz`` documentation for more /// information. /// - /// This function returns a Result containing a quil [`Program`] as a LaTeX - /// string or a [`LatexGenError`] defined using thiserror. Called on a - /// Program, the function starts with a check to ensure the [`Program`] - /// contains supported gates and modifers that can be serialized to LaTeX. - /// /// # Arguments /// `settings` - Customizes the rendering of a circuit. /// @@ -802,9 +807,7 @@ impl Latex for Program { // are implicit qubits required in settings and are there at least two or more qubits in the diagram? if diagram.settings.impute_missing_qubits { // add implicit qubits to circuit - diagram - .settings - .impute_missing_qubits(instructions.len() as u32, &mut diagram.circuit); + RenderSettings::impute_missing_qubits(instructions.len() as u32, &mut diagram.circuit); } for instruction in instructions { @@ -814,7 +817,7 @@ impl Latex for Program { // parse gate instructions into a new circuit if let Instruction::Gate(gate) = instruction { diagram.parse_gate(&gate)?; - diagram.set_ctrl_targ()?; + diagram.locate_targ()?; diagram.column += 1; } else if let Instruction::GateDefinition(_) = instruction { // GateDefinition is supported and parsed in Gate @@ -1222,8 +1225,7 @@ ______________________________________ugly-python-convention____________________ } } - /// Test module for unsupported programs (remove #[should_panic] and move - /// unit test to programs test module when supported) + /// Test module for unsupported programs mod unsupported { use crate::program::latex::{tests::get_latex, RenderSettings}; From 029f05b6c251b8e1195fa5e63212d6f563c19a2d Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 23 Mar 2023 11:36:55 -0700 Subject: [PATCH 068/118] Refactor gate setting from parse_gate into separate method set_gate. --- src/program/latex/mod.rs | 79 ++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index dd68d1ea..d00ad3c3 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -523,35 +523,13 @@ impl Diagram { self.parse_gate(&gate)?; } } - // gate is in canonical form, build wire + // gate is in canonical form } else { - // set gates - for fixed_qubit in &gate.qubits { - if let Qubit::Fixed(qubit) = fixed_qubit { - if let Some(wire) = self.circuit.get_mut(qubit) { - // set the control and target qubits - if let Some(relationship) = self.relationships.get(&self.column) { - // requires at least 2 qubits or is a PHASE gate - if relationship.len() > 1 || gate.name == "PHASE" { - // target is the last qubit in the instruction or the qubit in PHASE - if let Some(target) = relationship.last() { - if fixed_qubit == target { - wire.targ.insert(self.column, true); - // all other qubits are controls - } else { - wire.ctrl.insert(self.column, 0); - } - } - } else if wire.parameters.get(&self.column).is_some() { - // parameterized single qubit gates are unsupported - return Err(LatexGenError::UnsupportedGate { - gate: gate.name.clone(), - }); - } - } - - // set modifiers at this column for all qubits - wire.gates.insert(self.column, gate.clone()); + // set gate for each qubit + for qubit in &gate.qubits { + if let Qubit::Fixed(fixed_qubit) = qubit { + if let Some(wire) = self.circuit.get_mut(fixed_qubit) { + wire.set_gate(self.column, gate, qubit, &self.relationships)?; } } } @@ -737,6 +715,51 @@ impl Wire { self.parameters.insert(column, param); } + + /// Returns a result indicating the gate was successfully set for this Wire + /// or an error indicating the gate is unsupported. If this is a + /// multi-qubit gate, as indicated by the number of qubits in the + /// relationship, then, depending on its position in the relationship, is + /// set as a target or a control qubit. + /// + /// # Arguments + /// `&mut self` - exposes this Wire's gates at this column + /// `column` - the column taking the gate + /// `gate` - the gate attempted to be set + /// `relationships` - the qubits at this column in a relationship + fn set_gate( + &mut self, + column: u32, + gate: &Gate, + qubit: &Qubit, + relationships: &HashMap>, + ) -> Result<(), LatexGenError> { + // set the control and target qubits + if let Some(relationship) = relationships.get(&column) { + // requires at least 2 qubits or is a PHASE gate + if relationship.len() > 1 || gate.name == "PHASE" { + // target is the last qubit in the instruction or the qubit in PHASE + if let Some(target) = relationship.last() { + if qubit == target { + self.targ.insert(column, true); + // all other qubits are controls + } else { + self.ctrl.insert(column, 0); + } + } + } else if self.parameters.get(&column).is_some() { + // parameterized single qubit gates are unsupported + return Err(LatexGenError::UnsupportedGate { + gate: gate.name.clone(), + }); + } + } + + // set modifiers at this column for all qubits + self.gates.insert(column, gate.clone()); + + Ok(()) + } } #[derive(Clone, Debug, thiserror::Error)] From 421e7ebab6ca045b7e2b180d7e8bba121621340f Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 23 Mar 2023 12:39:39 -0700 Subject: [PATCH 069/118] Rename feature to ToLatex, set_qw to set_empty, make enums more idiomatic. --- src/program/latex/mod.rs | 96 ++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 58 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index d00ad3c3..8c9a196e 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -58,9 +58,9 @@ enum Command { Targ, } -impl Command { - fn get_command(command: Self) -> String { - match command { +impl ToString for Command { + fn to_string(&self) -> String { + match self { Self::Lstick(wire) => format!(r#"\lstick{{\ket{{q_{{{wire}}}}}}}"#), Self::Gate(name) => format!(r#"\gate{{{name}}}"#), Self::Phase(symbol) => format!(r#"\phase{{{symbol}}}"#), @@ -112,13 +112,8 @@ impl ToString for Symbol { } } -impl Symbol { - /// Returns the supported Symbol variant from text, otherwise, stores the - /// unsupported symbol as text in the Text variant. - /// - /// # Arguments - /// `text` - a String representing a Greek or alphanumeric symbol - fn match_symbol(text: String) -> Symbol { +impl From for Symbol { + fn from(text: String) -> Self { match text.as_str() { "alpha" => Symbol::Alpha, "beta" => Symbol::Beta, @@ -145,14 +140,9 @@ enum CanonicalGate { None, } -impl CanonicalGate { - /// Get the canonical form of a composite gate from a gate name within an - /// Instruction. - /// - /// # Arguments - /// `gate_name` - reference to a gate name from an Instruction - fn get_canonical(gate_name: &str) -> CanonicalGate { - match gate_name { +impl From for CanonicalGate { + fn from(gate_name: String) -> Self { + match gate_name.as_str() { "CNOT" => CanonicalGate::Cnot(String::from("CONTROLLED X")), "CCNOT" => CanonicalGate::Ccnot(String::from("CONTROLLED CONTROLLED X")), "CPHASE" => CanonicalGate::Cphase(String::from("CONTROLLED PHASE")), @@ -319,13 +309,13 @@ impl Diagram { /// the circuit to all of the qubits used in the quil program. If a qubit /// from the quil program is not found in the qubits in the single /// instruction line, then an empty slot is added to that column on the - /// qubit wire of the circuit. + /// qubit wire of the circuit indicating a "do nothing" at that column. /// /// # Arguments /// `&mut self` - exposes the wires on the Circuit /// `qubits` - exposes the qubits used in the Program /// `instruction` - exposes the qubits in a single Instruction - fn set_qw(&mut self, qubits: &HashSet, instruction: &Instruction) { + fn set_empty(&mut self, qubits: &HashSet, instruction: &Instruction) { 'program_loop: for program_qubit in qubits { if let Instruction::Gate(gate) = instruction { for gate_qubit in &gate.qubits { @@ -490,7 +480,7 @@ impl Diagram { } // parse the gate to a canonical gate if supported - let canonical_gate = match CanonicalGate::get_canonical(&gate.name) { + let canonical_gate = match CanonicalGate::from(gate.name.to_string()) { CanonicalGate::Cnot(inst) => Some(inst), CanonicalGate::Ccnot(inst) => Some(inst), CanonicalGate::Cphase(inst) => Some(inst), @@ -553,14 +543,10 @@ impl Display for Diagram { // are labels on in settings? if self.settings.label_qubit_lines { // add label to left side of wire - write!( - f, - "{}", - Command::get_command(Command::Lstick(key.to_string())) - )?; + write!(f, "{}", Command::Lstick(key.to_string()).to_string())?; } else { // add qw buffer to first column - write!(f, "{}", &Command::get_command(Command::Qw))?; + write!(f, "{}", Command::Qw.to_string())?; } // convert each column in the wire to string @@ -574,9 +560,9 @@ impl Display for Diagram { if let Some(modifiers) = wire.modifiers.get(&c) { for modifier in modifiers { if let GateModifier::Dagger = modifier { - superscript.push_str(&Command::get_command(Command::Super( - String::from("dagger"), - ))) + superscript.push_str( + &Command::Super(String::from("dagger")).to_string(), + ) } } } @@ -584,11 +570,7 @@ impl Display for Diagram { if wire.ctrl.get(&c).is_some() { // CONTROLLED qubits are displayed as `\ctrl{targ}` if let Some(targ) = wire.ctrl.get(&c) { - write!( - f, - "{}", - &Command::get_command(Command::Ctrl(targ.to_string())) - )?; + write!(f, "{}", &(Command::Ctrl(targ.to_string())).to_string())?; } continue; } else if wire.targ.get(&c).is_some() { @@ -601,10 +583,10 @@ impl Display for Diagram { // if the gate contains daggers, display target as X gate with dagger superscripts if !superscript.is_empty() { _gate.push_str(&superscript); - write!(f, "{}", &Command::get_command(Command::Gate(_gate)))?; + write!(f, "{}", &Command::Gate(_gate).to_string())?; // else display X target as an open dot } else { - write!(f, "{}", &Command::get_command(Command::Targ))?; + write!(f, "{}", &Command::Targ.to_string())?; } continue; // PHASE gates are displayed as `\phase{param}` @@ -615,9 +597,7 @@ impl Display for Diagram { write!( f, "{}", - &Command::get_command(Command::Phase( - param.to_string() - )) + &Command::Phase(param.to_string()).to_string() )?; } } @@ -632,24 +612,24 @@ impl Display for Diagram { _gate.push_str(&superscript); } - write!(f, "{}", &Command::get_command(Command::Gate(_gate)))?; + write!(f, "{}", &Command::Gate(_gate).to_string())?; } else if wire.empty.get(&c).is_some() { // chain an empty column qw to the end of the line write!(f, " & ")?; - write!(f, "{}", &Command::get_command(Command::Qw))?; + write!(f, "{}", &Command::Qw.to_string())?; } } } // chain an empty column qw to the end of the line write!(f, " & ")?; - write!(f, "{}", &Command::get_command(Command::Qw))?; + write!(f, "{}", &Command::Qw.to_string())?; // if this is the last key iteration, omit Nr from end of line if i < self.circuit.len() - 1 { // indicate a new row write!(f, " ")?; - write!(f, "{}", &Command::get_command(Command::Nr))?; + write!(f, "{}", &Command::Nr.to_string())?; i += 1; } @@ -707,7 +687,7 @@ impl Wire { // if texify_numerical_constants let param = if texify { // get the matching symbol from text - vec![Parameter::Symbol(Symbol::match_symbol(text))] + vec![Parameter::Symbol(text.into())] } else { // set the symbol as text vec![Parameter::Symbol(Symbol::Text(text))] @@ -774,13 +754,13 @@ pub enum LatexGenError { UnsupportedGate { gate: String }, } -pub trait Latex { +pub trait ToLatex { fn to_latex(self, settings: RenderSettings) -> Result; } -impl Latex for Program { +impl ToLatex for Program { /// Returns a Result containing a quil [`Program`] as a LaTeX string or a - /// [`LatexGenError`] defined using thiserror. + /// [`LatexGenError`]. /// /// This implementation of Latex can be viewed as a self-contained partial /// implementation of ``Quantikz`` with all available commands listed as @@ -835,7 +815,7 @@ impl Latex for Program { for instruction in instructions { // set QW for any unused qubits in this instruction - diagram.set_qw(&qubits, &instruction); + diagram.set_empty(&qubits, &instruction); // parse gate instructions into a new circuit if let Instruction::Gate(gate) = instruction { @@ -862,7 +842,7 @@ impl Latex for Program { #[cfg(test)] mod tests { - use super::{Latex, RenderSettings}; + use super::{RenderSettings, ToLatex}; use crate::Program; use std::str::FromStr; @@ -1040,42 +1020,42 @@ mod tests { #[test] fn test_command_left_ket() { - insta::assert_snapshot!(Command::get_command(Command::Lstick("0".to_string()))); + insta::assert_snapshot!(Command::Lstick("0".to_string()).to_string()); } #[test] fn test_command_gate() { - insta::assert_snapshot!(Command::get_command(Command::Gate("X".to_string()))); + insta::assert_snapshot!(Command::Gate("X".to_string()).to_string()); } #[test] fn test_command_phase() { - insta::assert_snapshot!(Command::get_command(Command::Phase(Symbol::Pi.to_string()))); + insta::assert_snapshot!(Command::Phase(Symbol::Pi.to_string()).to_string()); } #[test] fn test_command_super() { - insta::assert_snapshot!(Command::get_command(Command::Super("dagger".to_string()))); + insta::assert_snapshot!(Command::Super("dagger".to_string()).to_string()); } #[test] fn test_command_qw() { - insta::assert_snapshot!(Command::get_command(Command::Qw)); + insta::assert_snapshot!(Command::Qw.to_string()); } #[test] fn test_command_nr() { - insta::assert_snapshot!(Command::get_command(Command::Nr)); + insta::assert_snapshot!(Command::Nr.to_string()); } #[test] fn test_command_control() { - insta::assert_snapshot!(Command::get_command(Command::Ctrl("0".to_string()))); + insta::assert_snapshot!(Command::Ctrl("0".to_string()).to_string()); } #[test] fn test_command_cnot_target() { - insta::assert_snapshot!(Command::get_command(Command::Targ)); + insta::assert_snapshot!(Command::Targ.to_string()); } } From cab0cafb02dd0322191350cff223e0fe5905a2e2 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 23 Mar 2023 12:52:48 -0700 Subject: [PATCH 070/118] Update doctest examples. --- src/program/latex/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 8c9a196e..e1e3ef24 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -201,7 +201,7 @@ impl RenderSettings { /// /// # Examples /// ``` - /// use quil_rs::{Program, program::latex::{RenderSettings, Latex}}; + /// use quil_rs::{Program, program::latex::{RenderSettings, ToLatex}}; /// use std::str::FromStr; /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); /// let settings = RenderSettings { @@ -773,7 +773,7 @@ impl ToLatex for Program { /// # Examples /// ``` /// // To LaTeX for the Bell State Program. - /// use quil_rs::{Program, program::latex::{RenderSettings, Latex}}; + /// use quil_rs::{Program, program::latex::{RenderSettings, ToLatex}}; /// use std::str::FromStr; /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); /// let latex = program.to_latex(RenderSettings::default()).expect(""); @@ -781,7 +781,7 @@ impl ToLatex for Program { /// /// ``` /// // To LaTeX for the Toffoli Gate Program. - /// use quil_rs::{Program, program::latex::{RenderSettings, Latex}}; + /// use quil_rs::{Program, program::latex::{RenderSettings, ToLatex}}; /// use std::str::FromStr; /// let program = Program::from_str("CONTROLLED CNOT 2 1 0").expect(""); /// let latex = program.to_latex(RenderSettings::default()).expect(""); From 571c497e30ff6a7a9816506115a545dff6807c8a Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 23 Mar 2023 13:06:39 -0700 Subject: [PATCH 071/118] Rename err variant FoundCNOTWithNoTarget to FoundTargetWithNoControl. --- src/program/latex/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index e1e3ef24..ff634836 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -438,7 +438,7 @@ impl Diagram { } // pred is None if relationship is split and no iterators are returned indicating this erroneous instruction if pred.is_none() { - return Err(LatexGenError::FoundCNOTWithNoTarget); + return Err(LatexGenError::FoundTargetWithNoControl); } } } @@ -744,8 +744,8 @@ impl Wire { #[derive(Clone, Debug, thiserror::Error)] pub enum LatexGenError { - #[error("Tried to parse CNOT and found a control qubit without a target.")] - FoundCNOTWithNoTarget, + #[error("Found a target qubit with no control qubit.")] + FoundTargetWithNoControl, #[error("The FORKED modifier is unsupported.")] UnsupportedModifierForked, #[error("This instruction is unsupported: {instruction}.")] From 0af276933f5e341c234005bda348e0906c6624c5 Mon Sep 17 00:00:00 2001 From: Ryan Meneses <58452495+hiyaryan@users.noreply.github.com> Date: Fri, 24 Mar 2023 14:26:14 -0700 Subject: [PATCH 072/118] Refactor impute_missing_qubits and set_empty to use built-ins from std. Co-authored-by: Marquess Valdez --- src/program/latex/mod.rs | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index ff634836..eb2734cc 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -219,16 +219,14 @@ impl RenderSettings { // get the first qubit in the BTreeMap let first = circuit .first_key_value() - .expect("previously checked that circuit is not empty") - .0 - + 1; + .map(|wire| wire.0 + 1) + .expect("previously checked that circuit is not empty"); // get the last qubit in the BTreeMap let last = circuit .last_key_value() + .map(|wire| wire.0 - 1) .expect("previously checked that circuit has at least two wires") - .0 - - 1; // search through the range of qubits for qubit in first..=last { @@ -316,21 +314,20 @@ impl Diagram { /// `qubits` - exposes the qubits used in the Program /// `instruction` - exposes the qubits in a single Instruction fn set_empty(&mut self, qubits: &HashSet, instruction: &Instruction) { - 'program_loop: for program_qubit in qubits { - if let Instruction::Gate(gate) = instruction { - for gate_qubit in &gate.qubits { - if program_qubit == gate_qubit { - continue 'program_loop; - } - } - - if let Qubit::Fixed(q) = program_qubit { - if let Some(wire) = self.circuit.get_mut(q) { - wire.empty.insert(self.column, Command::Qw); - } - } - } + if let Instruction::Gate(gate) = instruction { + qubits + .difference(&gate.qubits.iter().cloned().collect()) + .filter_map(|q| match q { + Qubit::Fixed(index) => Some(index), + _ => None, + }) + .for_each(|index| { + self.circuit + .get_mut(index) + .map(|wire| wire.empty.insert(self.column, Command::Qw)); + }); } + } /// Utility function to insert modifiers of wires in this Circuit at the From 712a24c5144ab07f76c791cbe935cc4031e090b6 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 24 Mar 2023 14:29:14 -0700 Subject: [PATCH 073/118] Add missing semicolon and fix format. --- src/program/latex/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index eb2734cc..81319df5 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -226,7 +226,7 @@ impl RenderSettings { let last = circuit .last_key_value() .map(|wire| wire.0 - 1) - .expect("previously checked that circuit has at least two wires") + .expect("previously checked that circuit has at least two wires"); // search through the range of qubits for qubit in first..=last { @@ -327,7 +327,6 @@ impl Diagram { .map(|wire| wire.empty.insert(self.column, Command::Qw)); }); } - } /// Utility function to insert modifiers of wires in this Circuit at the From c353c3fe8565ffec1813eb3d01fa2c985c5c0667 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 27 Mar 2023 18:23:06 -0700 Subject: [PATCH 074/118] Organize code by restructuring design. --- src/program/latex/mod.rs | 224 ++++++++++++++------------------------- 1 file changed, 82 insertions(+), 142 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 81319df5..85751a1b 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -6,9 +6,8 @@ //! visualization tool. Be aware that not all Programs can be serialized as //! LaTeX. If a [`Program`] contains a gate or modifier not mentioned in the //! [Supported Gates and Modifiers](#supported-gates-and-modifiers) section -//! below, unexpected results may occur, one of which includes producing -//! incorrect quantum circuits, or an error will be returned detailing which -//! instruction or gate is unsupported in the Program being processed. +//! below, an error will be returned detailing which instruction or gate is +//! unsupported in the Program being processed. //! //! # Supported Gates and Modifiers //! @@ -296,8 +295,6 @@ struct Diagram { settings: RenderSettings, /// total number of elements on each wire column: u32, - /// column at which qubits in positional order form relationships - relationships: HashMap>, /// a BTreeMap of wires with the name of the wire as the key circuit: BTreeMap>, } @@ -361,101 +358,16 @@ impl Diagram { Ok(()) } - /// Locates the target qubit for the control qubits. The location of a - /// target qubit is found using its displacement from the control qubits - /// which is a physical vector with the tail at the control qubit and the - /// head pointing to the target qubit. The distance between the qubits - /// represents the number of wires between them, i.e the space that the - /// vector needs to traverse. If the control qubit comes before the target - /// qubit the direction is positive, otherwise, it is negative. See - /// Quantikz documentation on CNOT for justification on this approach. - /// - /// # Arguments - /// `&mut self` - exposes diagram relationships and the circuit - fn locate_targ(&mut self) -> Result<(), LatexGenError> { - if let Some(relationship) = self.relationships.get(&self.column) { - // requires at least two qubits - if relationship.len() < 2 { - return Ok(()); - } - // determine the physical vector where a positive vector points - // from control to target, negative, from target to control. The - // magnitude of the vector is the absolute value of the distance - // between them - if let Some(last) = relationship.last() { - if let Qubit::Fixed(targ) = last { - // any qubit before last in the relationship - let mut pred = None; - - // distance between qubits is the space between the ctrl and - // targ qubits in the circuit - for qubit in relationship.split(|q| q == last).next().unwrap() { - if let Qubit::Fixed(ctrl) = qubit { - pred = Some(ctrl); - - // represent inclusive [open, close] brackets of a range - let mut open = None; // opening qubit in range - let mut close = None; // closing qubit in range - - // find the range between the qubits - for (i, wire) in self.circuit.iter().enumerate() { - // get each existing qubit in the circuit - if *wire.0 == *ctrl || *wire.0 == *targ { - // if the qubit is the ctrl or target - if open.is_some() { - close = Some(i); - break; - - // open qubit in range not found, set open qubit - } else { - open = Some(i) - } - } - } - - let mut vector: i64 = 0; - if let Some(open) = open { - if let Some(close) = close { - if ctrl < targ { - // a vector with a head from the ctrl to the targ - vector = (close as i64) - (open as i64); - } else { - // a vector with a head from the targ to the ctrl - vector = -((close as i64) - (open as i64)); - } - } - } - // set wire at column as the control qubit of target qubit - // computed as the distance from the control qubit - self.circuit - .get_mut(ctrl) - .and_then(|wire| wire.ctrl.insert(self.column, vector)); - } - } - // pred is None if relationship is split and no iterators are returned indicating this erroneous instruction - if pred.is_none() { - return Err(LatexGenError::FoundTargetWithNoControl); - } - } - } - } - - Ok(()) - } - - /// Analyzes a Gate from an Instruction and sets the gate at this column on - /// the wire. If the gate name is a composite gate, the gate name is - /// decomposed into canonical form before being set in the circuit. For - /// example, CNOT is a composite gate that can be decomposed into the - /// equivalent canonical form, CONTROLLED X. + /// Inserts a gate from an instruction on the wires in the circuit + /// associate with the qubits from the gate. If the gate name is a + /// composite gate, the gate name is decomposed into canonical form before + /// being set in the circuit. For example, CNOT is a composite gate that + /// can be decomposed into the equivalent canonical form, CONTROLLED X. /// /// # Arguments /// `self` - exposes all attributes in the diagram /// `gate` - the Gate of the Instruction from `to_latex`. - fn parse_gate(&mut self, gate: &Gate) -> Result<(), LatexGenError> { - // preserve qubit order in instruction - self.relationships.insert(self.column, gate.qubits.clone()); - + fn insert_gate(&mut self, gate: &Gate) -> Result<(), LatexGenError> { // set modifiers from gate instruction for qubit in &gate.qubits { if let Qubit::Fixed(qubit) = qubit { @@ -506,16 +418,41 @@ impl Diagram { for instruction in instructions { if let Instruction::Gate(gate) = instruction { // call until all composite gates are in canonical form - self.parse_gate(&gate)?; + self.insert_gate(&gate)?; } } // gate is in canonical form } else { - // set gate for each qubit + // get the names of the qubits in the circuit before circuit is borrowed as mutable + let circuit_qubits: Vec = self.circuit.keys().cloned().collect(); + + // set gate for each qubit in the instruction for qubit in &gate.qubits { - if let Qubit::Fixed(fixed_qubit) = qubit { - if let Some(wire) = self.circuit.get_mut(fixed_qubit) { - wire.set_gate(self.column, gate, qubit, &self.relationships)?; + if let Qubit::Fixed(instruction_qubit) = qubit { + if let Some(wire) = self.circuit.get_mut(instruction_qubit) { + // set the control and target qubits + if gate.qubits.len() > 1 || gate.name == "PHASE" { + // set the target qubit if the qubit is equal to the last qubit in gate + if qubit == gate.qubits.last().unwrap() { + wire.set_targ(&self.column); + // otherwise, set the control qubit + } else { + wire.set_ctrl( + &self.column, + qubit, + gate.qubits.last().unwrap(), + &circuit_qubits, + ); + } + } else if wire.parameters.get(&self.column).is_some() { + // parameterized single qubit gates are unsupported + return Err(LatexGenError::UnsupportedGate { + gate: gate.name.clone(), + }); + } + + // set modifiers at this column for all qubits + wire.gates.insert(self.column, gate.clone()); } } } @@ -692,49 +629,41 @@ impl Wire { self.parameters.insert(column, param); } - /// Returns a result indicating the gate was successfully set for this Wire - /// or an error indicating the gate is unsupported. If this is a - /// multi-qubit gate, as indicated by the number of qubits in the - /// relationship, then, depending on its position in the relationship, is - /// set as a target or a control qubit. + /// Set target qubit at this column. /// /// # Arguments - /// `&mut self` - exposes this Wire's gates at this column - /// `column` - the column taking the gate - /// `gate` - the gate attempted to be set - /// `relationships` - the qubits at this column in a relationship - fn set_gate( - &mut self, - column: u32, - gate: &Gate, - qubit: &Qubit, - relationships: &HashMap>, - ) -> Result<(), LatexGenError> { - // set the control and target qubits - if let Some(relationship) = relationships.get(&column) { - // requires at least 2 qubits or is a PHASE gate - if relationship.len() > 1 || gate.name == "PHASE" { - // target is the last qubit in the instruction or the qubit in PHASE - if let Some(target) = relationship.last() { - if qubit == target { - self.targ.insert(column, true); - // all other qubits are controls - } else { - self.ctrl.insert(column, 0); + /// `&mut self` - exposes the Wire's targ at this column + /// `column` - the column taking the target + fn set_targ(&mut self, column: &u32) { + self.targ.insert(*column, true); + } + + /// Set control qubit at this column at some distance from the target. The + /// distance is determined by the relative position of the control and + /// target qubits in the circuit. + /// + /// # Arguments + /// `&mut self` - exposes the Wire's ctrl at this column + /// `column` - the column taking the control + /// `ctrl` - the control qubit + /// `targ` - the target qubit + /// `circuit_qubits` - the qubits in the circuit + fn set_ctrl(&mut self, column: &u32, ctrl: &Qubit, targ: &Qubit, circuit_qubits: &[u64]) { + if let Qubit::Fixed(ctrl) = ctrl { + if let Qubit::Fixed(targ) = targ { + // get the index of the control and target qubits + let ctrl_index = circuit_qubits.iter().position(|&x| x == *ctrl); + let targ_index = circuit_qubits.iter().position(|&x| x == *targ); + + // if the control and target qubits are found + if let Some(ctrl_index) = ctrl_index { + if let Some(targ_index) = targ_index { + self.ctrl + .insert(*column, targ_index as i64 - ctrl_index as i64); } } - } else if self.parameters.get(&column).is_some() { - // parameterized single qubit gates are unsupported - return Err(LatexGenError::UnsupportedGate { - gate: gate.name.clone(), - }); } } - - // set modifiers at this column for all qubits - self.gates.insert(column, gate.clone()); - - Ok(()) } } @@ -751,7 +680,7 @@ pub enum LatexGenError { } pub trait ToLatex { - fn to_latex(self, settings: RenderSettings) -> Result; + fn to_latex(&self, settings: RenderSettings) -> Result; } impl ToLatex for Program { @@ -782,7 +711,7 @@ impl ToLatex for Program { /// let program = Program::from_str("CONTROLLED CNOT 2 1 0").expect(""); /// let latex = program.to_latex(RenderSettings::default()).expect(""); /// ``` - fn to_latex(self, settings: RenderSettings) -> Result { + fn to_latex(&self, settings: RenderSettings) -> Result { // get a reference to the current program let instructions = self.to_instructions(false); @@ -815,8 +744,19 @@ impl ToLatex for Program { // parse gate instructions into a new circuit if let Instruction::Gate(gate) = instruction { - diagram.parse_gate(&gate)?; - diagram.locate_targ()?; + // if there are any duplicate qubits in the gate return an error + if gate.qubits.len() + != gate + .qubits + .iter() + .cloned() + .collect::>() + .len() + { + return Err(LatexGenError::FoundTargetWithNoControl); + } + + diagram.insert_gate(&gate)?; diagram.column += 1; } else if let Instruction::GateDefinition(_) = instruction { // GateDefinition is supported and parsed in Gate From 1ecc789b201788ff62f2ad96d10b780c22d237be Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 28 Mar 2023 10:22:08 -0700 Subject: [PATCH 075/118] Make ToLatex a method of Program. --- src/program/latex/mod.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 85751a1b..942b2c4b 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -200,7 +200,7 @@ impl RenderSettings { /// /// # Examples /// ``` - /// use quil_rs::{Program, program::latex::{RenderSettings, ToLatex}}; + /// use quil_rs::{Program, program::latex::RenderSettings}; /// use std::str::FromStr; /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); /// let settings = RenderSettings { @@ -679,11 +679,7 @@ pub enum LatexGenError { UnsupportedGate { gate: String }, } -pub trait ToLatex { - fn to_latex(&self, settings: RenderSettings) -> Result; -} - -impl ToLatex for Program { +impl Program { /// Returns a Result containing a quil [`Program`] as a LaTeX string or a /// [`LatexGenError`]. /// @@ -698,7 +694,7 @@ impl ToLatex for Program { /// # Examples /// ``` /// // To LaTeX for the Bell State Program. - /// use quil_rs::{Program, program::latex::{RenderSettings, ToLatex}}; + /// use quil_rs::{Program, program::latex::RenderSettings}; /// use std::str::FromStr; /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); /// let latex = program.to_latex(RenderSettings::default()).expect(""); @@ -706,12 +702,12 @@ impl ToLatex for Program { /// /// ``` /// // To LaTeX for the Toffoli Gate Program. - /// use quil_rs::{Program, program::latex::{RenderSettings, ToLatex}}; + /// use quil_rs::{Program, program::latex::RenderSettings}; /// use std::str::FromStr; /// let program = Program::from_str("CONTROLLED CNOT 2 1 0").expect(""); /// let latex = program.to_latex(RenderSettings::default()).expect(""); /// ``` - fn to_latex(&self, settings: RenderSettings) -> Result { + pub fn to_latex(&self, settings: RenderSettings) -> Result { // get a reference to the current program let instructions = self.to_instructions(false); @@ -722,7 +718,7 @@ impl ToLatex for Program { }; // initialize circuit with empty wires of all qubits in program - let qubits = Program::get_used_qubits(&self); + let qubits = Program::get_used_qubits(self); for qubit in &qubits { if let Qubit::Fixed(name) = qubit { let wire = Wire { @@ -778,9 +774,7 @@ impl ToLatex for Program { #[cfg(test)] mod tests { - use super::{RenderSettings, ToLatex}; - use crate::Program; - use std::str::FromStr; + use super::{FromStr, Program, RenderSettings}; /// Helper function takes instructions and return the LaTeX using the /// Latex::to_latex method. From 4e4ca5a339bbfd6cadf1576bc0a7156c9022a1e0 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 28 Mar 2023 14:32:54 -0700 Subject: [PATCH 076/118] Add derive_more, change From to FromStr, generify with From. --- Cargo.lock | 20 +++++ Cargo.toml | 7 +- src/program/latex/mod.rs | 163 +++++++++++++++++---------------------- 3 files changed, 95 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f323d46..a1a9f36b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,6 +137,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "criterion" version = "0.4.0" @@ -216,6 +222,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2 1.0.47", + "quote 1.0.21", + "rustc_version", + "syn 1.0.103", +] + [[package]] name = "dot-writer" version = "0.1.2" @@ -754,6 +773,7 @@ name = "quil-rs" version = "0.16.0-rc.1" dependencies = [ "criterion", + "derive_more", "dot-writer", "indexmap", "insta", diff --git a/Cargo.toml b/Cargo.toml index 25ef4130..a3ea5610 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "quil-rs" description = "Rust tooling for Quil (Quantum Instruction Language)" -version ="0.16.0-rc.1" +version = "0.16.0-rc.1" edition = "2021" license = "Apache-2.0" repository = "https://github.com/rigetti/quil-rust" @@ -9,9 +9,10 @@ keywords = ["Quil", "Quantum", "Rigetti"] categories = ["parser-implementations", "science", "compilers", "emulators"] [dependencies] +derive_more = { version = "0.99.17", optional = true } dot-writer = { version = "0.1.2", optional = true } indexmap = "1.6.1" -lexical = "6.1.1" +lexical = "6.1.1" nom = "7.1.1" nom_locate = "4.0.0" num-complex = "0.4.0" @@ -29,7 +30,7 @@ rstest = "0.15.0" [features] graphviz-dot = ["dot-writer"] -latex = [] +latex = ["derive_more"] [profile.release] lto = true diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 942b2c4b..bf98374a 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -37,89 +37,69 @@ use crate::Program; /// /// Single wire commands: lstick, gate, phase, super, qw, nr /// Multi-wire commands: ctrl, targ -#[derive(Clone, Debug)] -enum Command { - /// `\lstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the left. - Lstick(String), - /// `\gate{name}`: Make a gate on the wire. +#[derive(Clone, Debug, derive_more::Display)] +enum RenderCommand { + /// Make a qubit "stick out" from the left. + #[display(fmt = "\\lstick{{\\ket{{q_{{{_0}}}}}}}")] + Lstick(u64), + /// Make a gate on the wire. + #[display(fmt = "\\gate{{{_0}}}")] Gate(String), - /// `\phase{symbol}`: Make a phase on the wire with a rotation - Phase(String), - /// `^{\script}`: Add a superscript to a gate + /// Make a phase on the wire with a rotation + #[display(fmt = "\\phase{{{_0}}}")] + Phase(Parameter), + /// Add a superscript to a gate + #[display(fmt = "^{{\\{_0}}}")] Super(String), - /// `\qw`: Connect the current cell to the previous cell i.e. "do nothing". + /// Connect the current cell to the previous cell i.e. "do nothing". + #[display(fmt = "\\qw")] Qw, - /// `\\`: Start a new row + /// Start a new row + #[display(fmt = "\\\\")] Nr, - /// `\ctrl{wire}`: Make a control qubit--different from Control. - Ctrl(String), - /// `\targ{}`: Make a controlled-not gate. + /// Make a control qubit--different from Control. + #[display(fmt = "\\ctrl{{{_0}}}")] + Ctrl(i64), + /// Make a controlled-not gate. + #[display(fmt = "\\targ{{}}")] Targ, } -impl ToString for Command { - fn to_string(&self) -> String { - match self { - Self::Lstick(wire) => format!(r#"\lstick{{\ket{{q_{{{wire}}}}}}}"#), - Self::Gate(name) => format!(r#"\gate{{{name}}}"#), - Self::Phase(symbol) => format!(r#"\phase{{{symbol}}}"#), - Self::Super(script) => format!(r#"^{{\{script}}}"#), - Self::Qw => r"\qw".to_string(), - Self::Nr => r"\\".to_string(), - Self::Ctrl(wire) => format!(r#"\ctrl{{{wire}}}"#), - Self::Targ => r"\targ{}".to_string(), - } - } -} - /// Types of parameters passed to commands. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, derive_more::Display)] enum Parameter { /// Symbolic parameters + #[display(fmt = "{_0}")] Symbol(Symbol), } -impl ToString for Parameter { - fn to_string(&self) -> String { - match self { - Parameter::Symbol(symbol) => Symbol::to_string(symbol), - } - } -} - /// Supported Greek and alphanumeric symbols. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, derive_more::Display)] enum Symbol { + #[display(fmt = "\\alpha")] Alpha, + #[display(fmt = "\\beta")] Beta, + #[display(fmt = "\\gamma")] Gamma, + #[display(fmt = "\\phi")] Phi, + #[display(fmt = "\\pi")] Pi, + #[display(fmt = "\\text{{{_0}}}")] Text(String), } -impl ToString for Symbol { - fn to_string(&self) -> String { - match self { - Symbol::Alpha => r"\alpha".to_string(), - Symbol::Beta => r"\beta".to_string(), - Symbol::Gamma => r"\gamma".to_string(), - Symbol::Phi => r"\phi".to_string(), - Symbol::Pi => r"\pi".to_string(), - Symbol::Text(text) => format!(r#"\text{{{text}}}"#), - } - } -} - -impl From for Symbol { - fn from(text: String) -> Self { - match text.as_str() { - "alpha" => Symbol::Alpha, - "beta" => Symbol::Beta, - "gamma" => Symbol::Gamma, - "phi" => Symbol::Phi, - "pi" => Symbol::Pi, - _ => Symbol::Text(text), +impl FromStr for Symbol { + type Err = std::convert::Infallible; + fn from_str(s: &str) -> Result { + match s { + "alpha" => Ok(Self::Alpha), + "beta" => Ok(Self::Beta), + "gamma" => Ok(Self::Gamma), + "phi" => Ok(Self::Phi), + "pi" => Ok(Self::Pi), + _ => Ok(Self::Text(s.to_string())), } } } @@ -139,9 +119,12 @@ enum CanonicalGate { None, } -impl From for CanonicalGate { - fn from(gate_name: String) -> Self { - match gate_name.as_str() { +impl From for CanonicalGate +where + S: AsRef, +{ + fn from(gate_name: S) -> Self { + match gate_name.as_ref() { "CNOT" => CanonicalGate::Cnot(String::from("CONTROLLED X")), "CCNOT" => CanonicalGate::Ccnot(String::from("CONTROLLED CONTROLLED X")), "CPHASE" => CanonicalGate::Cphase(String::from("CONTROLLED PHASE")), @@ -237,7 +220,7 @@ impl RenderSettings { // insert empties based on total number of columns for c in 0..=last_column { - wire.empty.insert(c, Command::Qw); + wire.empty.insert(c, RenderCommand::Qw); } Box::new(wire) @@ -321,7 +304,7 @@ impl Diagram { .for_each(|index| { self.circuit .get_mut(index) - .map(|wire| wire.empty.insert(self.column, Command::Qw)); + .map(|wire| wire.empty.insert(self.column, RenderCommand::Qw)); }); } } @@ -476,10 +459,10 @@ impl Display for Diagram { // are labels on in settings? if self.settings.label_qubit_lines { // add label to left side of wire - write!(f, "{}", Command::Lstick(key.to_string()).to_string())?; + write!(f, "{}", RenderCommand::Lstick(*key))?; } else { // add qw buffer to first column - write!(f, "{}", Command::Qw.to_string())?; + write!(f, "{}", RenderCommand::Qw)?; } // convert each column in the wire to string @@ -494,8 +477,8 @@ impl Display for Diagram { for modifier in modifiers { if let GateModifier::Dagger = modifier { superscript.push_str( - &Command::Super(String::from("dagger")).to_string(), - ) + &RenderCommand::Super(String::from("dagger")).to_string(), + ); } } } @@ -503,7 +486,7 @@ impl Display for Diagram { if wire.ctrl.get(&c).is_some() { // CONTROLLED qubits are displayed as `\ctrl{targ}` if let Some(targ) = wire.ctrl.get(&c) { - write!(f, "{}", &(Command::Ctrl(targ.to_string())).to_string())?; + write!(f, "{}", &(RenderCommand::Ctrl(*targ)))?; } continue; } else if wire.targ.get(&c).is_some() { @@ -516,10 +499,10 @@ impl Display for Diagram { // if the gate contains daggers, display target as X gate with dagger superscripts if !superscript.is_empty() { _gate.push_str(&superscript); - write!(f, "{}", &Command::Gate(_gate).to_string())?; + write!(f, "{}", &RenderCommand::Gate(_gate))?; // else display X target as an open dot } else { - write!(f, "{}", &Command::Targ.to_string())?; + write!(f, "{}", &RenderCommand::Targ)?; } continue; // PHASE gates are displayed as `\phase{param}` @@ -527,11 +510,7 @@ impl Display for Diagram { // set the phase parameters if let Some(parameters) = wire.parameters.get(&c) { for param in parameters { - write!( - f, - "{}", - &Command::Phase(param.to_string()).to_string() - )?; + write!(f, "{}", &RenderCommand::Phase(param.clone()))?; } } continue; @@ -545,24 +524,24 @@ impl Display for Diagram { _gate.push_str(&superscript); } - write!(f, "{}", &Command::Gate(_gate).to_string())?; + write!(f, "{}", &RenderCommand::Gate(_gate))?; } else if wire.empty.get(&c).is_some() { // chain an empty column qw to the end of the line write!(f, " & ")?; - write!(f, "{}", &Command::Qw.to_string())?; + write!(f, "{}", &RenderCommand::Qw)?; } } } // chain an empty column qw to the end of the line write!(f, " & ")?; - write!(f, "{}", &Command::Qw.to_string())?; + write!(f, "{}", &RenderCommand::Qw)?; // if this is the last key iteration, omit Nr from end of line if i < self.circuit.len() - 1 { // indicate a new row write!(f, " ")?; - write!(f, "{}", &Command::Nr.to_string())?; + write!(f, "{}", &RenderCommand::Nr)?; i += 1; } @@ -596,7 +575,7 @@ struct Wire { /// the Modifiers on the wire callable by the column modifiers: HashMap>, /// empty column - empty: HashMap, + empty: HashMap, } impl Wire { @@ -620,7 +599,7 @@ impl Wire { // if texify_numerical_constants let param = if texify { // get the matching symbol from text - vec![Parameter::Symbol(text.into())] + vec![Parameter::Symbol(text.parse().unwrap())] } else { // set the symbol as text vec![Parameter::Symbol(Symbol::Text(text))] @@ -946,46 +925,46 @@ mod tests { /// Test module for Quantikz Commands mod commands { - use crate::program::latex::{Command, Symbol}; + use crate::program::latex::{Parameter, RenderCommand, Symbol}; #[test] fn test_command_left_ket() { - insta::assert_snapshot!(Command::Lstick("0".to_string()).to_string()); + insta::assert_snapshot!(RenderCommand::Lstick(0).to_string()); } #[test] fn test_command_gate() { - insta::assert_snapshot!(Command::Gate("X".to_string()).to_string()); + insta::assert_snapshot!(RenderCommand::Gate("X".to_string()).to_string()); } #[test] fn test_command_phase() { - insta::assert_snapshot!(Command::Phase(Symbol::Pi.to_string()).to_string()); + insta::assert_snapshot!(RenderCommand::Phase(Parameter::Symbol(Symbol::Pi)).to_string()); } #[test] fn test_command_super() { - insta::assert_snapshot!(Command::Super("dagger".to_string()).to_string()); + insta::assert_snapshot!(RenderCommand::Super("dagger".to_string()).to_string()); } #[test] fn test_command_qw() { - insta::assert_snapshot!(Command::Qw.to_string()); + insta::assert_snapshot!(RenderCommand::Qw.to_string()); } #[test] fn test_command_nr() { - insta::assert_snapshot!(Command::Nr.to_string()); + insta::assert_snapshot!(RenderCommand::Nr.to_string()); } #[test] fn test_command_control() { - insta::assert_snapshot!(Command::Ctrl("0".to_string()).to_string()); + insta::assert_snapshot!(RenderCommand::Ctrl(0).to_string()); } #[test] fn test_command_cnot_target() { - insta::assert_snapshot!(Command::Targ.to_string()); + insta::assert_snapshot!(RenderCommand::Targ.to_string()); } } From 46c024f2b21ca0176a0e26e9f9ccd5eccca90315 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 28 Mar 2023 16:11:20 -0700 Subject: [PATCH 077/118] Apply suggestions to extend derived traits and shorten/simplify code. --- src/program/latex/mod.rs | 59 +++++++++++++++------------------------- 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index bf98374a..eafec658 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -32,12 +32,7 @@ use crate::Program; /// Available commands used for building circuits with the same names taken /// from the Quantikz documentation for easy reference. LaTeX string denoted /// inside `backticks`. -/// -/// # Available Commands -/// -/// Single wire commands: lstick, gate, phase, super, qw, nr -/// Multi-wire commands: ctrl, targ -#[derive(Clone, Debug, derive_more::Display)] +#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash)] enum RenderCommand { /// Make a qubit "stick out" from the left. #[display(fmt = "\\lstick{{\\ket{{q_{{{_0}}}}}}}")] @@ -66,7 +61,7 @@ enum RenderCommand { } /// Types of parameters passed to commands. -#[derive(Clone, Debug, derive_more::Display)] +#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash)] enum Parameter { /// Symbolic parameters #[display(fmt = "{_0}")] @@ -74,7 +69,7 @@ enum Parameter { } /// Supported Greek and alphanumeric symbols. -#[derive(Clone, Debug, derive_more::Display)] +#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash)] enum Symbol { #[display(fmt = "\\alpha")] Alpha, @@ -106,6 +101,7 @@ impl FromStr for Symbol { /// Gates written in shorthand notation, i.e. composite form, that may be /// decomposed into modifiers and single gate instructions, i.e. canonical form. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] enum CanonicalGate { /// `CNOT` is `CONTROLLED X` Cnot(String), @@ -193,22 +189,17 @@ impl RenderSettings { /// program.to_latex(settings).expect(""); /// ``` fn impute_missing_qubits(last_column: u32, circuit: &mut BTreeMap>) { - // requires at least two qubits to impute missing qubits - if circuit.len() < 2 { - return; - } + let mut keys_iter = circuit.keys(); // get the first qubit in the BTreeMap - let first = circuit - .first_key_value() - .map(|wire| wire.0 + 1) - .expect("previously checked that circuit is not empty"); + let Some(first) = keys_iter + .next() + .map(|wire| wire + 1) else { return; }; // get the last qubit in the BTreeMap - let last = circuit - .last_key_value() - .map(|wire| wire.0 - 1) - .expect("previously checked that circuit has at least two wires"); + let Some(last) = keys_iter + .last() + .map(|wire| wire - 1) else { return; }; // search through the range of qubits for qubit in first..=last { @@ -329,7 +320,7 @@ impl Diagram { return Err(LatexGenError::UnsupportedModifierForked); } // insert for CONTROLLED and DAGGER - _ => { + GateModifier::Controlled | GateModifier::Dagger => { wire.modifiers .entry(*column) .and_modify(|m| m.push(modifier.clone())) @@ -371,7 +362,7 @@ impl Diagram { } // parse the gate to a canonical gate if supported - let canonical_gate = match CanonicalGate::from(gate.name.to_string()) { + let canonical_gate = match CanonicalGate::from(&gate.name) { CanonicalGate::Cnot(inst) => Some(inst), CanonicalGate::Ccnot(inst) => Some(inst), CanonicalGate::Cphase(inst) => Some(inst), @@ -379,18 +370,16 @@ impl Diagram { CanonicalGate::None => None, }; - // add the qubits to the canonical gate to form an instruction - let instruction = if let Some(mut canonical_gate) = canonical_gate { + // concatenate qubits from gate to canonical_gate + let instruction = canonical_gate.map(|instruction| { + let mut instruction = instruction; for qubit in &gate.qubits { if let Qubit::Fixed(qubit) = qubit { - canonical_gate.push(' '); - canonical_gate.push_str(&qubit.to_string()); + instruction.push_str(&format!(" {qubit}")); } } - Some(canonical_gate) - } else { - None - }; + instruction + }); // get gate from new program of canonical instruction if let Some(instruction) = instruction { @@ -520,9 +509,7 @@ impl Display for Diagram { let mut _gate = gate.name.clone(); // concatenate superscripts - if !superscript.is_empty() { - _gate.push_str(&superscript); - } + _gate.push_str(&superscript); write!(f, "{}", &RenderCommand::Gate(_gate))?; } else if wire.empty.get(&c).is_some() { @@ -646,7 +633,7 @@ impl Wire { } } -#[derive(Clone, Debug, thiserror::Error)] +#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq, Hash)] pub enum LatexGenError { #[error("Found a target qubit with no control qubit.")] FoundTargetWithNoControl, @@ -700,9 +687,7 @@ impl Program { let qubits = Program::get_used_qubits(self); for qubit in &qubits { if let Qubit::Fixed(name) = qubit { - let wire = Wire { - ..Default::default() - }; + let wire = Wire::default(); diagram.circuit.insert(*name, Box::new(wire)); } } From 30169aa43747428105f9cd6e9ffb0220a514b0cd Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 29 Mar 2023 13:38:32 -0700 Subject: [PATCH 078/118] Update GateDefinition Instruction block in to_latex. --- src/program/latex/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index eafec658..24d999c2 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -719,7 +719,8 @@ impl Program { diagram.insert_gate(&gate)?; diagram.column += 1; } else if let Instruction::GateDefinition(_) = instruction { - // GateDefinition is supported and parsed in Gate + // GateDefinition is supported but inserted into the circuit using its Gate instruction form + continue; } else { return Err(LatexGenError::UnsupportedInstruction { instruction: instruction.to_string(), From 0b58d763460bedea8bc36dfb9c3601dd9e532c14 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 29 Mar 2023 17:14:19 -0700 Subject: [PATCH 079/118] Change insert_gate to apply_gate and simplify using to_canonical. --- src/program/latex/mod.rs | 165 ++++++++++++++++++--------------------- 1 file changed, 77 insertions(+), 88 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 24d999c2..a63477ad 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -101,32 +101,52 @@ impl FromStr for Symbol { /// Gates written in shorthand notation, i.e. composite form, that may be /// decomposed into modifiers and single gate instructions, i.e. canonical form. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -enum CanonicalGate { +#[derive(Clone, Debug, strum::EnumString, PartialEq, Eq, Hash)] +#[strum(serialize_all = "UPPERCASE")] +enum CompositeGate { /// `CNOT` is `CONTROLLED X` - Cnot(String), + Cnot, /// `CCNOT` is `CONTROLLED CONTROLLED X` - Ccnot(String), + Ccnot, /// `CPHASE` is `CONTROLLED PHASE` - Cphase(String), + Cphase, /// `CZ` is `CONTROLLED Z` - Cz(String), + Cz, /// gate is in canonical form None, } -impl From for CanonicalGate -where - S: AsRef, -{ - fn from(gate_name: S) -> Self { - match gate_name.as_ref() { - "CNOT" => CanonicalGate::Cnot(String::from("CONTROLLED X")), - "CCNOT" => CanonicalGate::Ccnot(String::from("CONTROLLED CONTROLLED X")), - "CPHASE" => CanonicalGate::Cphase(String::from("CONTROLLED PHASE")), - "CZ" => CanonicalGate::Cz(String::from("CONTROLLED Z")), - _ => CanonicalGate::None, - } +impl CompositeGate { + /// Decompose a composite gate into its canonical form. If the gate is not + /// a composite gate, it is returned as is. + /// + /// # Arguments + /// `parameters` - Parameters of the gate + /// `modifiers` - Modifiers of the gate + /// `qubits` - Qubits the gate acts on + fn to_canonical(&self, modifiers: &[GateModifier]) -> Option { + let control_count = match self { + Self::Cnot => 1, + Self::Ccnot => 2, + Self::Cphase => 1, + Self::Cz => 1, + Self::None => return None, + }; + Some(Gate { + name: match self { + Self::Cnot | Self::Ccnot => String::from("X"), + Self::Cphase => String::from("PHASE"), + Self::Cz => String::from("Z"), + _ => unreachable!(), + }, + parameters: Vec::new(), + modifiers: modifiers + .iter() + .cloned() + .chain(std::iter::repeat(GateModifier::Controlled).take(control_count)) + .collect(), + qubits: Vec::new(), + }) } } @@ -332,17 +352,16 @@ impl Diagram { Ok(()) } - /// Inserts a gate from an instruction on the wires in the circuit - /// associate with the qubits from the gate. If the gate name is a - /// composite gate, the gate name is decomposed into canonical form before - /// being set in the circuit. For example, CNOT is a composite gate that - /// can be decomposed into the equivalent canonical form, CONTROLLED X. + /// Applies a gate from an instruction to the wires on the circuit + /// associate with the qubits from the gate. If the gate name matches a + /// composite gate, then the composite gate is applied to the circuit, + /// otherwise, the original gate is applied to the circuit. /// /// # Arguments /// `self` - exposes all attributes in the diagram /// `gate` - the Gate of the Instruction from `to_latex`. - fn insert_gate(&mut self, gate: &Gate) -> Result<(), LatexGenError> { - // set modifiers from gate instruction + fn apply_gate(&mut self, gate: &Gate) -> Result<(), LatexGenError> { + // set modifiers and parameters from gate instruction for qubit in &gate.qubits { if let Qubit::Fixed(qubit) = qubit { if let Some(wire) = self.circuit.get_mut(qubit) { @@ -361,71 +380,41 @@ impl Diagram { } } - // parse the gate to a canonical gate if supported - let canonical_gate = match CanonicalGate::from(&gate.name) { - CanonicalGate::Cnot(inst) => Some(inst), - CanonicalGate::Ccnot(inst) => Some(inst), - CanonicalGate::Cphase(inst) => Some(inst), - CanonicalGate::Cz(inst) => Some(inst), - CanonicalGate::None => None, - }; + let canonical_gate = CompositeGate::from_str(&gate.name) + .map(|g| g.to_canonical(&gate.modifiers)) + .unwrap_or(Some(gate.clone())) + .unwrap(); - // concatenate qubits from gate to canonical_gate - let instruction = canonical_gate.map(|instruction| { - let mut instruction = instruction; - for qubit in &gate.qubits { - if let Qubit::Fixed(qubit) = qubit { - instruction.push_str(&format!(" {qubit}")); - } - } - instruction - }); - - // get gate from new program of canonical instruction - if let Some(instruction) = instruction { - let instructions = Program::from_str(&instruction) - .expect("should return program {instruction}") - .to_instructions(false); - - for instruction in instructions { - if let Instruction::Gate(gate) = instruction { - // call until all composite gates are in canonical form - self.insert_gate(&gate)?; - } - } - // gate is in canonical form - } else { - // get the names of the qubits in the circuit before circuit is borrowed as mutable - let circuit_qubits: Vec = self.circuit.keys().cloned().collect(); - - // set gate for each qubit in the instruction - for qubit in &gate.qubits { - if let Qubit::Fixed(instruction_qubit) = qubit { - if let Some(wire) = self.circuit.get_mut(instruction_qubit) { - // set the control and target qubits - if gate.qubits.len() > 1 || gate.name == "PHASE" { - // set the target qubit if the qubit is equal to the last qubit in gate - if qubit == gate.qubits.last().unwrap() { - wire.set_targ(&self.column); - // otherwise, set the control qubit - } else { - wire.set_ctrl( - &self.column, - qubit, - gate.qubits.last().unwrap(), - &circuit_qubits, - ); - } - } else if wire.parameters.get(&self.column).is_some() { - // parameterized single qubit gates are unsupported - return Err(LatexGenError::UnsupportedGate { - gate: gate.name.clone(), - }); - } + // get the names of the qubits in the circuit before circuit is borrowed as mutable + let circuit_qubits: Vec = self.circuit.keys().cloned().collect(); - // set modifiers at this column for all qubits - wire.gates.insert(self.column, gate.clone()); + // set gate for each qubit in the instruction + for qubit in &gate.qubits { + if let Qubit::Fixed(instruction_qubit) = qubit { + if let Some(wire) = self.circuit.get_mut(instruction_qubit) { + // set the control and target qubits + if gate.qubits.len() > 1 || canonical_gate.name == "PHASE" { + // set the target qubit if the qubit is equal to the last qubit in gate + if qubit == gate.qubits.last().unwrap() { + wire.set_targ(&self.column); + // otherwise, set the control qubit + } else { + wire.set_ctrl( + &self.column, + qubit, + gate.qubits.last().unwrap(), + &circuit_qubits, + ); + } + } else if wire.parameters.get(&self.column).is_some() { + // parameterized single qubit gates are unsupported + return Err(LatexGenError::UnsupportedGate { + gate: gate.name.clone(), + }); } + + // set modifiers at this column for all qubits + wire.gates.insert(self.column, canonical_gate.clone()); } } } @@ -716,7 +705,7 @@ impl Program { return Err(LatexGenError::FoundTargetWithNoControl); } - diagram.insert_gate(&gate)?; + diagram.apply_gate(&gate)?; diagram.column += 1; } else if let Instruction::GateDefinition(_) = instruction { // GateDefinition is supported but inserted into the circuit using its Gate instruction form From 8b74dbef01c3aa0a7e2c6ca85791ad81b09f2032 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 29 Mar 2023 17:50:32 -0700 Subject: [PATCH 080/118] Refactor Wire.gate value type to String and remove CONTROLLED collection. --- src/program/latex/mod.rs | 58 ++++++++++------------------------------ 1 file changed, 14 insertions(+), 44 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index a63477ad..6b593a05 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -101,55 +101,25 @@ impl FromStr for Symbol { /// Gates written in shorthand notation, i.e. composite form, that may be /// decomposed into modifiers and single gate instructions, i.e. canonical form. -#[derive(Clone, Debug, strum::EnumString, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, strum::EnumString, derive_more::Display, PartialEq, Eq, Hash)] #[strum(serialize_all = "UPPERCASE")] enum CompositeGate { /// `CNOT` is `CONTROLLED X` + #[display(fmt = "X")] Cnot, /// `CCNOT` is `CONTROLLED CONTROLLED X` + #[display(fmt = "X")] Ccnot, /// `CPHASE` is `CONTROLLED PHASE` + #[display(fmt = "PHASE")] Cphase, /// `CZ` is `CONTROLLED Z` + #[display(fmt = "Z")] Cz, /// gate is in canonical form None, } -impl CompositeGate { - /// Decompose a composite gate into its canonical form. If the gate is not - /// a composite gate, it is returned as is. - /// - /// # Arguments - /// `parameters` - Parameters of the gate - /// `modifiers` - Modifiers of the gate - /// `qubits` - Qubits the gate acts on - fn to_canonical(&self, modifiers: &[GateModifier]) -> Option { - let control_count = match self { - Self::Cnot => 1, - Self::Ccnot => 2, - Self::Cphase => 1, - Self::Cz => 1, - Self::None => return None, - }; - Some(Gate { - name: match self { - Self::Cnot | Self::Ccnot => String::from("X"), - Self::Cphase => String::from("PHASE"), - Self::Cz => String::from("Z"), - _ => unreachable!(), - }, - parameters: Vec::new(), - modifiers: modifiers - .iter() - .cloned() - .chain(std::iter::repeat(GateModifier::Controlled).take(control_count)) - .collect(), - qubits: Vec::new(), - }) - } -} - /// RenderSettings contains the metadata that allows the user to customize how /// the circuit is rendered or use the default implementation. #[derive(Clone, Copy, Debug)] @@ -380,10 +350,10 @@ impl Diagram { } } + // get display of gate name from composite gate or original gate let canonical_gate = CompositeGate::from_str(&gate.name) - .map(|g| g.to_canonical(&gate.modifiers)) - .unwrap_or(Some(gate.clone())) - .unwrap(); + .map(|g| g.to_string()) + .unwrap_or(gate.name.clone()); // get the names of the qubits in the circuit before circuit is borrowed as mutable let circuit_qubits: Vec = self.circuit.keys().cloned().collect(); @@ -393,7 +363,7 @@ impl Diagram { if let Qubit::Fixed(instruction_qubit) = qubit { if let Some(wire) = self.circuit.get_mut(instruction_qubit) { // set the control and target qubits - if gate.qubits.len() > 1 || canonical_gate.name == "PHASE" { + if gate.qubits.len() > 1 || canonical_gate == "PHASE" { // set the target qubit if the qubit is equal to the last qubit in gate if qubit == gate.qubits.last().unwrap() { wire.set_targ(&self.column); @@ -469,10 +439,10 @@ impl Display for Diagram { continue; } else if wire.targ.get(&c).is_some() { // CONTROLLED X gates are displayed as `\targ{}` - if gate.name == "X" { + if gate == "X" { // set the qubit at this column as the target - let mut _gate = gate.name.clone(); + let mut _gate = gate.clone(); // if the gate contains daggers, display target as X gate with dagger superscripts if !superscript.is_empty() { @@ -484,7 +454,7 @@ impl Display for Diagram { } continue; // PHASE gates are displayed as `\phase{param}` - } else if gate.name == "PHASE" { + } else if gate == "PHASE" { // set the phase parameters if let Some(parameters) = wire.parameters.get(&c) { for param in parameters { @@ -495,7 +465,7 @@ impl Display for Diagram { } } // all other gates display as `\gate{name}` - let mut _gate = gate.name.clone(); + let mut _gate = gate.clone(); // concatenate superscripts _gate.push_str(&superscript); @@ -541,7 +511,7 @@ impl Display for Diagram { #[derive(Clone, Debug, Default)] struct Wire { /// the Gates on the wire callable by the column - gates: HashMap, + gates: HashMap, /// at this column the wire is a control ctrl: HashMap, /// at this column is the wire a target? From 9b782fac4e17f00f06ff7dd6d48cfed0d01ea0ee Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 29 Mar 2023 18:32:06 -0700 Subject: [PATCH 081/118] Rename set_modifiers to extract_daggers, Wire.modifiers to Wire.daggers, and update docs. --- src/program/latex/mod.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 6b593a05..6ed76267 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -52,10 +52,10 @@ enum RenderCommand { /// Start a new row #[display(fmt = "\\\\")] Nr, - /// Make a control qubit--different from Control. + /// Make a control qubit. #[display(fmt = "\\ctrl{{{_0}}}")] Ctrl(i64), - /// Make a controlled-not gate. + /// Make a target qubit. #[display(fmt = "\\targ{{}}")] Targ, } @@ -290,14 +290,15 @@ impl Diagram { } } - /// Utility function to insert modifiers of wires in this Circuit at the - /// current column. Returns an Err for unsupported modifiers. + /// Iterates over the modifiers from the gate instruction and sets it as a + /// dagger modifier of this Wire in the Circuit at the current column. + /// Returns an Err for FORKED modifiers, and does nothing for CONTROLLED. /// /// # Arguments /// `wire` - an exposed wire on the Circuit /// `column` - the current column of the Circuit /// `modifiers` - the modifiers from the Gate - fn set_modifiers( + fn extract_daggers( wire: &mut Wire, column: &u32, modifiers: &Vec, @@ -309,13 +310,15 @@ impl Diagram { GateModifier::Forked => { return Err(LatexGenError::UnsupportedModifierForked); } - // insert for CONTROLLED and DAGGER - GateModifier::Controlled | GateModifier::Dagger => { - wire.modifiers + // insert DAGGER + GateModifier::Dagger => { + wire.daggers .entry(*column) .and_modify(|m| m.push(modifier.clone())) .or_insert_with(|| vec![modifier.clone()]); } + // do nothing for CONTROLLED + _ => (), } } @@ -336,7 +339,7 @@ impl Diagram { if let Qubit::Fixed(qubit) = qubit { if let Some(wire) = self.circuit.get_mut(qubit) { // set modifiers at this column for all qubits - Self::set_modifiers(wire, &self.column, &gate.modifiers)?; + Self::extract_daggers(wire, &self.column, &gate.modifiers)?; // set parameters at this column for all qubits for expression in &gate.parameters { @@ -421,7 +424,7 @@ impl Display for Diagram { let mut superscript = String::from(""); // attach modifiers to gate name if any - if let Some(modifiers) = wire.modifiers.get(&c) { + if let Some(modifiers) = wire.daggers.get(&c) { for modifier in modifiers { if let GateModifier::Dagger = modifier { superscript.push_str( @@ -518,8 +521,8 @@ struct Wire { targ: HashMap, /// the Parameters on the wire callable by the column parameters: HashMap>, - /// the Modifiers on the wire callable by the column - modifiers: HashMap>, + /// the Dagger modifiers on the wire callable by the column + daggers: HashMap>, /// empty column empty: HashMap, } From 2e4a67befcfeb7fda72c84af77b6c855959ba1fd Mon Sep 17 00:00:00 2001 From: Ryan Meneses <58452495+hiyaryan@users.noreply.github.com> Date: Thu, 30 Mar 2023 04:41:30 +0000 Subject: [PATCH 082/118] Show Display path from fmt module. --- src/program/latex/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 6ed76267..c8b28891 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -22,7 +22,7 @@ //! [`Quantikz`]: https://arxiv.org/pdf/1809.03842.pdf use std::collections::{BTreeMap, HashMap, HashSet}; -use std::fmt::Display; +use std::fmt; use std::str::FromStr; use crate::expression::Expression; @@ -237,7 +237,7 @@ impl Default for Document { } } -impl Display for Document { +impl fmt::Display for Document { /// Returns the entire document in LaTeX string. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}{}{}", self.header, self.body, self.footer) @@ -396,7 +396,7 @@ impl Diagram { } } -impl Display for Diagram { +impl fmt::Display for Diagram { /// Returns a result containing the Diagram Circuit as LaTeX string which /// can be input into the body of the Document. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { From 7a6f5132a8c991c24949cac54c924b8d61d67e70 Mon Sep 17 00:00:00 2001 From: Ryan Meneses <58452495+hiyaryan@users.noreply.github.com> Date: Thu, 30 Mar 2023 06:43:36 +0000 Subject: [PATCH 083/118] Move FromStr for Symbol into Symbol enum block using strum. --- src/program/latex/mod.rs | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index c8b28891..eca3e129 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -69,7 +69,8 @@ enum Parameter { } /// Supported Greek and alphanumeric symbols. -#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, strum::EnumString, derive_more::Display, PartialEq, Eq, Hash)] +#[strum(serialize_all = "lowercase")] enum Symbol { #[display(fmt = "\\alpha")] Alpha, @@ -85,20 +86,6 @@ enum Symbol { Text(String), } -impl FromStr for Symbol { - type Err = std::convert::Infallible; - fn from_str(s: &str) -> Result { - match s { - "alpha" => Ok(Self::Alpha), - "beta" => Ok(Self::Beta), - "gamma" => Ok(Self::Gamma), - "phi" => Ok(Self::Phi), - "pi" => Ok(Self::Pi), - _ => Ok(Self::Text(s.to_string())), - } - } -} - /// Gates written in shorthand notation, i.e. composite form, that may be /// decomposed into modifiers and single gate instructions, i.e. canonical form. #[derive(Clone, Debug, strum::EnumString, derive_more::Display, PartialEq, Eq, Hash)] @@ -547,8 +534,10 @@ impl Wire { // if texify_numerical_constants let param = if texify { - // get the matching symbol from text - vec![Parameter::Symbol(text.parse().unwrap())] + // set the texified symbol + let symbol = Parameter::Symbol(Symbol::from_str(&text).unwrap_or(Symbol::Text(text))); + + vec![symbol] } else { // set the symbol as text vec![Parameter::Symbol(Symbol::Text(text))] From 392e7af64e33ec61c7fb2a471b746af36c81b0bf Mon Sep 17 00:00:00 2001 From: Ryan Meneses <58452495+hiyaryan@users.noreply.github.com> Date: Thu, 30 Mar 2023 06:49:01 +0000 Subject: [PATCH 084/118] Remove unused CompositeGate variant None. --- src/program/latex/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index eca3e129..7188af95 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -103,8 +103,6 @@ enum CompositeGate { /// `CZ` is `CONTROLLED Z` #[display(fmt = "Z")] Cz, - /// gate is in canonical form - None, } /// RenderSettings contains the metadata that allows the user to customize how From f85ddbf22592ae43590dc80add7c2ae15d979a6e Mon Sep 17 00:00:00 2001 From: Ryan Meneses <58452495+hiyaryan@users.noreply.github.com> Date: Thu, 30 Mar 2023 07:11:56 +0000 Subject: [PATCH 085/118] Make empty string with new instead of from. --- src/program/latex/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 7188af95..c8d05b64 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -407,7 +407,7 @@ impl fmt::Display for Diagram { if let Some(gate) = wire.gates.get(&c) { write!(f, " & ")?; - let mut superscript = String::from(""); + let mut superscript = String::new(); // attach modifiers to gate name if any if let Some(modifiers) = wire.daggers.get(&c) { for modifier in modifiers { From 935875a5aaffca71e4d6d69fc241341abc4613b7 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 30 Mar 2023 14:09:40 -0700 Subject: [PATCH 086/118] Initial file restructuring. --- src/program/latex/diagram/mod.rs | 346 +++++++++++++++++ src/program/latex/diagram/settings.rs | 93 +++++ src/program/latex/diagram/wire.rs | 110 ++++++ src/program/latex/mod.rs | 534 +------------------------- 4 files changed, 559 insertions(+), 524 deletions(-) create mode 100644 src/program/latex/diagram/mod.rs create mode 100644 src/program/latex/diagram/settings.rs create mode 100644 src/program/latex/diagram/wire.rs diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs new file mode 100644 index 00000000..c31257ed --- /dev/null +++ b/src/program/latex/diagram/mod.rs @@ -0,0 +1,346 @@ +use std::{ + collections::{BTreeMap, HashSet}, + fmt, + str::FromStr, +}; + +use self::{settings::RenderSettings, wire::Wire}; +use super::LatexGenError; +use crate::instruction::{Gate, GateModifier, Instruction, Qubit}; + +pub(crate) mod settings; +pub(crate) mod wire; + +/// Available commands used for building circuits with the same names taken +/// from the Quantikz documentation for easy reference. LaTeX string denoted +/// inside `backticks`. +#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash)] +pub(crate) enum RenderCommand { + /// Make a qubit "stick out" from the left. + #[display(fmt = "\\lstick{{\\ket{{q_{{{_0}}}}}}}")] + Lstick(u64), + /// Make a gate on the wire. + #[display(fmt = "\\gate{{{_0}}}")] + Gate(String), + /// Make a phase on the wire with a rotation + #[display(fmt = "\\phase{{{_0}}}")] + Phase(Parameter), + /// Add a superscript to a gate + #[display(fmt = "^{{\\{_0}}}")] + Super(String), + /// Connect the current cell to the previous cell i.e. "do nothing". + #[display(fmt = "\\qw")] + Qw, + /// Start a new row + #[display(fmt = "\\\\")] + Nr, + /// Make a control qubit. + #[display(fmt = "\\ctrl{{{_0}}}")] + Ctrl(i64), + /// Make a target qubit. + #[display(fmt = "\\targ{{}}")] + Targ, +} + +/// Types of parameters passed to commands. +#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash)] +pub(crate) enum Parameter { + /// Symbolic parameters + #[display(fmt = "{_0}")] + Symbol(Symbol), +} + +/// Supported Greek and alphanumeric symbols. +#[derive(Clone, Debug, strum::EnumString, derive_more::Display, PartialEq, Eq, Hash)] +#[strum(serialize_all = "lowercase")] +pub(crate) enum Symbol { + #[display(fmt = "\\alpha")] + Alpha, + #[display(fmt = "\\beta")] + Beta, + #[display(fmt = "\\gamma")] + Gamma, + #[display(fmt = "\\phi")] + Phi, + #[display(fmt = "\\pi")] + Pi, + #[display(fmt = "\\text{{{_0}}}")] + Text(String), +} + +/// Gates written in shorthand notation, i.e. composite form, that may be +/// decomposed into modifiers and single gate instructions, i.e. canonical form. +#[derive(Clone, Debug, strum::EnumString, derive_more::Display, PartialEq, Eq, Hash)] +#[strum(serialize_all = "UPPERCASE")] +enum CompositeGate { + /// `CNOT` is `CONTROLLED X` + #[display(fmt = "X")] + Cnot, + /// `CCNOT` is `CONTROLLED CONTROLLED X` + #[display(fmt = "X")] + Ccnot, + /// `CPHASE` is `CONTROLLED PHASE` + #[display(fmt = "PHASE")] + Cphase, + /// `CZ` is `CONTROLLED Z` + #[display(fmt = "Z")] + Cz, +} + +/// A Diagram represents a collection of wires in a circuit. It encodes the +/// wires row in the diagram and its relationship to other wires. A row is one +/// of the wires in the circuit BTreeMap. Diagram tracks relationships between +/// wires with two pieces of information--1. the wires row (its order in the +/// BTreeMap), and 2. the column that spreads between all wires that pass +/// through a multi qubit gate, e.g. CNOT. The size of the diagram can be +/// measured by multiplying the column with the length of the circuit. This is +/// an [m x n] matrix where each element in the matrix represents an item to be +/// rendered onto the diagram using one of the Quantikz commands. +#[derive(Clone, Debug, Default)] +pub(super) struct Diagram { + /// customizes how the diagram renders the circuit + pub(crate) settings: RenderSettings, + /// total number of elements on each wire + pub(crate) column: u32, + /// a BTreeMap of wires with the name of the wire as the key + pub(crate) circuit: BTreeMap>, +} + +impl Diagram { + /// Compares qubits from a single instruction associated with a column on + /// the circuit to all of the qubits used in the quil program. If a qubit + /// from the quil program is not found in the qubits in the single + /// instruction line, then an empty slot is added to that column on the + /// qubit wire of the circuit indicating a "do nothing" at that column. + /// + /// # Arguments + /// `&mut self` - exposes the wires on the Circuit + /// `qubits` - exposes the qubits used in the Program + /// `instruction` - exposes the qubits in a single Instruction + pub(crate) fn set_empty(&mut self, qubits: &HashSet, instruction: &Instruction) { + if let Instruction::Gate(gate) = instruction { + qubits + .difference(&gate.qubits.iter().cloned().collect()) + .filter_map(|q| match q { + Qubit::Fixed(index) => Some(index), + _ => None, + }) + .for_each(|index| { + self.circuit + .get_mut(index) + .map(|wire| wire.empty.insert(self.column, RenderCommand::Qw)); + }); + } + } + + /// Iterates over the modifiers from the gate instruction and sets it as a + /// dagger modifier of this Wire in the Circuit at the current column. + /// Returns an Err for FORKED modifiers, and does nothing for CONTROLLED. + /// + /// # Arguments + /// `wire` - an exposed wire on the Circuit + /// `column` - the current column of the Circuit + /// `modifiers` - the modifiers from the Gate + fn extract_daggers( + wire: &mut Wire, + column: &u32, + modifiers: &Vec, + ) -> Result<(), LatexGenError> { + // set modifers + for modifier in modifiers { + match modifier { + // return error for unsupported modifier FORKED + GateModifier::Forked => { + return Err(LatexGenError::UnsupportedModifierForked); + } + // insert DAGGER + GateModifier::Dagger => { + wire.daggers + .entry(*column) + .and_modify(|m| m.push(modifier.clone())) + .or_insert_with(|| vec![modifier.clone()]); + } + // do nothing for CONTROLLED + _ => (), + } + } + + Ok(()) + } + + /// Applies a gate from an instruction to the wires on the circuit + /// associate with the qubits from the gate. If the gate name matches a + /// composite gate, then the composite gate is applied to the circuit, + /// otherwise, the original gate is applied to the circuit. + /// + /// # Arguments + /// `self` - exposes all attributes in the diagram + /// `gate` - the Gate of the Instruction from `to_latex`. + pub(crate) fn apply_gate(&mut self, gate: &Gate) -> Result<(), LatexGenError> { + // set modifiers and parameters from gate instruction + for qubit in &gate.qubits { + if let Qubit::Fixed(qubit) = qubit { + if let Some(wire) = self.circuit.get_mut(qubit) { + // set modifiers at this column for all qubits + Self::extract_daggers(wire, &self.column, &gate.modifiers)?; + + // set parameters at this column for all qubits + for expression in &gate.parameters { + wire.set_param( + expression, + self.column, + self.settings.texify_numerical_constants, + ); + } + } + } + } + + // get display of gate name from composite gate or original gate + let canonical_gate = CompositeGate::from_str(&gate.name) + .map(|g| g.to_string()) + .unwrap_or(gate.name.clone()); + + // get the names of the qubits in the circuit before circuit is borrowed as mutable + let circuit_qubits: Vec = self.circuit.keys().cloned().collect(); + + // set gate for each qubit in the instruction + for qubit in &gate.qubits { + if let Qubit::Fixed(instruction_qubit) = qubit { + if let Some(wire) = self.circuit.get_mut(instruction_qubit) { + // set the control and target qubits + if gate.qubits.len() > 1 || canonical_gate == "PHASE" { + // set the target qubit if the qubit is equal to the last qubit in gate + if qubit == gate.qubits.last().unwrap() { + wire.set_targ(&self.column); + // otherwise, set the control qubit + } else { + wire.set_ctrl( + &self.column, + qubit, + gate.qubits.last().unwrap(), + &circuit_qubits, + ); + } + } else if wire.parameters.get(&self.column).is_some() { + // parameterized single qubit gates are unsupported + return Err(LatexGenError::UnsupportedGate { + gate: gate.name.clone(), + }); + } + + // set modifiers at this column for all qubits + wire.gates.insert(self.column, canonical_gate.clone()); + } + } + } + + Ok(()) + } +} + +impl fmt::Display for Diagram { + /// Returns a result containing the Diagram Circuit as LaTeX string which + /// can be input into the body of the Document. + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // add a newline between the first line and the header + writeln!(f)?; + + let mut i = 0; // used to omit trailing Nr + + // write the LaTeX string for each wire in the circuit + for key in self.circuit.keys() { + // are labels on in settings? + if self.settings.label_qubit_lines { + // add label to left side of wire + write!(f, "{}", RenderCommand::Lstick(*key))?; + } else { + // add qw buffer to first column + write!(f, "{}", RenderCommand::Qw)?; + } + + // convert each column in the wire to string + if let Some(wire) = self.circuit.get(key) { + for c in 0..self.column { + if let Some(gate) = wire.gates.get(&c) { + write!(f, " & ")?; + + let mut superscript = String::new(); + // attach modifiers to gate name if any + if let Some(modifiers) = wire.daggers.get(&c) { + for modifier in modifiers { + if let GateModifier::Dagger = modifier { + superscript.push_str( + &RenderCommand::Super(String::from("dagger")).to_string(), + ); + } + } + } + + if wire.ctrl.get(&c).is_some() { + // CONTROLLED qubits are displayed as `\ctrl{targ}` + if let Some(targ) = wire.ctrl.get(&c) { + write!(f, "{}", &(RenderCommand::Ctrl(*targ)))?; + } + continue; + } else if wire.targ.get(&c).is_some() { + // CONTROLLED X gates are displayed as `\targ{}` + if gate == "X" { + // set the qubit at this column as the target + + let mut _gate = gate.clone(); + + // if the gate contains daggers, display target as X gate with dagger superscripts + if !superscript.is_empty() { + _gate.push_str(&superscript); + write!(f, "{}", &RenderCommand::Gate(_gate))?; + // else display X target as an open dot + } else { + write!(f, "{}", &RenderCommand::Targ)?; + } + continue; + // PHASE gates are displayed as `\phase{param}` + } else if gate == "PHASE" { + // set the phase parameters + if let Some(parameters) = wire.parameters.get(&c) { + for param in parameters { + write!(f, "{}", &RenderCommand::Phase(param.clone()))?; + } + } + continue; + } + } + // all other gates display as `\gate{name}` + let mut _gate = gate.clone(); + + // concatenate superscripts + _gate.push_str(&superscript); + + write!(f, "{}", &RenderCommand::Gate(_gate))?; + } else if wire.empty.get(&c).is_some() { + // chain an empty column qw to the end of the line + write!(f, " & ")?; + write!(f, "{}", &RenderCommand::Qw)?; + } + } + } + + // chain an empty column qw to the end of the line + write!(f, " & ")?; + write!(f, "{}", &RenderCommand::Qw)?; + + // if this is the last key iteration, omit Nr from end of line + if i < self.circuit.len() - 1 { + // indicate a new row + write!(f, " ")?; + write!(f, "{}", &RenderCommand::Nr)?; + i += 1; + } + + // add a newline between each new line or the footer + writeln!(f)?; + } + + Ok(()) + } +} diff --git a/src/program/latex/diagram/settings.rs b/src/program/latex/diagram/settings.rs new file mode 100644 index 00000000..c8a35a50 --- /dev/null +++ b/src/program/latex/diagram/settings.rs @@ -0,0 +1,93 @@ +use std::collections::BTreeMap; + +use super::{wire::Wire, RenderCommand}; + +/// RenderSettings contains the metadata that allows the user to customize how +/// the circuit is rendered or use the default implementation. +#[derive(Clone, Copy, Debug)] +pub struct RenderSettings { + /// Convert numerical constants, e.g. pi, to LaTeX form. + pub texify_numerical_constants: bool, + /// Include all qubits implicitly referenced in the Quil program. + pub impute_missing_qubits: bool, + /// Label qubit lines. + pub label_qubit_lines: bool, + /// Write controlled rotations in compact form. + pub abbreviate_controlled_rotations: bool, + /// Extend the length of open wires at the right of the diagram. + pub qubit_line_open_wire_length: u32, + /// Align measurement operations to appear at the end of the diagram. + pub right_align_terminal_measurements: bool, +} + +impl Default for RenderSettings { + /// Returns the default RenderSettings. + fn default() -> Self { + Self { + /// false: pi is π. + texify_numerical_constants: true, + /// true: `CNOT 0 2` would have three qubit lines: 0, 1, 2. + impute_missing_qubits: false, + /// false: remove Lstick/Rstick from latex. + label_qubit_lines: true, + /// true: `RX(pi)` displayed as `X_{\\pi}` instead of `R_X(\\pi)`. + abbreviate_controlled_rotations: false, + /// 0: condenses the size of subdiagrams. + qubit_line_open_wire_length: 1, + /// false: include Meter in the current column. + right_align_terminal_measurements: true, + } + } +} + +impl RenderSettings { + /// Adds missing qubits between the first qubit and last qubit in a + /// diagram's circuit. If a missing qubit is found, a new wire is created + /// and pushed to the diagram's circuit. + /// + /// # Arguments + /// `last_column` - total number of instructions from Program + /// `circuit` - the circuit of the diagram + /// + /// # Examples + /// ``` + /// use quil_rs::{Program, program::latex::RenderSettings}; + /// use std::str::FromStr; + /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); + /// let settings = RenderSettings { + /// impute_missing_qubits: true, + /// ..Default::default() + /// }; + /// program.to_latex(settings).expect(""); + /// ``` + pub(crate) fn impute_missing_qubits(last_column: u32, circuit: &mut BTreeMap>) { + let mut keys_iter = circuit.keys(); + + // get the first qubit in the BTreeMap + let Some(first) = keys_iter + .next() + .map(|wire| wire + 1) else { return; }; + + // get the last qubit in the BTreeMap + let Some(last) = keys_iter + .last() + .map(|wire| wire - 1) else { return; }; + + // search through the range of qubits + for qubit in first..=last { + // if the qubit is not found impute it + circuit.entry(qubit).or_insert_with(|| { + let mut wire = Wire { + ..Default::default() + }; + + // insert empties based on total number of columns + for c in 0..=last_column { + wire.empty.insert(c, RenderCommand::Qw); + } + + Box::new(wire) + }); + } + } +} diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs new file mode 100644 index 00000000..03fa5ca1 --- /dev/null +++ b/src/program/latex/diagram/wire.rs @@ -0,0 +1,110 @@ +use std::{collections::HashMap, str::FromStr}; + +use crate::{ + expression::Expression, + instruction::{GateModifier, Qubit}, + program::latex::diagram::Symbol, +}; + +use super::{Parameter, RenderCommand}; + +/// A Wire represents a single qubit. A wire only needs to keep track of all +/// the elements it contains mapped to some arbitrary column. Diagram keeps +/// track of where the Wire belongs in the larger circuit, its row, and knows +/// how each Wire relates to each other at that column. When Diagram parses the +/// wires as a collection, if the Wire relates to another at some column, then +/// its field will be updated at that column based on the knowledge Diagram has +/// about this connection. This updated value also looks arbitrary to Wire, it +/// does not explicitly define which qubit it relates to, but a digit that +/// describes how far away it is from the related qubit based on Quantikz. +#[derive(Clone, Debug, Default)] +pub(crate) struct Wire { + /// the Gates on the wire callable by the column + pub(crate) gates: HashMap, + /// at this column the wire is a control + pub(crate) ctrl: HashMap, + /// at this column is the wire a target? + pub(crate) targ: HashMap, + /// the Parameters on the wire callable by the column + pub(crate) parameters: HashMap>, + /// the Dagger modifiers on the wire callable by the column + pub(crate) daggers: HashMap>, + /// empty column + pub(crate) empty: HashMap, +} + +impl Wire { + /// Retrieves a gate's parameters from Expression and matches them with its + /// symbolic definition which is then stored into wire at the specific + /// column. + /// + /// # Arguments + /// `&mut self` - exposes the Wire's parameters at this column + /// `expression` - expression from Program to get name of Parameter + /// `column` - the column taking the parameters + /// `texify` - is texify_numerical_constants setting on? + pub(crate) fn set_param(&mut self, expression: &Expression, column: u32, texify: bool) { + // get the name of the supported expression + let text = match expression { + Expression::Address(mr) => mr.name.to_string(), + Expression::Number(c) => c.re.to_string(), + expression => expression.to_string(), + }; + + // if texify_numerical_constants + let param = if texify { + // set the texified symbol + let symbol = Parameter::Symbol(Symbol::from_str(&text).unwrap_or(Symbol::Text(text))); + + vec![symbol] + } else { + // set the symbol as text + vec![Parameter::Symbol(Symbol::Text(text))] + }; + + self.parameters.insert(column, param); + } + + /// Set target qubit at this column. + /// + /// # Arguments + /// `&mut self` - exposes the Wire's targ at this column + /// `column` - the column taking the target + pub(crate) fn set_targ(&mut self, column: &u32) { + self.targ.insert(*column, true); + } + + /// Set control qubit at this column at some distance from the target. The + /// distance is determined by the relative position of the control and + /// target qubits in the circuit. + /// + /// # Arguments + /// `&mut self` - exposes the Wire's ctrl at this column + /// `column` - the column taking the control + /// `ctrl` - the control qubit + /// `targ` - the target qubit + /// `circuit_qubits` - the qubits in the circuit + pub(crate) fn set_ctrl( + &mut self, + column: &u32, + ctrl: &Qubit, + targ: &Qubit, + circuit_qubits: &[u64], + ) { + if let Qubit::Fixed(ctrl) = ctrl { + if let Qubit::Fixed(targ) = targ { + // get the index of the control and target qubits + let ctrl_index = circuit_qubits.iter().position(|&x| x == *ctrl); + let targ_index = circuit_qubits.iter().position(|&x| x == *targ); + + // if the control and target qubits are found + if let Some(ctrl_index) = ctrl_index { + if let Some(targ_index) = targ_index { + self.ctrl + .insert(*column, targ_index as i64 - ctrl_index as i64); + } + } + } + } + } +} diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index c8d05b64..80337463 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -21,179 +21,17 @@ //! //! [`Quantikz`]: https://arxiv.org/pdf/1809.03842.pdf -use std::collections::{BTreeMap, HashMap, HashSet}; +mod diagram; + +use std::collections::HashSet; use std::fmt; -use std::str::FromStr; -use crate::expression::Expression; -use crate::instruction::{Gate, GateModifier, Instruction, Qubit}; +use crate::instruction::{Instruction, Qubit}; use crate::Program; -/// Available commands used for building circuits with the same names taken -/// from the Quantikz documentation for easy reference. LaTeX string denoted -/// inside `backticks`. -#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash)] -enum RenderCommand { - /// Make a qubit "stick out" from the left. - #[display(fmt = "\\lstick{{\\ket{{q_{{{_0}}}}}}}")] - Lstick(u64), - /// Make a gate on the wire. - #[display(fmt = "\\gate{{{_0}}}")] - Gate(String), - /// Make a phase on the wire with a rotation - #[display(fmt = "\\phase{{{_0}}}")] - Phase(Parameter), - /// Add a superscript to a gate - #[display(fmt = "^{{\\{_0}}}")] - Super(String), - /// Connect the current cell to the previous cell i.e. "do nothing". - #[display(fmt = "\\qw")] - Qw, - /// Start a new row - #[display(fmt = "\\\\")] - Nr, - /// Make a control qubit. - #[display(fmt = "\\ctrl{{{_0}}}")] - Ctrl(i64), - /// Make a target qubit. - #[display(fmt = "\\targ{{}}")] - Targ, -} - -/// Types of parameters passed to commands. -#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash)] -enum Parameter { - /// Symbolic parameters - #[display(fmt = "{_0}")] - Symbol(Symbol), -} - -/// Supported Greek and alphanumeric symbols. -#[derive(Clone, Debug, strum::EnumString, derive_more::Display, PartialEq, Eq, Hash)] -#[strum(serialize_all = "lowercase")] -enum Symbol { - #[display(fmt = "\\alpha")] - Alpha, - #[display(fmt = "\\beta")] - Beta, - #[display(fmt = "\\gamma")] - Gamma, - #[display(fmt = "\\phi")] - Phi, - #[display(fmt = "\\pi")] - Pi, - #[display(fmt = "\\text{{{_0}}}")] - Text(String), -} - -/// Gates written in shorthand notation, i.e. composite form, that may be -/// decomposed into modifiers and single gate instructions, i.e. canonical form. -#[derive(Clone, Debug, strum::EnumString, derive_more::Display, PartialEq, Eq, Hash)] -#[strum(serialize_all = "UPPERCASE")] -enum CompositeGate { - /// `CNOT` is `CONTROLLED X` - #[display(fmt = "X")] - Cnot, - /// `CCNOT` is `CONTROLLED CONTROLLED X` - #[display(fmt = "X")] - Ccnot, - /// `CPHASE` is `CONTROLLED PHASE` - #[display(fmt = "PHASE")] - Cphase, - /// `CZ` is `CONTROLLED Z` - #[display(fmt = "Z")] - Cz, -} - -/// RenderSettings contains the metadata that allows the user to customize how -/// the circuit is rendered or use the default implementation. -#[derive(Clone, Copy, Debug)] -pub struct RenderSettings { - /// Convert numerical constants, e.g. pi, to LaTeX form. - pub texify_numerical_constants: bool, - /// Include all qubits implicitly referenced in the Quil program. - pub impute_missing_qubits: bool, - /// Label qubit lines. - pub label_qubit_lines: bool, - /// Write controlled rotations in compact form. - pub abbreviate_controlled_rotations: bool, - /// Extend the length of open wires at the right of the diagram. - pub qubit_line_open_wire_length: u32, - /// Align measurement operations to appear at the end of the diagram. - pub right_align_terminal_measurements: bool, -} - -impl Default for RenderSettings { - /// Returns the default RenderSettings. - fn default() -> Self { - Self { - /// false: pi is π. - texify_numerical_constants: true, - /// true: `CNOT 0 2` would have three qubit lines: 0, 1, 2. - impute_missing_qubits: false, - /// false: remove Lstick/Rstick from latex. - label_qubit_lines: true, - /// true: `RX(pi)` displayed as `X_{\\pi}` instead of `R_X(\\pi)`. - abbreviate_controlled_rotations: false, - /// 0: condenses the size of subdiagrams. - qubit_line_open_wire_length: 1, - /// false: include Meter in the current column. - right_align_terminal_measurements: true, - } - } -} - -impl RenderSettings { - /// Adds missing qubits between the first qubit and last qubit in a - /// diagram's circuit. If a missing qubit is found, a new wire is created - /// and pushed to the diagram's circuit. - /// - /// # Arguments - /// `last_column` - total number of instructions from Program - /// `circuit` - the circuit of the diagram - /// - /// # Examples - /// ``` - /// use quil_rs::{Program, program::latex::RenderSettings}; - /// use std::str::FromStr; - /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); - /// let settings = RenderSettings { - /// impute_missing_qubits: true, - /// ..Default::default() - /// }; - /// program.to_latex(settings).expect(""); - /// ``` - fn impute_missing_qubits(last_column: u32, circuit: &mut BTreeMap>) { - let mut keys_iter = circuit.keys(); - - // get the first qubit in the BTreeMap - let Some(first) = keys_iter - .next() - .map(|wire| wire + 1) else { return; }; - - // get the last qubit in the BTreeMap - let Some(last) = keys_iter - .last() - .map(|wire| wire - 1) else { return; }; - - // search through the range of qubits - for qubit in first..=last { - // if the qubit is not found impute it - circuit.entry(qubit).or_insert_with(|| { - let mut wire = Wire { - ..Default::default() - }; - - // insert empties based on total number of columns - for c in 0..=last_column { - wire.empty.insert(c, RenderCommand::Qw); - } - - Box::new(wire) - }); - } - } -} +use diagram::settings::RenderSettings; +use diagram::wire::Wire; +use diagram::Diagram; /// The structure of a LaTeX document. Typically a LaTeX document contains /// metadata defining the setup and packages used in a document within a header @@ -229,359 +67,6 @@ impl fmt::Display for Document { } } -/// A Diagram represents a collection of wires in a circuit. It encodes the -/// wires row in the diagram and its relationship to other wires. A row is one -/// of the wires in the circuit BTreeMap. Diagram tracks relationships between -/// wires with two pieces of information--1. the wires row (its order in the -/// BTreeMap), and 2. the column that spreads between all wires that pass -/// through a multi qubit gate, e.g. CNOT. The size of the diagram can be -/// measured by multiplying the column with the length of the circuit. This is -/// an [m x n] matrix where each element in the matrix represents an item to be -/// rendered onto the diagram using one of the Quantikz commands. -#[derive(Clone, Debug, Default)] -struct Diagram { - /// customizes how the diagram renders the circuit - settings: RenderSettings, - /// total number of elements on each wire - column: u32, - /// a BTreeMap of wires with the name of the wire as the key - circuit: BTreeMap>, -} - -impl Diagram { - /// Compares qubits from a single instruction associated with a column on - /// the circuit to all of the qubits used in the quil program. If a qubit - /// from the quil program is not found in the qubits in the single - /// instruction line, then an empty slot is added to that column on the - /// qubit wire of the circuit indicating a "do nothing" at that column. - /// - /// # Arguments - /// `&mut self` - exposes the wires on the Circuit - /// `qubits` - exposes the qubits used in the Program - /// `instruction` - exposes the qubits in a single Instruction - fn set_empty(&mut self, qubits: &HashSet, instruction: &Instruction) { - if let Instruction::Gate(gate) = instruction { - qubits - .difference(&gate.qubits.iter().cloned().collect()) - .filter_map(|q| match q { - Qubit::Fixed(index) => Some(index), - _ => None, - }) - .for_each(|index| { - self.circuit - .get_mut(index) - .map(|wire| wire.empty.insert(self.column, RenderCommand::Qw)); - }); - } - } - - /// Iterates over the modifiers from the gate instruction and sets it as a - /// dagger modifier of this Wire in the Circuit at the current column. - /// Returns an Err for FORKED modifiers, and does nothing for CONTROLLED. - /// - /// # Arguments - /// `wire` - an exposed wire on the Circuit - /// `column` - the current column of the Circuit - /// `modifiers` - the modifiers from the Gate - fn extract_daggers( - wire: &mut Wire, - column: &u32, - modifiers: &Vec, - ) -> Result<(), LatexGenError> { - // set modifers - for modifier in modifiers { - match modifier { - // return error for unsupported modifier FORKED - GateModifier::Forked => { - return Err(LatexGenError::UnsupportedModifierForked); - } - // insert DAGGER - GateModifier::Dagger => { - wire.daggers - .entry(*column) - .and_modify(|m| m.push(modifier.clone())) - .or_insert_with(|| vec![modifier.clone()]); - } - // do nothing for CONTROLLED - _ => (), - } - } - - Ok(()) - } - - /// Applies a gate from an instruction to the wires on the circuit - /// associate with the qubits from the gate. If the gate name matches a - /// composite gate, then the composite gate is applied to the circuit, - /// otherwise, the original gate is applied to the circuit. - /// - /// # Arguments - /// `self` - exposes all attributes in the diagram - /// `gate` - the Gate of the Instruction from `to_latex`. - fn apply_gate(&mut self, gate: &Gate) -> Result<(), LatexGenError> { - // set modifiers and parameters from gate instruction - for qubit in &gate.qubits { - if let Qubit::Fixed(qubit) = qubit { - if let Some(wire) = self.circuit.get_mut(qubit) { - // set modifiers at this column for all qubits - Self::extract_daggers(wire, &self.column, &gate.modifiers)?; - - // set parameters at this column for all qubits - for expression in &gate.parameters { - wire.set_param( - expression, - self.column, - self.settings.texify_numerical_constants, - ); - } - } - } - } - - // get display of gate name from composite gate or original gate - let canonical_gate = CompositeGate::from_str(&gate.name) - .map(|g| g.to_string()) - .unwrap_or(gate.name.clone()); - - // get the names of the qubits in the circuit before circuit is borrowed as mutable - let circuit_qubits: Vec = self.circuit.keys().cloned().collect(); - - // set gate for each qubit in the instruction - for qubit in &gate.qubits { - if let Qubit::Fixed(instruction_qubit) = qubit { - if let Some(wire) = self.circuit.get_mut(instruction_qubit) { - // set the control and target qubits - if gate.qubits.len() > 1 || canonical_gate == "PHASE" { - // set the target qubit if the qubit is equal to the last qubit in gate - if qubit == gate.qubits.last().unwrap() { - wire.set_targ(&self.column); - // otherwise, set the control qubit - } else { - wire.set_ctrl( - &self.column, - qubit, - gate.qubits.last().unwrap(), - &circuit_qubits, - ); - } - } else if wire.parameters.get(&self.column).is_some() { - // parameterized single qubit gates are unsupported - return Err(LatexGenError::UnsupportedGate { - gate: gate.name.clone(), - }); - } - - // set modifiers at this column for all qubits - wire.gates.insert(self.column, canonical_gate.clone()); - } - } - } - - Ok(()) - } -} - -impl fmt::Display for Diagram { - /// Returns a result containing the Diagram Circuit as LaTeX string which - /// can be input into the body of the Document. - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // add a newline between the first line and the header - writeln!(f)?; - - let mut i = 0; // used to omit trailing Nr - - // write the LaTeX string for each wire in the circuit - for key in self.circuit.keys() { - // are labels on in settings? - if self.settings.label_qubit_lines { - // add label to left side of wire - write!(f, "{}", RenderCommand::Lstick(*key))?; - } else { - // add qw buffer to first column - write!(f, "{}", RenderCommand::Qw)?; - } - - // convert each column in the wire to string - if let Some(wire) = self.circuit.get(key) { - for c in 0..self.column { - if let Some(gate) = wire.gates.get(&c) { - write!(f, " & ")?; - - let mut superscript = String::new(); - // attach modifiers to gate name if any - if let Some(modifiers) = wire.daggers.get(&c) { - for modifier in modifiers { - if let GateModifier::Dagger = modifier { - superscript.push_str( - &RenderCommand::Super(String::from("dagger")).to_string(), - ); - } - } - } - - if wire.ctrl.get(&c).is_some() { - // CONTROLLED qubits are displayed as `\ctrl{targ}` - if let Some(targ) = wire.ctrl.get(&c) { - write!(f, "{}", &(RenderCommand::Ctrl(*targ)))?; - } - continue; - } else if wire.targ.get(&c).is_some() { - // CONTROLLED X gates are displayed as `\targ{}` - if gate == "X" { - // set the qubit at this column as the target - - let mut _gate = gate.clone(); - - // if the gate contains daggers, display target as X gate with dagger superscripts - if !superscript.is_empty() { - _gate.push_str(&superscript); - write!(f, "{}", &RenderCommand::Gate(_gate))?; - // else display X target as an open dot - } else { - write!(f, "{}", &RenderCommand::Targ)?; - } - continue; - // PHASE gates are displayed as `\phase{param}` - } else if gate == "PHASE" { - // set the phase parameters - if let Some(parameters) = wire.parameters.get(&c) { - for param in parameters { - write!(f, "{}", &RenderCommand::Phase(param.clone()))?; - } - } - continue; - } - } - // all other gates display as `\gate{name}` - let mut _gate = gate.clone(); - - // concatenate superscripts - _gate.push_str(&superscript); - - write!(f, "{}", &RenderCommand::Gate(_gate))?; - } else if wire.empty.get(&c).is_some() { - // chain an empty column qw to the end of the line - write!(f, " & ")?; - write!(f, "{}", &RenderCommand::Qw)?; - } - } - } - - // chain an empty column qw to the end of the line - write!(f, " & ")?; - write!(f, "{}", &RenderCommand::Qw)?; - - // if this is the last key iteration, omit Nr from end of line - if i < self.circuit.len() - 1 { - // indicate a new row - write!(f, " ")?; - write!(f, "{}", &RenderCommand::Nr)?; - i += 1; - } - - // add a newline between each new line or the footer - writeln!(f)?; - } - - Ok(()) - } -} - -/// A Wire represents a single qubit. A wire only needs to keep track of all -/// the elements it contains mapped to some arbitrary column. Diagram keeps -/// track of where the Wire belongs in the larger circuit, its row, and knows -/// how each Wire relates to each other at that column. When Diagram parses the -/// wires as a collection, if the Wire relates to another at some column, then -/// its field will be updated at that column based on the knowledge Diagram has -/// about this connection. This updated value also looks arbitrary to Wire, it -/// does not explicitly define which qubit it relates to, but a digit that -/// describes how far away it is from the related qubit based on Quantikz. -#[derive(Clone, Debug, Default)] -struct Wire { - /// the Gates on the wire callable by the column - gates: HashMap, - /// at this column the wire is a control - ctrl: HashMap, - /// at this column is the wire a target? - targ: HashMap, - /// the Parameters on the wire callable by the column - parameters: HashMap>, - /// the Dagger modifiers on the wire callable by the column - daggers: HashMap>, - /// empty column - empty: HashMap, -} - -impl Wire { - /// Retrieves a gate's parameters from Expression and matches them with its - /// symbolic definition which is then stored into wire at the specific - /// column. - /// - /// # Arguments - /// `&mut self` - exposes the Wire's parameters at this column - /// `expression` - expression from Program to get name of Parameter - /// `column` - the column taking the parameters - /// `texify` - is texify_numerical_constants setting on? - fn set_param(&mut self, expression: &Expression, column: u32, texify: bool) { - // get the name of the supported expression - let text = match expression { - Expression::Address(mr) => mr.name.to_string(), - Expression::Number(c) => c.re.to_string(), - expression => expression.to_string(), - }; - - // if texify_numerical_constants - let param = if texify { - // set the texified symbol - let symbol = Parameter::Symbol(Symbol::from_str(&text).unwrap_or(Symbol::Text(text))); - - vec![symbol] - } else { - // set the symbol as text - vec![Parameter::Symbol(Symbol::Text(text))] - }; - - self.parameters.insert(column, param); - } - - /// Set target qubit at this column. - /// - /// # Arguments - /// `&mut self` - exposes the Wire's targ at this column - /// `column` - the column taking the target - fn set_targ(&mut self, column: &u32) { - self.targ.insert(*column, true); - } - - /// Set control qubit at this column at some distance from the target. The - /// distance is determined by the relative position of the control and - /// target qubits in the circuit. - /// - /// # Arguments - /// `&mut self` - exposes the Wire's ctrl at this column - /// `column` - the column taking the control - /// `ctrl` - the control qubit - /// `targ` - the target qubit - /// `circuit_qubits` - the qubits in the circuit - fn set_ctrl(&mut self, column: &u32, ctrl: &Qubit, targ: &Qubit, circuit_qubits: &[u64]) { - if let Qubit::Fixed(ctrl) = ctrl { - if let Qubit::Fixed(targ) = targ { - // get the index of the control and target qubits - let ctrl_index = circuit_qubits.iter().position(|&x| x == *ctrl); - let targ_index = circuit_qubits.iter().position(|&x| x == *targ); - - // if the control and target qubits are found - if let Some(ctrl_index) = ctrl_index { - if let Some(targ_index) = targ_index { - self.ctrl - .insert(*column, targ_index as i64 - ctrl_index as i64); - } - } - } - } - } -} - #[derive(Clone, Debug, thiserror::Error, PartialEq, Eq, Hash)] pub enum LatexGenError { #[error("Found a target qubit with no control qubit.")] @@ -688,7 +173,8 @@ impl Program { #[cfg(test)] mod tests { - use super::{FromStr, Program, RenderSettings}; + use super::{Program, RenderSettings}; + use std::str::FromStr; /// Helper function takes instructions and return the LaTeX using the /// Latex::to_latex method. @@ -860,7 +346,7 @@ mod tests { /// Test module for Quantikz Commands mod commands { - use crate::program::latex::{Parameter, RenderCommand, Symbol}; + use crate::program::latex::diagram::{Parameter, RenderCommand, Symbol}; #[test] fn test_command_left_ket() { From 90c3cb1f778b2f0aaf133dd2863f09d49e8ed41f Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 30 Mar 2023 14:18:04 -0700 Subject: [PATCH 087/118] Move settings to latex crate. --- src/program/latex/diagram/mod.rs | 6 +++--- src/program/latex/mod.rs | 10 +++++----- src/program/latex/{diagram => }/settings.rs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) rename src/program/latex/{diagram => }/settings.rs (98%) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index c31257ed..9b4f2e07 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -4,11 +4,11 @@ use std::{ str::FromStr, }; -use self::{settings::RenderSettings, wire::Wire}; -use super::LatexGenError; use crate::instruction::{Gate, GateModifier, Instruction, Qubit}; -pub(crate) mod settings; +use self::wire::Wire; +use super::{LatexGenError, RenderSettings}; + pub(crate) mod wire; /// Available commands used for building circuits with the same names taken diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 80337463..b7bff85f 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -23,15 +23,15 @@ mod diagram; -use std::collections::HashSet; -use std::fmt; +use std::{collections::HashSet, fmt}; use crate::instruction::{Instruction, Qubit}; use crate::Program; -use diagram::settings::RenderSettings; -use diagram::wire::Wire; -use diagram::Diagram; +use self::settings::RenderSettings; +use diagram::{wire::Wire, Diagram}; + +pub(crate) mod settings; /// The structure of a LaTeX document. Typically a LaTeX document contains /// metadata defining the setup and packages used in a document within a header diff --git a/src/program/latex/diagram/settings.rs b/src/program/latex/settings.rs similarity index 98% rename from src/program/latex/diagram/settings.rs rename to src/program/latex/settings.rs index c8a35a50..07fef636 100644 --- a/src/program/latex/diagram/settings.rs +++ b/src/program/latex/settings.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use super::{wire::Wire, RenderCommand}; +use super::diagram::{wire::Wire, RenderCommand}; /// RenderSettings contains the metadata that allows the user to customize how /// the circuit is rendered or use the default implementation. From ee42a06deae7fb56acb0a4b47d8561852d3bb46b Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 30 Mar 2023 15:40:48 -0700 Subject: [PATCH 088/118] Organize latex module into submodules and update docs. --- src/program/latex/diagram/mod.rs | 113 +++--------------------------- src/program/latex/diagram/wire.rs | 61 +++++++++++----- src/program/latex/mod.rs | 67 ++++++++++++++++-- src/program/latex/settings.rs | 4 +- 4 files changed, 116 insertions(+), 129 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 9b4f2e07..7794bd6f 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -7,67 +7,10 @@ use std::{ use crate::instruction::{Gate, GateModifier, Instruction, Qubit}; use self::wire::Wire; -use super::{LatexGenError, RenderSettings}; +use super::{LatexGenError, RenderCommand, RenderSettings}; pub(crate) mod wire; -/// Available commands used for building circuits with the same names taken -/// from the Quantikz documentation for easy reference. LaTeX string denoted -/// inside `backticks`. -#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash)] -pub(crate) enum RenderCommand { - /// Make a qubit "stick out" from the left. - #[display(fmt = "\\lstick{{\\ket{{q_{{{_0}}}}}}}")] - Lstick(u64), - /// Make a gate on the wire. - #[display(fmt = "\\gate{{{_0}}}")] - Gate(String), - /// Make a phase on the wire with a rotation - #[display(fmt = "\\phase{{{_0}}}")] - Phase(Parameter), - /// Add a superscript to a gate - #[display(fmt = "^{{\\{_0}}}")] - Super(String), - /// Connect the current cell to the previous cell i.e. "do nothing". - #[display(fmt = "\\qw")] - Qw, - /// Start a new row - #[display(fmt = "\\\\")] - Nr, - /// Make a control qubit. - #[display(fmt = "\\ctrl{{{_0}}}")] - Ctrl(i64), - /// Make a target qubit. - #[display(fmt = "\\targ{{}}")] - Targ, -} - -/// Types of parameters passed to commands. -#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash)] -pub(crate) enum Parameter { - /// Symbolic parameters - #[display(fmt = "{_0}")] - Symbol(Symbol), -} - -/// Supported Greek and alphanumeric symbols. -#[derive(Clone, Debug, strum::EnumString, derive_more::Display, PartialEq, Eq, Hash)] -#[strum(serialize_all = "lowercase")] -pub(crate) enum Symbol { - #[display(fmt = "\\alpha")] - Alpha, - #[display(fmt = "\\beta")] - Beta, - #[display(fmt = "\\gamma")] - Gamma, - #[display(fmt = "\\phi")] - Phi, - #[display(fmt = "\\pi")] - Pi, - #[display(fmt = "\\text{{{_0}}}")] - Text(String), -} - /// Gates written in shorthand notation, i.e. composite form, that may be /// decomposed into modifiers and single gate instructions, i.e. canonical form. #[derive(Clone, Debug, strum::EnumString, derive_more::Display, PartialEq, Eq, Hash)] @@ -87,15 +30,12 @@ enum CompositeGate { Cz, } -/// A Diagram represents a collection of wires in a circuit. It encodes the -/// wires row in the diagram and its relationship to other wires. A row is one -/// of the wires in the circuit BTreeMap. Diagram tracks relationships between -/// wires with two pieces of information--1. the wires row (its order in the -/// BTreeMap), and 2. the column that spreads between all wires that pass -/// through a multi qubit gate, e.g. CNOT. The size of the diagram can be -/// measured by multiplying the column with the length of the circuit. This is -/// an [m x n] matrix where each element in the matrix represents an item to be -/// rendered onto the diagram using one of the Quantikz commands. +/// A Diagram represents a collection of wires in a Circuit. The size of the +/// Circuit can be measured by multiplying the column with the length of the +/// Circuit. This is an [m x n] matrix where m, is the number of Quil +/// instructions (or columns) plus one empty column, and n, is the number of +/// wires. Each individual element of the matrix represents an item that can be +/// rendered onto the LaTeX document using the ``Quantikz`` RenderCommands. #[derive(Clone, Debug, Default)] pub(super) struct Diagram { /// customizes how the diagram renders the circuit @@ -114,7 +54,6 @@ impl Diagram { /// qubit wire of the circuit indicating a "do nothing" at that column. /// /// # Arguments - /// `&mut self` - exposes the wires on the Circuit /// `qubits` - exposes the qubits used in the Program /// `instruction` - exposes the qubits in a single Instruction pub(crate) fn set_empty(&mut self, qubits: &HashSet, instruction: &Instruction) { @@ -133,48 +72,12 @@ impl Diagram { } } - /// Iterates over the modifiers from the gate instruction and sets it as a - /// dagger modifier of this Wire in the Circuit at the current column. - /// Returns an Err for FORKED modifiers, and does nothing for CONTROLLED. - /// - /// # Arguments - /// `wire` - an exposed wire on the Circuit - /// `column` - the current column of the Circuit - /// `modifiers` - the modifiers from the Gate - fn extract_daggers( - wire: &mut Wire, - column: &u32, - modifiers: &Vec, - ) -> Result<(), LatexGenError> { - // set modifers - for modifier in modifiers { - match modifier { - // return error for unsupported modifier FORKED - GateModifier::Forked => { - return Err(LatexGenError::UnsupportedModifierForked); - } - // insert DAGGER - GateModifier::Dagger => { - wire.daggers - .entry(*column) - .and_modify(|m| m.push(modifier.clone())) - .or_insert_with(|| vec![modifier.clone()]); - } - // do nothing for CONTROLLED - _ => (), - } - } - - Ok(()) - } - /// Applies a gate from an instruction to the wires on the circuit /// associate with the qubits from the gate. If the gate name matches a /// composite gate, then the composite gate is applied to the circuit, /// otherwise, the original gate is applied to the circuit. /// /// # Arguments - /// `self` - exposes all attributes in the diagram /// `gate` - the Gate of the Instruction from `to_latex`. pub(crate) fn apply_gate(&mut self, gate: &Gate) -> Result<(), LatexGenError> { // set modifiers and parameters from gate instruction @@ -182,7 +85,7 @@ impl Diagram { if let Qubit::Fixed(qubit) = qubit { if let Some(wire) = self.circuit.get_mut(qubit) { // set modifiers at this column for all qubits - Self::extract_daggers(wire, &self.column, &gate.modifiers)?; + wire.extract_daggers(&self.column, &gate.modifiers)?; // set parameters at this column for all qubits for expression in &gate.parameters { diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs index 03fa5ca1..08c6573b 100644 --- a/src/program/latex/diagram/wire.rs +++ b/src/program/latex/diagram/wire.rs @@ -3,43 +3,72 @@ use std::{collections::HashMap, str::FromStr}; use crate::{ expression::Expression, instruction::{GateModifier, Qubit}, - program::latex::diagram::Symbol, }; -use super::{Parameter, RenderCommand}; +use super::super::{LatexGenError, Parameter, RenderCommand, Symbol}; -/// A Wire represents a single qubit. A wire only needs to keep track of all -/// the elements it contains mapped to some arbitrary column. Diagram keeps -/// track of where the Wire belongs in the larger circuit, its row, and knows -/// how each Wire relates to each other at that column. When Diagram parses the -/// wires as a collection, if the Wire relates to another at some column, then -/// its field will be updated at that column based on the knowledge Diagram has -/// about this connection. This updated value also looks arbitrary to Wire, it -/// does not explicitly define which qubit it relates to, but a digit that -/// describes how far away it is from the related qubit based on Quantikz. +/// A Wire represents a single qubit. This is a row vector, or [m x 1] matrix, +/// where m, is the total number of Quil instructions (or columns) plus one +/// empty column. Each column on the wire maps to some item that can be +/// rendered onto the LaTeX document using the ``Quantikz`` RenderCommands. A +/// wire is part of the Circuit which is an [m x n] matrix where n, is the +/// number of wires. #[derive(Clone, Debug, Default)] pub(crate) struct Wire { /// the Gates on the wire callable by the column pub(crate) gates: HashMap, - /// at this column the wire is a control + /// at this column the wire is a control some distance from the target pub(crate) ctrl: HashMap, /// at this column is the wire a target? pub(crate) targ: HashMap, - /// the Parameters on the wire callable by the column + /// the Parameters on the wire at this column pub(crate) parameters: HashMap>, - /// the Dagger modifiers on the wire callable by the column + /// the Dagger modifiers on the wire at this column pub(crate) daggers: HashMap>, /// empty column pub(crate) empty: HashMap, } impl Wire { + /// Iterates over the modifiers from the gate instruction and sets it as a + /// dagger modifier of this Wire in the Circuit at the current column. + /// Returns an Err for FORKED modifiers, and does nothing for CONTROLLED. + /// + /// # Arguments + /// `column` - the current column of the Circuit + /// `modifiers` - the modifiers from the Gate + pub(crate) fn extract_daggers( + &mut self, + column: &u32, + modifiers: &Vec, + ) -> Result<(), LatexGenError> { + // set modifers + for modifier in modifiers { + match modifier { + // return error for unsupported modifier FORKED + GateModifier::Forked => { + return Err(LatexGenError::UnsupportedModifierForked); + } + // insert DAGGER + GateModifier::Dagger => { + self.daggers + .entry(*column) + .and_modify(|m| m.push(modifier.clone())) + .or_insert_with(|| vec![modifier.clone()]); + } + // do nothing for CONTROLLED + _ => (), + } + } + + Ok(()) + } + /// Retrieves a gate's parameters from Expression and matches them with its /// symbolic definition which is then stored into wire at the specific /// column. /// /// # Arguments - /// `&mut self` - exposes the Wire's parameters at this column /// `expression` - expression from Program to get name of Parameter /// `column` - the column taking the parameters /// `texify` - is texify_numerical_constants setting on? @@ -68,7 +97,6 @@ impl Wire { /// Set target qubit at this column. /// /// # Arguments - /// `&mut self` - exposes the Wire's targ at this column /// `column` - the column taking the target pub(crate) fn set_targ(&mut self, column: &u32) { self.targ.insert(*column, true); @@ -79,7 +107,6 @@ impl Wire { /// target qubits in the circuit. /// /// # Arguments - /// `&mut self` - exposes the Wire's ctrl at this column /// `column` - the column taking the control /// `ctrl` - the control qubit /// `targ` - the target qubit diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index b7bff85f..49aa3153 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -31,7 +31,7 @@ use crate::Program; use self::settings::RenderSettings; use diagram::{wire::Wire, Diagram}; -pub(crate) mod settings; +pub mod settings; /// The structure of a LaTeX document. Typically a LaTeX document contains /// metadata defining the setup and packages used in a document within a header @@ -67,6 +67,63 @@ impl fmt::Display for Document { } } +/// Available commands used for building circuits with the same names taken +/// from the ``Quantikz`` documentation for easy reference. LaTeX string denoted +/// inside `backticks`. +#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash)] +pub(crate) enum RenderCommand { + /// Make a qubit "stick out" from the left. + #[display(fmt = "\\lstick{{\\ket{{q_{{{_0}}}}}}}")] + Lstick(u64), + /// Make a gate on the wire. + #[display(fmt = "\\gate{{{_0}}}")] + Gate(String), + /// Make a phase on the wire with a rotation + #[display(fmt = "\\phase{{{_0}}}")] + Phase(Parameter), + /// Add a superscript to a gate + #[display(fmt = "^{{\\{_0}}}")] + Super(String), + /// Connect the current cell to the previous cell i.e. "do nothing". + #[display(fmt = "\\qw")] + Qw, + /// Start a new row + #[display(fmt = "\\\\")] + Nr, + /// Make a control qubit. + #[display(fmt = "\\ctrl{{{_0}}}")] + Ctrl(i64), + /// Make a target qubit. + #[display(fmt = "\\targ{{}}")] + Targ, +} + +/// Types of parameters passed to commands. +#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash)] +pub(crate) enum Parameter { + /// Symbolic parameters + #[display(fmt = "{_0}")] + Symbol(Symbol), +} + +/// Supported Greek and alphanumeric symbols. +#[derive(Clone, Debug, strum::EnumString, derive_more::Display, PartialEq, Eq, Hash)] +#[strum(serialize_all = "lowercase")] +pub(crate) enum Symbol { + #[display(fmt = "\\alpha")] + Alpha, + #[display(fmt = "\\beta")] + Beta, + #[display(fmt = "\\gamma")] + Gamma, + #[display(fmt = "\\phi")] + Phi, + #[display(fmt = "\\pi")] + Pi, + #[display(fmt = "\\text{{{_0}}}")] + Text(String), +} + #[derive(Clone, Debug, thiserror::Error, PartialEq, Eq, Hash)] pub enum LatexGenError { #[error("Found a target qubit with no control qubit.")] @@ -94,7 +151,7 @@ impl Program { /// # Examples /// ``` /// // To LaTeX for the Bell State Program. - /// use quil_rs::{Program, program::latex::RenderSettings}; + /// use quil_rs::{Program, program::latex::settings::RenderSettings}; /// use std::str::FromStr; /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); /// let latex = program.to_latex(RenderSettings::default()).expect(""); @@ -102,7 +159,7 @@ impl Program { /// /// ``` /// // To LaTeX for the Toffoli Gate Program. - /// use quil_rs::{Program, program::latex::RenderSettings}; + /// use quil_rs::{Program, program::latex::settings::RenderSettings}; /// use std::str::FromStr; /// let program = Program::from_str("CONTROLLED CNOT 2 1 0").expect(""); /// let latex = program.to_latex(RenderSettings::default()).expect(""); @@ -344,9 +401,9 @@ mod tests { } } - /// Test module for Quantikz Commands + /// Test module for ``Quantikz`` Commands mod commands { - use crate::program::latex::diagram::{Parameter, RenderCommand, Symbol}; + use crate::program::latex::{Parameter, RenderCommand, Symbol}; #[test] fn test_command_left_ket() { diff --git a/src/program/latex/settings.rs b/src/program/latex/settings.rs index 07fef636..a55fb4ce 100644 --- a/src/program/latex/settings.rs +++ b/src/program/latex/settings.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use super::diagram::{wire::Wire, RenderCommand}; +use super::{diagram::wire::Wire, RenderCommand}; /// RenderSettings contains the metadata that allows the user to customize how /// the circuit is rendered or use the default implementation. @@ -51,7 +51,7 @@ impl RenderSettings { /// /// # Examples /// ``` - /// use quil_rs::{Program, program::latex::RenderSettings}; + /// use quil_rs::{Program, program::latex::settings::RenderSettings}; /// use std::str::FromStr; /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); /// let settings = RenderSettings { From 6833a9baf64636e31cd4e6bebc77a9d52cf7da55 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Thu, 30 Mar 2023 16:05:34 -0700 Subject: [PATCH 089/118] Correct docs to m is row, n is column, for m x n matrix. --- src/program/latex/diagram/mod.rs | 4 ++-- src/program/latex/diagram/wire.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 7794bd6f..0b044cdd 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -32,8 +32,8 @@ enum CompositeGate { /// A Diagram represents a collection of wires in a Circuit. The size of the /// Circuit can be measured by multiplying the column with the length of the -/// Circuit. This is an [m x n] matrix where m, is the number of Quil -/// instructions (or columns) plus one empty column, and n, is the number of +/// Circuit. This is an [m x n] matrix where n, is the number of Quil +/// instructions (or columns) plus one empty column, and m, is the number of /// wires. Each individual element of the matrix represents an item that can be /// rendered onto the LaTeX document using the ``Quantikz`` RenderCommands. #[derive(Clone, Debug, Default)] diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs index 08c6573b..23e2f2d2 100644 --- a/src/program/latex/diagram/wire.rs +++ b/src/program/latex/diagram/wire.rs @@ -7,11 +7,11 @@ use crate::{ use super::super::{LatexGenError, Parameter, RenderCommand, Symbol}; -/// A Wire represents a single qubit. This is a row vector, or [m x 1] matrix, -/// where m, is the total number of Quil instructions (or columns) plus one +/// A Wire represents a single qubit. This is a row vector, or [1 x n] matrix, +/// where n, is the total number of Quil instructions (or columns) plus one /// empty column. Each column on the wire maps to some item that can be /// rendered onto the LaTeX document using the ``Quantikz`` RenderCommands. A -/// wire is part of the Circuit which is an [m x n] matrix where n, is the +/// wire is part of the Circuit which is an [m x n] matrix where m, is the /// number of wires. #[derive(Clone, Debug, Default)] pub(crate) struct Wire { From 041b969087cfce5ad204371ae87a569c857222a1 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 31 Mar 2023 09:54:03 -0700 Subject: [PATCH 090/118] Move set_empty into gate block in to_latex. --- src/program/latex/diagram/mod.rs | 26 ++++++++++++-------------- src/program/latex/mod.rs | 6 +++--- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 0b044cdd..b3975b24 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -56,20 +56,18 @@ impl Diagram { /// # Arguments /// `qubits` - exposes the qubits used in the Program /// `instruction` - exposes the qubits in a single Instruction - pub(crate) fn set_empty(&mut self, qubits: &HashSet, instruction: &Instruction) { - if let Instruction::Gate(gate) = instruction { - qubits - .difference(&gate.qubits.iter().cloned().collect()) - .filter_map(|q| match q { - Qubit::Fixed(index) => Some(index), - _ => None, - }) - .for_each(|index| { - self.circuit - .get_mut(index) - .map(|wire| wire.empty.insert(self.column, RenderCommand::Qw)); - }); - } + pub(crate) fn set_empty(&mut self, qubits: &HashSet, gate: &Gate) { + qubits + .difference(&gate.qubits.iter().cloned().collect()) + .filter_map(|q| match q { + Qubit::Fixed(index) => Some(index), + _ => None, + }) + .for_each(|index| { + self.circuit + .get_mut(index) + .map(|wire| wire.empty.insert(self.column, RenderCommand::Qw)); + }); } /// Applies a gate from an instruction to the wires on the circuit diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 49aa3153..980b9777 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -190,11 +190,11 @@ impl Program { } for instruction in instructions { - // set QW for any unused qubits in this instruction - diagram.set_empty(&qubits, &instruction); - // parse gate instructions into a new circuit if let Instruction::Gate(gate) = instruction { + // set QW for any qubits in the circuit not used in this gate + diagram.set_empty(&qubits, &gate); + // if there are any duplicate qubits in the gate return an error if gate.qubits.len() != gate From f2d2733a05c630e755d7eb0f78e7cfcac197dcd2 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 31 Mar 2023 11:24:52 -0700 Subject: [PATCH 091/118] Pull column from Wire methods, set as field, refactor set_empty to apply_empty. --- src/program/latex/diagram/mod.rs | 34 +++++++++----------- src/program/latex/diagram/wire.rs | 52 ++++++++++++++----------------- src/program/latex/mod.rs | 4 +-- src/program/latex/settings.rs | 4 +-- 4 files changed, 41 insertions(+), 53 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index b3975b24..219d4340 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -4,7 +4,7 @@ use std::{ str::FromStr, }; -use crate::instruction::{Gate, GateModifier, Instruction, Qubit}; +use crate::instruction::{Gate, GateModifier, Qubit}; use self::wire::Wire; use super::{LatexGenError, RenderCommand, RenderSettings}; @@ -56,7 +56,7 @@ impl Diagram { /// # Arguments /// `qubits` - exposes the qubits used in the Program /// `instruction` - exposes the qubits in a single Instruction - pub(crate) fn set_empty(&mut self, qubits: &HashSet, gate: &Gate) { + pub(crate) fn apply_empty(&mut self, qubits: &HashSet, gate: &Gate) { qubits .difference(&gate.qubits.iter().cloned().collect()) .filter_map(|q| match q { @@ -64,9 +64,11 @@ impl Diagram { _ => None, }) .for_each(|index| { - self.circuit - .get_mut(index) - .map(|wire| wire.empty.insert(self.column, RenderCommand::Qw)); + if let Some(wire) = self.circuit.get_mut(index) { + // set the column on the wire for the empty slot + wire.column = self.column; + wire.set_empty() + } }); } @@ -78,20 +80,19 @@ impl Diagram { /// # Arguments /// `gate` - the Gate of the Instruction from `to_latex`. pub(crate) fn apply_gate(&mut self, gate: &Gate) -> Result<(), LatexGenError> { - // set modifiers and parameters from gate instruction + // for each fixed qubit in the gate for qubit in &gate.qubits { if let Qubit::Fixed(qubit) = qubit { if let Some(wire) = self.circuit.get_mut(qubit) { + // set the column on the wire for the gate + wire.column = self.column; + // set modifiers at this column for all qubits - wire.extract_daggers(&self.column, &gate.modifiers)?; + wire.set_daggers(&gate.modifiers)?; // set parameters at this column for all qubits for expression in &gate.parameters { - wire.set_param( - expression, - self.column, - self.settings.texify_numerical_constants, - ); + wire.set_param(expression, self.settings.texify_numerical_constants); } } } @@ -113,15 +114,10 @@ impl Diagram { if gate.qubits.len() > 1 || canonical_gate == "PHASE" { // set the target qubit if the qubit is equal to the last qubit in gate if qubit == gate.qubits.last().unwrap() { - wire.set_targ(&self.column); + wire.set_targ(); // otherwise, set the control qubit } else { - wire.set_ctrl( - &self.column, - qubit, - gate.qubits.last().unwrap(), - &circuit_qubits, - ); + wire.set_ctrl(qubit, gate.qubits.last().unwrap(), &circuit_qubits); } } else if wire.parameters.get(&self.column).is_some() { // parameterized single qubit gates are unsupported diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs index 23e2f2d2..4dfd7d6a 100644 --- a/src/program/latex/diagram/wire.rs +++ b/src/program/latex/diagram/wire.rs @@ -5,7 +5,7 @@ use crate::{ instruction::{GateModifier, Qubit}, }; -use super::super::{LatexGenError, Parameter, RenderCommand, Symbol}; +use super::super::{LatexGenError, Parameter, Symbol}; /// A Wire represents a single qubit. This is a row vector, or [1 x n] matrix, /// where n, is the total number of Quil instructions (or columns) plus one @@ -15,6 +15,8 @@ use super::super::{LatexGenError, Parameter, RenderCommand, Symbol}; /// number of wires. #[derive(Clone, Debug, Default)] pub(crate) struct Wire { + /// the column of the wire + pub(crate) column: u32, /// the Gates on the wire callable by the column pub(crate) gates: HashMap, /// at this column the wire is a control some distance from the target @@ -26,20 +28,23 @@ pub(crate) struct Wire { /// the Dagger modifiers on the wire at this column pub(crate) daggers: HashMap>, /// empty column - pub(crate) empty: HashMap, + pub(crate) empty: HashMap, } impl Wire { - /// Iterates over the modifiers from the gate instruction and sets it as a - /// dagger modifier of this Wire in the Circuit at the current column. - /// Returns an Err for FORKED modifiers, and does nothing for CONTROLLED. + /// Set empty at the current column. + pub(crate) fn set_empty(&mut self) { + self.empty.insert(self.column, true); + } + + /// Iterates over the modifiers from the gate instruction and pushes DAGGER + /// modifiers to daggers vector at the current column. Returns an Err for + /// FORKED modifiers, and does nothing for modifiers. /// /// # Arguments - /// `column` - the current column of the Circuit /// `modifiers` - the modifiers from the Gate - pub(crate) fn extract_daggers( + pub(crate) fn set_daggers( &mut self, - column: &u32, modifiers: &Vec, ) -> Result<(), LatexGenError> { // set modifers @@ -52,7 +57,7 @@ impl Wire { // insert DAGGER GateModifier::Dagger => { self.daggers - .entry(*column) + .entry(self.column) .and_modify(|m| m.push(modifier.clone())) .or_insert_with(|| vec![modifier.clone()]); } @@ -70,9 +75,8 @@ impl Wire { /// /// # Arguments /// `expression` - expression from Program to get name of Parameter - /// `column` - the column taking the parameters /// `texify` - is texify_numerical_constants setting on? - pub(crate) fn set_param(&mut self, expression: &Expression, column: u32, texify: bool) { + pub(crate) fn set_param(&mut self, expression: &Expression, texify: bool) { // get the name of the supported expression let text = match expression { Expression::Address(mr) => mr.name.to_string(), @@ -91,33 +95,23 @@ impl Wire { vec![Parameter::Symbol(Symbol::Text(text))] }; - self.parameters.insert(column, param); + self.parameters.insert(self.column, param); } - /// Set target qubit at this column. - /// - /// # Arguments - /// `column` - the column taking the target - pub(crate) fn set_targ(&mut self, column: &u32) { - self.targ.insert(*column, true); + /// Set target qubit at the current column. + pub(crate) fn set_targ(&mut self) { + self.targ.insert(self.column, true); } - /// Set control qubit at this column at some distance from the target. The - /// distance is determined by the relative position of the control and + /// Set control qubit at the current column some distance from the target. + /// The distance is determined by the relative position of the control and /// target qubits in the circuit. /// /// # Arguments - /// `column` - the column taking the control /// `ctrl` - the control qubit /// `targ` - the target qubit /// `circuit_qubits` - the qubits in the circuit - pub(crate) fn set_ctrl( - &mut self, - column: &u32, - ctrl: &Qubit, - targ: &Qubit, - circuit_qubits: &[u64], - ) { + pub(crate) fn set_ctrl(&mut self, ctrl: &Qubit, targ: &Qubit, circuit_qubits: &[u64]) { if let Qubit::Fixed(ctrl) = ctrl { if let Qubit::Fixed(targ) = targ { // get the index of the control and target qubits @@ -128,7 +122,7 @@ impl Wire { if let Some(ctrl_index) = ctrl_index { if let Some(targ_index) = targ_index { self.ctrl - .insert(*column, targ_index as i64 - ctrl_index as i64); + .insert(self.column, targ_index as i64 - ctrl_index as i64); } } } diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 980b9777..437b53ce 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -192,9 +192,6 @@ impl Program { for instruction in instructions { // parse gate instructions into a new circuit if let Instruction::Gate(gate) = instruction { - // set QW for any qubits in the circuit not used in this gate - diagram.set_empty(&qubits, &gate); - // if there are any duplicate qubits in the gate return an error if gate.qubits.len() != gate @@ -208,6 +205,7 @@ impl Program { } diagram.apply_gate(&gate)?; + diagram.apply_empty(&qubits, &gate); diagram.column += 1; } else if let Instruction::GateDefinition(_) = instruction { // GateDefinition is supported but inserted into the circuit using its Gate instruction form diff --git a/src/program/latex/settings.rs b/src/program/latex/settings.rs index a55fb4ce..52473fd7 100644 --- a/src/program/latex/settings.rs +++ b/src/program/latex/settings.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use super::{diagram::wire::Wire, RenderCommand}; +use super::diagram::wire::Wire; /// RenderSettings contains the metadata that allows the user to customize how /// the circuit is rendered or use the default implementation. @@ -83,7 +83,7 @@ impl RenderSettings { // insert empties based on total number of columns for c in 0..=last_column { - wire.empty.insert(c, RenderCommand::Qw); + wire.empty.insert(c, true); } Box::new(wire) From 1464300cf068dd13784c9db2982f6e3421401d71 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 31 Mar 2023 12:00:13 -0700 Subject: [PATCH 092/118] Refactor column from int to usize, track in wire, set total in diagram. --- src/program/latex/diagram/mod.rs | 18 +++++++++--------- src/program/latex/diagram/wire.rs | 16 ++++++++-------- src/program/latex/mod.rs | 4 ++-- src/program/latex/settings.rs | 7 +++++-- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 219d4340..44caecae 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -40,8 +40,8 @@ enum CompositeGate { pub(super) struct Diagram { /// customizes how the diagram renders the circuit pub(crate) settings: RenderSettings, - /// total number of elements on each wire - pub(crate) column: u32, + /// the total number of columns as vertical lines through all wires + pub(crate) verticals: usize, /// a BTreeMap of wires with the name of the wire as the key pub(crate) circuit: BTreeMap>, } @@ -65,8 +65,8 @@ impl Diagram { }) .for_each(|index| { if let Some(wire) = self.circuit.get_mut(index) { - // set the column on the wire for the empty slot - wire.column = self.column; + // increment the wire column + wire.column += 1; wire.set_empty() } }); @@ -84,8 +84,8 @@ impl Diagram { for qubit in &gate.qubits { if let Qubit::Fixed(qubit) = qubit { if let Some(wire) = self.circuit.get_mut(qubit) { - // set the column on the wire for the gate - wire.column = self.column; + // increment the wire column + wire.column += 1; // set modifiers at this column for all qubits wire.set_daggers(&gate.modifiers)?; @@ -119,7 +119,7 @@ impl Diagram { } else { wire.set_ctrl(qubit, gate.qubits.last().unwrap(), &circuit_qubits); } - } else if wire.parameters.get(&self.column).is_some() { + } else if wire.parameters.get(&wire.column).is_some() { // parameterized single qubit gates are unsupported return Err(LatexGenError::UnsupportedGate { gate: gate.name.clone(), @@ -127,7 +127,7 @@ impl Diagram { } // set modifiers at this column for all qubits - wire.gates.insert(self.column, canonical_gate.clone()); + wire.gates.insert(wire.column, canonical_gate.clone()); } } } @@ -158,7 +158,7 @@ impl fmt::Display for Diagram { // convert each column in the wire to string if let Some(wire) = self.circuit.get(key) { - for c in 0..self.column { + for c in 0..self.verticals { if let Some(gate) = wire.gates.get(&c) { write!(f, " & ")?; diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs index 4dfd7d6a..bf12be84 100644 --- a/src/program/latex/diagram/wire.rs +++ b/src/program/latex/diagram/wire.rs @@ -15,20 +15,20 @@ use super::super::{LatexGenError, Parameter, Symbol}; /// number of wires. #[derive(Clone, Debug, Default)] pub(crate) struct Wire { - /// the column of the wire - pub(crate) column: u32, + /// the current column of the wire + pub(crate) column: usize, /// the Gates on the wire callable by the column - pub(crate) gates: HashMap, + pub(crate) gates: HashMap, /// at this column the wire is a control some distance from the target - pub(crate) ctrl: HashMap, + pub(crate) ctrl: HashMap, /// at this column is the wire a target? - pub(crate) targ: HashMap, + pub(crate) targ: HashMap, /// the Parameters on the wire at this column - pub(crate) parameters: HashMap>, + pub(crate) parameters: HashMap>, /// the Dagger modifiers on the wire at this column - pub(crate) daggers: HashMap>, + pub(crate) daggers: HashMap>, /// empty column - pub(crate) empty: HashMap, + pub(crate) empty: HashMap, } impl Wire { diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 437b53ce..45dc9f16 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -171,6 +171,7 @@ impl Program { // initialize a new diagram let mut diagram = Diagram { settings, + verticals: instructions.len() + 1, ..Default::default() }; @@ -186,7 +187,7 @@ impl Program { // are implicit qubits required in settings and are there at least two or more qubits in the diagram? if diagram.settings.impute_missing_qubits { // add implicit qubits to circuit - RenderSettings::impute_missing_qubits(instructions.len() as u32, &mut diagram.circuit); + RenderSettings::impute_missing_qubits(instructions.len(), &mut diagram.circuit); } for instruction in instructions { @@ -206,7 +207,6 @@ impl Program { diagram.apply_gate(&gate)?; diagram.apply_empty(&qubits, &gate); - diagram.column += 1; } else if let Instruction::GateDefinition(_) = instruction { // GateDefinition is supported but inserted into the circuit using its Gate instruction form continue; diff --git a/src/program/latex/settings.rs b/src/program/latex/settings.rs index 52473fd7..28405677 100644 --- a/src/program/latex/settings.rs +++ b/src/program/latex/settings.rs @@ -60,7 +60,10 @@ impl RenderSettings { /// }; /// program.to_latex(settings).expect(""); /// ``` - pub(crate) fn impute_missing_qubits(last_column: u32, circuit: &mut BTreeMap>) { + pub(crate) fn impute_missing_qubits( + last_column: usize, + circuit: &mut BTreeMap>, + ) { let mut keys_iter = circuit.keys(); // get the first qubit in the BTreeMap @@ -82,7 +85,7 @@ impl RenderSettings { }; // insert empties based on total number of columns - for c in 0..=last_column { + for c in 0..last_column { wire.empty.insert(c, true); } From 09d7fac5e764d389eb2f15de856391cd85784819 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 31 Mar 2023 15:39:51 -0700 Subject: [PATCH 093/118] Refactor Diagram fmt to use enumerate and iters and expand comments. --- src/program/latex/diagram/mod.rs | 81 ++++++++++++++++---------------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 44caecae..9346fac9 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -4,7 +4,7 @@ use std::{ str::FromStr, }; -use crate::instruction::{Gate, GateModifier, Qubit}; +use crate::instruction::{Gate, Qubit}; use self::wire::Wire; use super::{LatexGenError, RenderCommand, RenderSettings}; @@ -140,81 +140,83 @@ impl fmt::Display for Diagram { /// Returns a result containing the Diagram Circuit as LaTeX string which /// can be input into the body of the Document. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // add a newline between the first line and the header + // write a newline between the body and the Document header writeln!(f)?; - let mut i = 0; // used to omit trailing Nr - // write the LaTeX string for each wire in the circuit - for key in self.circuit.keys() { + for (i, key) in self.circuit.keys().enumerate() { // are labels on in settings? if self.settings.label_qubit_lines { - // add label to left side of wire + // write the label to the left side of wire write!(f, "{}", RenderCommand::Lstick(*key))?; } else { - // add qw buffer to first column + // write an empty column buffer as the first column write!(f, "{}", RenderCommand::Qw)?; } - // convert each column in the wire to string + // write the LaTeX string for each item at each column in the wire if let Some(wire) = self.circuit.get(key) { - for c in 0..self.verticals { - if let Some(gate) = wire.gates.get(&c) { + for column in 0..self.verticals { + // write the string for some item at this column + if let Some(gate) = wire.gates.get(&column) { write!(f, " & ")?; + // appended to the end of the gate name let mut superscript = String::new(); - // attach modifiers to gate name if any - if let Some(modifiers) = wire.daggers.get(&c) { - for modifier in modifiers { - if let GateModifier::Dagger = modifier { - superscript.push_str( - &RenderCommand::Super(String::from("dagger")).to_string(), - ); - } - } + + // iterate over daggers and build superscript + if let Some(daggers) = wire.daggers.get(&column) { + daggers.iter().for_each(|_| { + superscript.push_str( + &RenderCommand::Super(String::from("dagger")).to_string(), + ); + }); } - if wire.ctrl.get(&c).is_some() { - // CONTROLLED qubits are displayed as `\ctrl{targ}` - if let Some(targ) = wire.ctrl.get(&c) { + // if the wire has a control at this column write the control string and continue + if wire.ctrl.get(&column).is_some() { + if let Some(targ) = wire.ctrl.get(&column) { write!(f, "{}", &(RenderCommand::Ctrl(*targ)))?; } continue; - } else if wire.targ.get(&c).is_some() { - // CONTROLLED X gates are displayed as `\targ{}` - if gate == "X" { - // set the qubit at this column as the target + // if the wire has a target at this column determine if it is associated with an X gate or a PHASE gate + } else if wire.targ.get(&column).is_some() { + // if the target is associated with an X gate determine if it is associated with dagger superscripts + if gate == "X" { let mut _gate = gate.clone(); - // if the gate contains daggers, display target as X gate with dagger superscripts + // if it is associated with dagger superscripts write it as an X gate with superscripts if !superscript.is_empty() { _gate.push_str(&superscript); + write!(f, "{}", &RenderCommand::Gate(_gate))?; - // else display X target as an open dot + + // otherwise, write it as an open dot } else { write!(f, "{}", &RenderCommand::Targ)?; } continue; - // PHASE gates are displayed as `\phase{param}` + + // otherwise, if the target is associated with a PHASE gate write it as a PHASE gate with parameters } else if gate == "PHASE" { - // set the phase parameters - if let Some(parameters) = wire.parameters.get(&c) { - for param in parameters { - write!(f, "{}", &RenderCommand::Phase(param.clone()))?; - } + if let Some(parameters) = wire.parameters.get(&column) { + parameters.iter().for_each(|p| { + write!(f, "{}", &RenderCommand::Phase(p.clone())).ok(); + }); } continue; } } - // all other gates display as `\gate{name}` - let mut _gate = gate.clone(); - // concatenate superscripts + // write all other items as a generic gate with superscripts if applicable + let mut _gate = gate.clone(); _gate.push_str(&superscript); write!(f, "{}", &RenderCommand::Gate(_gate))?; - } else if wire.empty.get(&c).is_some() { + + // otherwise, write the string as an empty column + } else if wire.empty.get(&column).is_some() { // chain an empty column qw to the end of the line write!(f, " & ")?; write!(f, "{}", &RenderCommand::Qw)?; @@ -231,10 +233,9 @@ impl fmt::Display for Diagram { // indicate a new row write!(f, " ")?; write!(f, "{}", &RenderCommand::Nr)?; - i += 1; } - // add a newline between each new line or the footer + // write a newline between each row and or the body and the document footer writeln!(f)?; } From 3010fe8aace3ffed17ad6d42e52e109e9898a134 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 31 Mar 2023 15:46:16 -0700 Subject: [PATCH 094/118] Remove enumeration in Diagram fmt. --- src/program/latex/diagram/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 9346fac9..6486a5ad 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -144,7 +144,7 @@ impl fmt::Display for Diagram { writeln!(f)?; // write the LaTeX string for each wire in the circuit - for (i, key) in self.circuit.keys().enumerate() { + for key in self.circuit.keys() { // are labels on in settings? if self.settings.label_qubit_lines { // write the label to the left side of wire @@ -229,8 +229,8 @@ impl fmt::Display for Diagram { write!(f, "{}", &RenderCommand::Qw)?; // if this is the last key iteration, omit Nr from end of line - if i < self.circuit.len() - 1 { - // indicate a new row + if key != self.circuit.keys().last().unwrap() { + // otherwise, write Nr to the end of the line write!(f, " ")?; write!(f, "{}", &RenderCommand::Nr)?; } From 680883786cc9125d905b0c5c714998647f6813f7 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 31 Mar 2023 16:28:56 -0700 Subject: [PATCH 095/118] Add inner String type to Gate and Phase variants, and implement dagger for phase. --- src/program/latex/diagram/mod.rs | 26 ++++++++------ src/program/latex/mod.rs | 35 ++++++++++++++----- ...ts__modifiers__modifier_cphase_dagger.snap | 15 ++++++++ ...sts__modifiers__modifier_phase_dagger.snap | 14 ++++++++ 4 files changed, 71 insertions(+), 19 deletions(-) create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_cphase_dagger.snap create mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_phase_dagger.snap diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 6486a5ad..dc623943 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -184,13 +184,13 @@ impl fmt::Display for Diagram { } else if wire.targ.get(&column).is_some() { // if the target is associated with an X gate determine if it is associated with dagger superscripts if gate == "X" { - let mut _gate = gate.clone(); - // if it is associated with dagger superscripts write it as an X gate with superscripts if !superscript.is_empty() { - _gate.push_str(&superscript); - - write!(f, "{}", &RenderCommand::Gate(_gate))?; + write!( + f, + "{}", + &RenderCommand::Gate(String::from("X"), superscript) + )?; // otherwise, write it as an open dot } else { @@ -202,7 +202,12 @@ impl fmt::Display for Diagram { } else if gate == "PHASE" { if let Some(parameters) = wire.parameters.get(&column) { parameters.iter().for_each(|p| { - write!(f, "{}", &RenderCommand::Phase(p.clone())).ok(); + write!( + f, + "{}", + &RenderCommand::Phase(p.clone(), superscript.clone()) + ) + .ok(); }); } continue; @@ -210,10 +215,11 @@ impl fmt::Display for Diagram { } // write all other items as a generic gate with superscripts if applicable - let mut _gate = gate.clone(); - _gate.push_str(&superscript); - - write!(f, "{}", &RenderCommand::Gate(_gate))?; + write!( + f, + "{}", + &RenderCommand::Gate(String::from(gate), superscript) + )?; // otherwise, write the string as an empty column } else if wire.empty.get(&column).is_some() { diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 45dc9f16..b1680c26 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -75,12 +75,12 @@ pub(crate) enum RenderCommand { /// Make a qubit "stick out" from the left. #[display(fmt = "\\lstick{{\\ket{{q_{{{_0}}}}}}}")] Lstick(u64), - /// Make a gate on the wire. - #[display(fmt = "\\gate{{{_0}}}")] - Gate(String), - /// Make a phase on the wire with a rotation - #[display(fmt = "\\phase{{{_0}}}")] - Phase(Parameter), + /// Make a gate on the wire with an optional superscript. + #[display(fmt = "\\gate{{{_0}{_1}}}")] + Gate(String, String), + /// Make a phase on the wire with a rotation and optional superscript + #[display(fmt = "\\phase{{{_0}{_1}}}")] + Phase(Parameter, String), /// Add a superscript to a gate #[display(fmt = "^{{\\{_0}}}")] Super(String), @@ -244,7 +244,7 @@ mod tests { /// Test functionality of to_latex using default settings. fn test_to_latex() { let latex = get_latex( - "H 0\nCNOT 0 1", + "DAGGER CPHASE(alpha) 0 1", RenderSettings { impute_missing_qubits: true, ..Default::default() @@ -393,10 +393,23 @@ mod tests { insta::assert_snapshot!(get_latex("DAGGER DAGGER Y 0", RenderSettings::default())); } + #[test] + fn test_modifier_phase_dagger() { + insta::assert_snapshot!(get_latex("DAGGER PHASE(pi) 0", RenderSettings::default())); + } + #[test] fn test_modifier_dagger_cz() { insta::assert_snapshot!(get_latex("DAGGER CZ 0 1", RenderSettings::default())); } + + #[test] + fn test_modifier_cphase_dagger() { + insta::assert_snapshot!(get_latex( + "DAGGER CPHASE(alpha) 0 1", + RenderSettings::default() + )); + } } /// Test module for ``Quantikz`` Commands @@ -410,12 +423,16 @@ mod tests { #[test] fn test_command_gate() { - insta::assert_snapshot!(RenderCommand::Gate("X".to_string()).to_string()); + insta::assert_snapshot!(RenderCommand::Gate("X".to_string(), String::new()).to_string()); } #[test] fn test_command_phase() { - insta::assert_snapshot!(RenderCommand::Phase(Parameter::Symbol(Symbol::Pi)).to_string()); + insta::assert_snapshot!(RenderCommand::Phase( + Parameter::Symbol(Symbol::Pi), + String::new() + ) + .to_string()); } #[test] diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_cphase_dagger.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_cphase_dagger.snap new file mode 100644 index 00000000..d8b2bb63 --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_cphase_dagger.snap @@ -0,0 +1,15 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 413 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\ctrl{1} & \\qw \\\\\n\\lstick{\\ket{q_{1}}} & \\phase{\\alpha^{\\dagger}} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \ctrl{1} & \qw \\ +\lstick{\ket{q_{1}}} & \phase{\alpha^{\dagger}} & \qw +\end{tikzcd} +\end{document} diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_phase_dagger.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_phase_dagger.snap new file mode 100644 index 00000000..c4a781ff --- /dev/null +++ b/src/program/latex/snapshots/quil_rs__program__latex__tests__modifiers__modifier_phase_dagger.snap @@ -0,0 +1,14 @@ +--- +source: src/program/latex/mod.rs +assertion_line: 403 +expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\lstick{\\ket{q_{0}}} & \\phase{\\pi^{\\dagger}} & \\qw\n\\end{tikzcd}\n\\end{document}\"#" +--- +\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd} +\lstick{\ket{q_{0}}} & \phase{\pi^{\dagger}} & \qw +\end{tikzcd} +\end{document} From 07b618480862dcb07a3d1fd38e267d165d32c129 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 31 Mar 2023 18:12:57 -0700 Subject: [PATCH 096/118] Remove verticals from Diagram, add Display for Wire, refactor Display for Diagram to write Wire. --- src/program/latex/diagram/mod.rs | 80 ++----------------------------- src/program/latex/diagram/wire.rs | 76 ++++++++++++++++++++++++++++- src/program/latex/mod.rs | 1 - 3 files changed, 77 insertions(+), 80 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index dc623943..8895c44e 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -40,8 +40,6 @@ enum CompositeGate { pub(super) struct Diagram { /// customizes how the diagram renders the circuit pub(crate) settings: RenderSettings, - /// the total number of columns as vertical lines through all wires - pub(crate) verticals: usize, /// a BTreeMap of wires with the name of the wire as the key pub(crate) circuit: BTreeMap>, } @@ -137,8 +135,7 @@ impl Diagram { } impl fmt::Display for Diagram { - /// Returns a result containing the Diagram Circuit as LaTeX string which - /// can be input into the body of the Document. + /// Returns a result containing the body of the Document. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // write a newline between the body and the Document header writeln!(f)?; @@ -154,80 +151,9 @@ impl fmt::Display for Diagram { write!(f, "{}", RenderCommand::Qw)?; } - // write the LaTeX string for each item at each column in the wire + // write the LaTeX string for the wire if let Some(wire) = self.circuit.get(key) { - for column in 0..self.verticals { - // write the string for some item at this column - if let Some(gate) = wire.gates.get(&column) { - write!(f, " & ")?; - - // appended to the end of the gate name - let mut superscript = String::new(); - - // iterate over daggers and build superscript - if let Some(daggers) = wire.daggers.get(&column) { - daggers.iter().for_each(|_| { - superscript.push_str( - &RenderCommand::Super(String::from("dagger")).to_string(), - ); - }); - } - - // if the wire has a control at this column write the control string and continue - if wire.ctrl.get(&column).is_some() { - if let Some(targ) = wire.ctrl.get(&column) { - write!(f, "{}", &(RenderCommand::Ctrl(*targ)))?; - } - continue; - - // if the wire has a target at this column determine if it is associated with an X gate or a PHASE gate - } else if wire.targ.get(&column).is_some() { - // if the target is associated with an X gate determine if it is associated with dagger superscripts - if gate == "X" { - // if it is associated with dagger superscripts write it as an X gate with superscripts - if !superscript.is_empty() { - write!( - f, - "{}", - &RenderCommand::Gate(String::from("X"), superscript) - )?; - - // otherwise, write it as an open dot - } else { - write!(f, "{}", &RenderCommand::Targ)?; - } - continue; - - // otherwise, if the target is associated with a PHASE gate write it as a PHASE gate with parameters - } else if gate == "PHASE" { - if let Some(parameters) = wire.parameters.get(&column) { - parameters.iter().for_each(|p| { - write!( - f, - "{}", - &RenderCommand::Phase(p.clone(), superscript.clone()) - ) - .ok(); - }); - } - continue; - } - } - - // write all other items as a generic gate with superscripts if applicable - write!( - f, - "{}", - &RenderCommand::Gate(String::from(gate), superscript) - )?; - - // otherwise, write the string as an empty column - } else if wire.empty.get(&column).is_some() { - // chain an empty column qw to the end of the line - write!(f, " & ")?; - write!(f, "{}", &RenderCommand::Qw)?; - } - } + write!(f, "{wire}")?; } // chain an empty column qw to the end of the line diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs index bf12be84..83014495 100644 --- a/src/program/latex/diagram/wire.rs +++ b/src/program/latex/diagram/wire.rs @@ -1,11 +1,11 @@ -use std::{collections::HashMap, str::FromStr}; +use std::{collections::HashMap, fmt, str::FromStr}; use crate::{ expression::Expression, instruction::{GateModifier, Qubit}, }; -use super::super::{LatexGenError, Parameter, Symbol}; +use super::super::{LatexGenError, Parameter, RenderCommand, Symbol}; /// A Wire represents a single qubit. This is a row vector, or [1 x n] matrix, /// where n, is the total number of Quil instructions (or columns) plus one @@ -129,3 +129,75 @@ impl Wire { } } } + +impl fmt::Display for Wire { + /// Returns a result containing the LaTeX string for the wire + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // write the LaTeX string for each item at each column with a length the total number of instructions in the Wire plus one empty column + for column in 0..=self.column + 1 { + // write the string for some item at this column + if let Some(gate) = self.gates.get(&column) { + write!(f, " & ")?; + + // appended to the end of the gate name + let mut superscript = String::new(); + + // iterate over daggers and build superscript + if let Some(daggers) = self.daggers.get(&column) { + daggers.iter().for_each(|_| { + superscript + .push_str(&RenderCommand::Super(String::from("dagger")).to_string()); + }); + } + + // if the wire has a control at this column write the control string and continue + if self.ctrl.get(&column).is_some() { + if let Some(targ) = self.ctrl.get(&column) { + write!(f, "{}", &(RenderCommand::Ctrl(*targ)))?; + } + continue; + + // if the wire has a target at this column determine if it is associated with an X gate or a PHASE gate + } else if self.targ.get(&column).is_some() { + // if the target is associated with an X gate determine if it is associated with dagger superscripts + if gate == "X" { + // if it is associated with dagger superscripts write it as an X gate with superscripts + if !superscript.is_empty() { + write!(f, "{}", &RenderCommand::Gate("X".to_string(), superscript))?; + + // otherwise, write it as an open dot + } else { + write!(f, "{}", &RenderCommand::Targ)?; + } + continue; + + // otherwise, if the target is associated with a PHASE gate write it as a PHASE gate with parameters + } else if gate == "PHASE" { + if let Some(parameters) = self.parameters.get(&column) { + parameters.iter().for_each(|p| { + write!( + f, + "{}", + &RenderCommand::Phase(p.clone(), superscript.clone()) + ) + .ok(); + }); + } + continue; + } + } + + // write all other items as a generic gate with superscripts if applicable + write!(f, "{}", &RenderCommand::Gate(gate.to_string(), superscript))?; + + // otherwise, write the string as an empty column + } else if self.empty.get(&column).is_some() { + // chain an empty column qw to the end of the line + write!(f, " & ")?; + write!(f, "{}", &RenderCommand::Qw)?; + } + } + + Ok(()) + } +} diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index b1680c26..f62fb2ed 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -171,7 +171,6 @@ impl Program { // initialize a new diagram let mut diagram = Diagram { settings, - verticals: instructions.len() + 1, ..Default::default() }; From f7e040914024ff1b796bf341ed0d19ca04fe03ee Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 31 Mar 2023 19:21:48 -0700 Subject: [PATCH 097/118] Iterate over circuit instead of keys in Diagram fmt and update docs. --- src/program/latex/diagram/mod.rs | 26 ++++++++++++-------------- src/program/latex/diagram/wire.rs | 11 +++++------ 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 8895c44e..b2e3032c 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -31,11 +31,11 @@ enum CompositeGate { } /// A Diagram represents a collection of wires in a Circuit. The size of the -/// Circuit can be measured by multiplying the column with the length of the -/// Circuit. This is an [m x n] matrix where n, is the number of Quil -/// instructions (or columns) plus one empty column, and m, is the number of -/// wires. Each individual element of the matrix represents an item that can be -/// rendered onto the LaTeX document using the ``Quantikz`` RenderCommands. +/// Diagram can be measured by multiplying the number of Instructions in a +/// Program with the length of the Circuit. This is an [m x n] matrix where n, +/// is the number of Quil instructions (or columns), and m, is the number of +/// wires (or rows). Each individual element of the matrix represents an item +/// that is serializable into LaTeX using the ``Quantikz`` RenderCommands. #[derive(Clone, Debug, Default)] pub(super) struct Diagram { /// customizes how the diagram renders the circuit @@ -141,28 +141,26 @@ impl fmt::Display for Diagram { writeln!(f)?; // write the LaTeX string for each wire in the circuit - for key in self.circuit.keys() { + for (qubit, wire) in &self.circuit { // are labels on in settings? if self.settings.label_qubit_lines { // write the label to the left side of wire - write!(f, "{}", RenderCommand::Lstick(*key))?; + write!(f, "{}", RenderCommand::Lstick(*qubit))?; } else { // write an empty column buffer as the first column write!(f, "{}", RenderCommand::Qw)?; } // write the LaTeX string for the wire - if let Some(wire) = self.circuit.get(key) { - write!(f, "{wire}")?; - } + write!(f, "{wire}")?; - // chain an empty column qw to the end of the line + // chain an empty column to the end of the line write!(f, " & ")?; write!(f, "{}", &RenderCommand::Qw)?; - // if this is the last key iteration, omit Nr from end of line - if key != self.circuit.keys().last().unwrap() { - // otherwise, write Nr to the end of the line + // if this is the last iteration, omit a new row from the end of the line + if *qubit != *self.circuit.keys().last().unwrap() { + // otherwise, write a new row to the end of the line write!(f, " ")?; write!(f, "{}", &RenderCommand::Nr)?; } diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs index 83014495..7bbc746a 100644 --- a/src/program/latex/diagram/wire.rs +++ b/src/program/latex/diagram/wire.rs @@ -8,11 +8,10 @@ use crate::{ use super::super::{LatexGenError, Parameter, RenderCommand, Symbol}; /// A Wire represents a single qubit. This is a row vector, or [1 x n] matrix, -/// where n, is the total number of Quil instructions (or columns) plus one -/// empty column. Each column on the wire maps to some item that can be -/// rendered onto the LaTeX document using the ``Quantikz`` RenderCommands. A -/// wire is part of the Circuit which is an [m x n] matrix where m, is the -/// number of wires. +/// where n, is the total number of Quil instructions (or columns). Each column +/// on the wire maps to some item that can be serialized into LaTeX using the +/// ``Quantikz`` RenderCommands. A wire is part of the Circuit which is an [m x +/// n] matrix where m, is the total number of wires, or length, of the Circuit. #[derive(Clone, Debug, Default)] pub(crate) struct Wire { /// the current column of the wire @@ -163,7 +162,7 @@ impl fmt::Display for Wire { if gate == "X" { // if it is associated with dagger superscripts write it as an X gate with superscripts if !superscript.is_empty() { - write!(f, "{}", &RenderCommand::Gate("X".to_string(), superscript))?; + write!(f, "{}", &RenderCommand::Gate(gate.to_string(), superscript))?; // otherwise, write it as an open dot } else { From 08a60137604b3f252c4ef9d6659cf406742b0f5d Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 3 Apr 2023 10:14:51 -0700 Subject: [PATCH 098/118] Move unwrap for last item in collections to outside of loop. --- src/program/latex/diagram/mod.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index b2e3032c..c58db26c 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -105,17 +105,18 @@ impl Diagram { let circuit_qubits: Vec = self.circuit.keys().cloned().collect(); // set gate for each qubit in the instruction + let targ = gate.qubits.last().unwrap(); for qubit in &gate.qubits { if let Qubit::Fixed(instruction_qubit) = qubit { if let Some(wire) = self.circuit.get_mut(instruction_qubit) { // set the control and target qubits if gate.qubits.len() > 1 || canonical_gate == "PHASE" { // set the target qubit if the qubit is equal to the last qubit in gate - if qubit == gate.qubits.last().unwrap() { + if qubit == targ { wire.set_targ(); // otherwise, set the control qubit } else { - wire.set_ctrl(qubit, gate.qubits.last().unwrap(), &circuit_qubits); + wire.set_ctrl(qubit, targ, &circuit_qubits); } } else if wire.parameters.get(&wire.column).is_some() { // parameterized single qubit gates are unsupported @@ -141,6 +142,7 @@ impl fmt::Display for Diagram { writeln!(f)?; // write the LaTeX string for each wire in the circuit + let last = self.circuit.keys().last().unwrap_or(&0); for (qubit, wire) in &self.circuit { // are labels on in settings? if self.settings.label_qubit_lines { @@ -158,8 +160,8 @@ impl fmt::Display for Diagram { write!(f, " & ")?; write!(f, "{}", &RenderCommand::Qw)?; - // if this is the last iteration, omit a new row from the end of the line - if *qubit != *self.circuit.keys().last().unwrap() { + // omit a new row if this is the last qubit wire + if *qubit != *last { // otherwise, write a new row to the end of the line write!(f, " ")?; write!(f, "{}", &RenderCommand::Nr)?; From 68cc16eeec162a13ac3246a8cfa5fd1978c78546 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 3 Apr 2023 16:24:35 -0700 Subject: [PATCH 099/118] Use regex for canonical gate decomposition and handle unimplemented XY gate. --- Cargo.lock | 47 ++++++++++++++++++++++++++++---- Cargo.toml | 3 +- src/program/latex/diagram/mod.rs | 47 +++++++++++++++----------------- src/program/latex/mod.rs | 12 ++++++++ 4 files changed, 77 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a1a9f36b..73128f5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + [[package]] name = "anes" version = "0.1.6" @@ -463,6 +472,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy-regex" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff63c423c68ea6814b7da9e88ce585f793c87ddd9e78f646970891769c8235d4" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8edfc11b8f56ce85e207e62ea21557cfa09bb24a8f6b04ae181b086ff8611c22" +dependencies = [ + "proc-macro2 1.0.47", + "quote 1.0.21", + "regex", + "syn 1.0.103", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -635,9 +667,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.15.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "oorandom" @@ -777,6 +809,7 @@ dependencies = [ "dot-writer", "indexmap", "insta", + "lazy-regex", "lexical", "nom", "nom_locate", @@ -882,18 +915,20 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "remove_dir_all" diff --git a/Cargo.toml b/Cargo.toml index a3ea5610..babee73c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ categories = ["parser-implementations", "science", "compilers", "emulators"] derive_more = { version = "0.99.17", optional = true } dot-writer = { version = "0.1.2", optional = true } indexmap = "1.6.1" +lazy-regex = { version = "2.5.0", optional = true } lexical = "6.1.1" nom = "7.1.1" nom_locate = "4.0.0" @@ -30,7 +31,7 @@ rstest = "0.15.0" [features] graphviz-dot = ["dot-writer"] -latex = ["derive_more"] +latex = ["derive_more", "lazy-regex"] [profile.release] lto = true diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index c58db26c..33bbea9a 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -1,9 +1,10 @@ use std::{ collections::{BTreeMap, HashSet}, fmt, - str::FromStr, }; +use lazy_regex::{Lazy, Regex}; + use crate::instruction::{Gate, Qubit}; use self::wire::Wire; @@ -11,25 +12,6 @@ use super::{LatexGenError, RenderCommand, RenderSettings}; pub(crate) mod wire; -/// Gates written in shorthand notation, i.e. composite form, that may be -/// decomposed into modifiers and single gate instructions, i.e. canonical form. -#[derive(Clone, Debug, strum::EnumString, derive_more::Display, PartialEq, Eq, Hash)] -#[strum(serialize_all = "UPPERCASE")] -enum CompositeGate { - /// `CNOT` is `CONTROLLED X` - #[display(fmt = "X")] - Cnot, - /// `CCNOT` is `CONTROLLED CONTROLLED X` - #[display(fmt = "X")] - Ccnot, - /// `CPHASE` is `CONTROLLED PHASE` - #[display(fmt = "PHASE")] - Cphase, - /// `CZ` is `CONTROLLED Z` - #[display(fmt = "Z")] - Cz, -} - /// A Diagram represents a collection of wires in a Circuit. The size of the /// Diagram can be measured by multiplying the number of Instructions in a /// Program with the length of the Circuit. This is an [m x n] matrix where n, @@ -96,10 +78,19 @@ impl Diagram { } } - // get display of gate name from composite gate or original gate - let canonical_gate = CompositeGate::from_str(&gate.name) - .map(|g| g.to_string()) - .unwrap_or(gate.name.clone()); + // if the gate is a composite gate, then apply the composite gate + static ABBREVIATED_CONTROLLED_GATE: Lazy = + Lazy::new(|| Regex::new("(?PC+)(?PPHASE|X|Y|Z|NOT)").unwrap()); + + let mut canonical_gate = gate.name.clone(); + if let Some(captures) = ABBREVIATED_CONTROLLED_GATE.captures(&gate.name) { + let base = captures.name("base").unwrap().as_str(); + + match base { + "NOT" => canonical_gate = "X".to_string(), + _ => canonical_gate = base.to_string(), + } + } // get the names of the qubits in the circuit before circuit is borrowed as mutable let circuit_qubits: Vec = self.circuit.keys().cloned().collect(); @@ -111,6 +102,12 @@ impl Diagram { if let Some(wire) = self.circuit.get_mut(instruction_qubit) { // set the control and target qubits if gate.qubits.len() > 1 || canonical_gate == "PHASE" { + if canonical_gate == "XY" { + return Err(LatexGenError::UnsupportedGate { + gate: gate.name.clone(), + }); + } + // set the target qubit if the qubit is equal to the last qubit in gate if qubit == targ { wire.set_targ(); @@ -126,7 +123,7 @@ impl Diagram { } // set modifiers at this column for all qubits - wire.gates.insert(wire.column, canonical_gate.clone()); + wire.gates.insert(wire.column, canonical_gate.to_string()); } } } diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index f62fb2ed..9e16de52 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -359,6 +359,18 @@ mod tests { fn test_gate_cz() { insta::assert_snapshot!(get_latex("CZ 0 1", RenderSettings::default())); } + + #[test] + #[should_panic] + fn test_gate_xy() { + get_latex("XY 0 1", RenderSettings::default()); + } + + #[test] + #[should_panic] + fn test_gate_controlled_xy() { + get_latex("CONTROLLED XY 0 1 2", RenderSettings::default()); + } } /// Test module for modifiers From 4f9cae4cf57ed8b436a5ee42b54b62bbd0dec2d4 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 4 Apr 2023 12:43:37 -0700 Subject: [PATCH 100/118] Merge targ and ctrl fields into single generic gates field. --- src/program/latex/diagram/mod.rs | 17 ++---- src/program/latex/diagram/wire.rs | 92 ++++++++++++++----------------- src/program/latex/mod.rs | 5 +- src/program/latex/settings.rs | 6 +- 4 files changed, 54 insertions(+), 66 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 33bbea9a..c1bc7f69 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -7,7 +7,7 @@ use lazy_regex::{Lazy, Regex}; use crate::instruction::{Gate, Qubit}; -use self::wire::Wire; +use self::wire::{Wire, T}; use super::{LatexGenError, RenderCommand, RenderSettings}; pub(crate) mod wire; @@ -45,8 +45,6 @@ impl Diagram { }) .for_each(|index| { if let Some(wire) = self.circuit.get_mut(index) { - // increment the wire column - wire.column += 1; wire.set_empty() } }); @@ -64,9 +62,6 @@ impl Diagram { for qubit in &gate.qubits { if let Qubit::Fixed(qubit) = qubit { if let Some(wire) = self.circuit.get_mut(qubit) { - // increment the wire column - wire.column += 1; - // set modifiers at this column for all qubits wire.set_daggers(&gate.modifiers)?; @@ -110,20 +105,20 @@ impl Diagram { // set the target qubit if the qubit is equal to the last qubit in gate if qubit == targ { - wire.set_targ(); + wire.set_targ(canonical_gate.to_string()); // otherwise, set the control qubit } else { wire.set_ctrl(qubit, targ, &circuit_qubits); } - } else if wire.parameters.get(&wire.column).is_some() { + } else if wire.parameters.get(&wire.gates.len()).is_some() { // parameterized single qubit gates are unsupported return Err(LatexGenError::UnsupportedGate { gate: gate.name.clone(), }); + } else { + // set modifiers at this column for all qubits + wire.gates.push(T::Standard(canonical_gate.to_string())); } - - // set modifiers at this column for all qubits - wire.gates.insert(wire.column, canonical_gate.to_string()); } } } diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs index 7bbc746a..25e85893 100644 --- a/src/program/latex/diagram/wire.rs +++ b/src/program/latex/diagram/wire.rs @@ -14,26 +14,27 @@ use super::super::{LatexGenError, Parameter, RenderCommand, Symbol}; /// n] matrix where m, is the total number of wires, or length, of the Circuit. #[derive(Clone, Debug, Default)] pub(crate) struct Wire { - /// the current column of the wire - pub(crate) column: usize, /// the Gates on the wire callable by the column - pub(crate) gates: HashMap, - /// at this column the wire is a control some distance from the target - pub(crate) ctrl: HashMap, - /// at this column is the wire a target? - pub(crate) targ: HashMap, + pub(crate) gates: Vec, /// the Parameters on the wire at this column pub(crate) parameters: HashMap>, /// the Dagger modifiers on the wire at this column pub(crate) daggers: HashMap>, - /// empty column - pub(crate) empty: HashMap, +} + +#[derive(Clone, Debug, Default)] +pub(crate) enum T { + #[default] + Empty, + Standard(String), + Ctrl(i64), + Targ(String), } impl Wire { /// Set empty at the current column. pub(crate) fn set_empty(&mut self) { - self.empty.insert(self.column, true); + self.gates.push(T::Empty); } /// Iterates over the modifiers from the gate instruction and pushes DAGGER @@ -56,7 +57,7 @@ impl Wire { // insert DAGGER GateModifier::Dagger => { self.daggers - .entry(self.column) + .entry(self.gates.len() - 1) .and_modify(|m| m.push(modifier.clone())) .or_insert_with(|| vec![modifier.clone()]); } @@ -94,12 +95,12 @@ impl Wire { vec![Parameter::Symbol(Symbol::Text(text))] }; - self.parameters.insert(self.column, param); + self.parameters.insert(self.gates.len() - 1, param); } /// Set target qubit at the current column. - pub(crate) fn set_targ(&mut self) { - self.targ.insert(self.column, true); + pub(crate) fn set_targ(&mut self, gate: String) { + self.gates.push(T::Targ(gate)); } /// Set control qubit at the current column some distance from the target. @@ -120,8 +121,8 @@ impl Wire { // if the control and target qubits are found if let Some(ctrl_index) = ctrl_index { if let Some(targ_index) = targ_index { - self.ctrl - .insert(self.column, targ_index as i64 - ctrl_index as i64); + self.gates + .push(T::Ctrl(targ_index as i64 - ctrl_index as i64)); } } } @@ -133,32 +134,32 @@ impl fmt::Display for Wire { /// Returns a result containing the LaTeX string for the wire fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // write the LaTeX string for each item at each column with a length the total number of instructions in the Wire plus one empty column - for column in 0..=self.column + 1 { - // write the string for some item at this column - if let Some(gate) = self.gates.get(&column) { - write!(f, " & ")?; - - // appended to the end of the gate name - let mut superscript = String::new(); - - // iterate over daggers and build superscript - if let Some(daggers) = self.daggers.get(&column) { - daggers.iter().for_each(|_| { - superscript - .push_str(&RenderCommand::Super(String::from("dagger")).to_string()); - }); - } + for column in 0..self.gates.len() { + write!(f, " & ")?; - // if the wire has a control at this column write the control string and continue - if self.ctrl.get(&column).is_some() { - if let Some(targ) = self.ctrl.get(&column) { - write!(f, "{}", &(RenderCommand::Ctrl(*targ)))?; - } - continue; + // appended to the end of the gate name + let mut superscript = String::new(); + + // iterate over daggers and build superscript + if let Some(daggers) = self.daggers.get(&column) { + daggers.iter().for_each(|_| { + superscript.push_str(&RenderCommand::Super(String::from("dagger")).to_string()); + }); + } - // if the wire has a target at this column determine if it is associated with an X gate or a PHASE gate - } else if self.targ.get(&column).is_some() { - // if the target is associated with an X gate determine if it is associated with dagger superscripts + match &self.gates[column] { + T::Empty => { + // chain an empty column qw to the end of the line + // write!(f, " & ")?; + write!(f, "{}", &RenderCommand::Qw)?; + } + T::Standard(gate) => { + write!(f, "{}", &RenderCommand::Gate(gate.to_string(), superscript))?; + } + T::Ctrl(targ) => { + write!(f, "{}", &(RenderCommand::Ctrl(*targ)))?; + } + T::Targ(gate) => { if gate == "X" { // if it is associated with dagger superscripts write it as an X gate with superscripts if !superscript.is_empty() { @@ -169,8 +170,6 @@ impl fmt::Display for Wire { write!(f, "{}", &RenderCommand::Targ)?; } continue; - - // otherwise, if the target is associated with a PHASE gate write it as a PHASE gate with parameters } else if gate == "PHASE" { if let Some(parameters) = self.parameters.get(&column) { parameters.iter().for_each(|p| { @@ -185,15 +184,6 @@ impl fmt::Display for Wire { continue; } } - - // write all other items as a generic gate with superscripts if applicable - write!(f, "{}", &RenderCommand::Gate(gate.to_string(), superscript))?; - - // otherwise, write the string as an empty column - } else if self.empty.get(&column).is_some() { - // chain an empty column qw to the end of the line - write!(f, " & ")?; - write!(f, "{}", &RenderCommand::Qw)?; } } diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 9e16de52..aac41751 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -178,7 +178,10 @@ impl Program { let qubits = Program::get_used_qubits(self); for qubit in &qubits { if let Qubit::Fixed(name) = qubit { - let wire = Wire::default(); + let wire = Wire { + gates: Vec::with_capacity(instructions.len()), + ..Default::default() + }; diagram.circuit.insert(*name, Box::new(wire)); } } diff --git a/src/program/latex/settings.rs b/src/program/latex/settings.rs index 28405677..88108387 100644 --- a/src/program/latex/settings.rs +++ b/src/program/latex/settings.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use super::diagram::wire::Wire; +use super::diagram::wire::{Wire, T}; /// RenderSettings contains the metadata that allows the user to customize how /// the circuit is rendered or use the default implementation. @@ -85,8 +85,8 @@ impl RenderSettings { }; // insert empties based on total number of columns - for c in 0..last_column { - wire.empty.insert(c, true); + for _ in 0..last_column { + wire.gates.push(T::Empty) } Box::new(wire) From 755883df424663d2c49a222c9d2d90b3abe73c26 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 5 Apr 2023 14:09:40 -0700 Subject: [PATCH 101/118] Use the generic gate and counted modifiers, and catch unsupported gates using the CONTROLLED count. --- src/program/latex/diagram/mod.rs | 73 ++++++--------- src/program/latex/diagram/wire.rs | 142 +++++++++++++++++------------- 2 files changed, 110 insertions(+), 105 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index c1bc7f69..b8fde253 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -3,11 +3,9 @@ use std::{ fmt, }; -use lazy_regex::{Lazy, Regex}; - use crate::instruction::{Gate, Qubit}; -use self::wire::{Wire, T}; +use self::wire::Wire; use super::{LatexGenError, RenderCommand, RenderSettings}; pub(crate) mod wire; @@ -50,22 +48,16 @@ impl Diagram { }); } - /// Applies a gate from an instruction to the wires on the circuit - /// associate with the qubits from the gate. If the gate name matches a - /// composite gate, then the composite gate is applied to the circuit, - /// otherwise, the original gate is applied to the circuit. + /// Applies a gate from an instruction to the associated wires in the + /// circuit of this diagram. /// /// # Arguments /// `gate` - the Gate of the Instruction from `to_latex`. pub(crate) fn apply_gate(&mut self, gate: &Gate) -> Result<(), LatexGenError> { - // for each fixed qubit in the gate + // set the parameters for each qubit in the instruction for qubit in &gate.qubits { if let Qubit::Fixed(qubit) = qubit { if let Some(wire) = self.circuit.get_mut(qubit) { - // set modifiers at this column for all qubits - wire.set_daggers(&gate.modifiers)?; - - // set parameters at this column for all qubits for expression in &gate.parameters { wire.set_param(expression, self.settings.texify_numerical_constants); } @@ -73,51 +65,42 @@ impl Diagram { } } - // if the gate is a composite gate, then apply the composite gate - static ABBREVIATED_CONTROLLED_GATE: Lazy = - Lazy::new(|| Regex::new("(?PC+)(?PPHASE|X|Y|Z|NOT)").unwrap()); - - let mut canonical_gate = gate.name.clone(); - if let Some(captures) = ABBREVIATED_CONTROLLED_GATE.captures(&gate.name) { - let base = captures.name("base").unwrap().as_str(); - - match base { - "NOT" => canonical_gate = "X".to_string(), - _ => canonical_gate = base.to_string(), - } - } - // get the names of the qubits in the circuit before circuit is borrowed as mutable let circuit_qubits: Vec = self.circuit.keys().cloned().collect(); // set gate for each qubit in the instruction - let targ = gate.qubits.last().unwrap(); - for qubit in &gate.qubits { + let last_qubit = gate.qubits.last().unwrap(); + for (i, qubit) in gate.qubits.iter().enumerate() { if let Qubit::Fixed(instruction_qubit) = qubit { if let Some(wire) = self.circuit.get_mut(instruction_qubit) { - // set the control and target qubits - if gate.qubits.len() > 1 || canonical_gate == "PHASE" { - if canonical_gate == "XY" { - return Err(LatexGenError::UnsupportedGate { - gate: gate.name.clone(), - }); + // push the gate to the wire + wire.gates.push(wire::T::try_from(gate.clone())?); + + // update the last gate to a control gate if the instruction has more than one qubit + if gate.qubits.len() > 1 { + if let wire::T::StdGate { + name, + dagger_count: _, + ctrl_count, + } = wire.gates.last().unwrap() + { + if i != gate.qubits.len() - 1 { + // reset the last gate to a control gate + wire.gates.pop(); + wire.set_ctrl(qubit, last_qubit, &circuit_qubits); + } else if i == gate.qubits.len() - 1 && ctrl_count - i > 1 { + // there should be no more than one control modifier from the last qubit in a control and target instruction + return Err(LatexGenError::UnsupportedGate { gate: name.clone() }); + } } + } - // set the target qubit if the qubit is equal to the last qubit in gate - if qubit == targ { - wire.set_targ(canonical_gate.to_string()); - // otherwise, set the control qubit - } else { - wire.set_ctrl(qubit, targ, &circuit_qubits); - } - } else if wire.parameters.get(&wire.gates.len()).is_some() { + // parameterized non-PHASE gates are unsupported + if !wire.parameters.is_empty() && !gate.name.contains("PHASE") { // parameterized single qubit gates are unsupported return Err(LatexGenError::UnsupportedGate { gate: gate.name.clone(), }); - } else { - // set modifiers at this column for all qubits - wire.gates.push(T::Standard(canonical_gate.to_string())); } } } diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs index 25e85893..fa20e2b4 100644 --- a/src/program/latex/diagram/wire.rs +++ b/src/program/latex/diagram/wire.rs @@ -1,8 +1,10 @@ use std::{collections::HashMap, fmt, str::FromStr}; +use lazy_regex::{Lazy, Regex}; + use crate::{ expression::Expression, - instruction::{GateModifier, Qubit}, + instruction::{Gate, GateModifier, Qubit}, }; use super::super::{LatexGenError, Parameter, RenderCommand, Symbol}; @@ -18,55 +20,83 @@ pub(crate) struct Wire { pub(crate) gates: Vec, /// the Parameters on the wire at this column pub(crate) parameters: HashMap>, - /// the Dagger modifiers on the wire at this column - pub(crate) daggers: HashMap>, } +/// A T represents a Gate that can be pushed onto a Wire. The ``Gate`` struct +/// in the ``instruction`` module is used to form this T variant which is +/// pushed onto the Wire and then serialized into LaTeX using the associated +/// ``Quantikz`` RenderCommand. #[derive(Clone, Debug, Default)] pub(crate) enum T { #[default] Empty, - Standard(String), + StdGate { + name: String, + dagger_count: usize, + ctrl_count: usize, + }, Ctrl(i64), - Targ(String), } -impl Wire { - /// Set empty at the current column. - pub(crate) fn set_empty(&mut self) { - self.gates.push(T::Empty); - } +impl TryFrom for T { + type Error = LatexGenError; - /// Iterates over the modifiers from the gate instruction and pushes DAGGER - /// modifiers to daggers vector at the current column. Returns an Err for - /// FORKED modifiers, and does nothing for modifiers. + /// Returns a StdGate that can be pushed onto the wire. This gate is first + /// decomposed into canonical form and may contain modifiers. The modifiers + /// are counted and applied to the gate. If the modifier is FORKED, then an + /// error returned. This is because FORKED is not supported by the + /// ``Quantikz`` library. /// /// # Arguments - /// `modifiers` - the modifiers from the Gate - pub(crate) fn set_daggers( - &mut self, - modifiers: &Vec, - ) -> Result<(), LatexGenError> { - // set modifers - for modifier in modifiers { + /// `gate` - the Gate of the Instruction from `to_latex`. + fn try_from(gate: Gate) -> Result { + // if the gate is a composite gate, then apply the composite gate + static ABBREVIATED_CONTROLLED_GATE: Lazy = + Lazy::new(|| Regex::new("(?PC+)(?PPHASE|X|Y|Z|NOT)").unwrap()); + + let mut canonical_gate = gate.name.to_string(); + let mut ctrl_count = 0; + if let Some(captures) = ABBREVIATED_CONTROLLED_GATE.captures(&gate.name) { + let base = captures.name("base").unwrap().as_str(); + ctrl_count = captures.name("count").unwrap().as_str().len(); + + // convert abbreviated controlled gates to canonical form. If the base is NOT, then this is an X gate. + match base { + "NOT" => canonical_gate = "X".to_string(), + _ => canonical_gate = base.to_string(), + } + } + + // count the supported modifiers + let mut dagger_count = 0; + for modifier in gate.modifiers { match modifier { // return error for unsupported modifier FORKED GateModifier::Forked => { return Err(LatexGenError::UnsupportedModifierForked); } - // insert DAGGER GateModifier::Dagger => { - self.daggers - .entry(self.gates.len() - 1) - .and_modify(|m| m.push(modifier.clone())) - .or_insert_with(|| vec![modifier.clone()]); + dagger_count += 1; + } + GateModifier::Controlled => { + ctrl_count += 1; } - // do nothing for CONTROLLED - _ => (), } } - Ok(()) + // return the StdGate + Ok(T::StdGate { + name: canonical_gate, + dagger_count, + ctrl_count, + }) + } +} + +impl Wire { + /// Set empty at the current column. + pub(crate) fn set_empty(&mut self) { + self.gates.push(T::Empty); } /// Retrieves a gate's parameters from Expression and matches them with its @@ -95,12 +125,7 @@ impl Wire { vec![Parameter::Symbol(Symbol::Text(text))] }; - self.parameters.insert(self.gates.len() - 1, param); - } - - /// Set target qubit at the current column. - pub(crate) fn set_targ(&mut self, gate: String) { - self.gates.push(T::Targ(gate)); + self.parameters.insert(self.gates.len(), param); } /// Set control qubit at the current column some distance from the target. @@ -140,37 +165,23 @@ impl fmt::Display for Wire { // appended to the end of the gate name let mut superscript = String::new(); - // iterate over daggers and build superscript - if let Some(daggers) = self.daggers.get(&column) { - daggers.iter().for_each(|_| { - superscript.push_str(&RenderCommand::Super(String::from("dagger")).to_string()); - }); - } - match &self.gates[column] { T::Empty => { // chain an empty column qw to the end of the line // write!(f, " & ")?; write!(f, "{}", &RenderCommand::Qw)?; } - T::Standard(gate) => { - write!(f, "{}", &RenderCommand::Gate(gate.to_string(), superscript))?; - } - T::Ctrl(targ) => { - write!(f, "{}", &(RenderCommand::Ctrl(*targ)))?; - } - T::Targ(gate) => { - if gate == "X" { - // if it is associated with dagger superscripts write it as an X gate with superscripts - if !superscript.is_empty() { - write!(f, "{}", &RenderCommand::Gate(gate.to_string(), superscript))?; - - // otherwise, write it as an open dot - } else { - write!(f, "{}", &RenderCommand::Targ)?; - } - continue; - } else if gate == "PHASE" { + T::StdGate { + name, + dagger_count, + ctrl_count, + } => { + (0..*dagger_count).for_each(|_| { + superscript + .push_str(&RenderCommand::Super(String::from("dagger")).to_string()); + }); + + if name == "PHASE" { if let Some(parameters) = self.parameters.get(&column) { parameters.iter().for_each(|p| { write!( @@ -181,9 +192,20 @@ impl fmt::Display for Wire { .ok(); }); } - continue; + } else if name == "X" { + // if it is associated with dagger superscripts write it as an X gate with superscripts + if dagger_count > &0 || ctrl_count == &0 { + write!(f, "{}", &RenderCommand::Gate(name.to_string(), superscript))?; + } else { + write!(f, "{}", &RenderCommand::Targ)?; + } + } else { + write!(f, "{}", &RenderCommand::Gate(name.to_string(), superscript))?; } } + T::Ctrl(targ) => { + write!(f, "{}", &(RenderCommand::Ctrl(*targ)))?; + } } } From 8cf007332fd68c1d1853cb9fe0121e350843002a Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 5 Apr 2023 15:52:43 -0700 Subject: [PATCH 102/118] Change parameters values from vector to single parameter. --- src/program/latex/diagram/wire.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs index fa20e2b4..eb7add6e 100644 --- a/src/program/latex/diagram/wire.rs +++ b/src/program/latex/diagram/wire.rs @@ -19,7 +19,7 @@ pub(crate) struct Wire { /// the Gates on the wire callable by the column pub(crate) gates: Vec, /// the Parameters on the wire at this column - pub(crate) parameters: HashMap>, + pub(crate) parameters: HashMap, } /// A T represents a Gate that can be pushed onto a Wire. The ``Gate`` struct @@ -117,12 +117,11 @@ impl Wire { // if texify_numerical_constants let param = if texify { // set the texified symbol - let symbol = Parameter::Symbol(Symbol::from_str(&text).unwrap_or(Symbol::Text(text))); - vec![symbol] + Parameter::Symbol(Symbol::from_str(&text).unwrap_or(Symbol::Text(text))) } else { // set the symbol as text - vec![Parameter::Symbol(Symbol::Text(text))] + Parameter::Symbol(Symbol::Text(text)) }; self.parameters.insert(self.gates.len(), param); @@ -182,15 +181,12 @@ impl fmt::Display for Wire { }); if name == "PHASE" { - if let Some(parameters) = self.parameters.get(&column) { - parameters.iter().for_each(|p| { - write!( - f, - "{}", - &RenderCommand::Phase(p.clone(), superscript.clone()) - ) - .ok(); - }); + if let Some(p) = self.parameters.get(&column) { + write!( + f, + "{}", + &RenderCommand::Phase(p.clone(), superscript.clone()) + )?; } } else if name == "X" { // if it is associated with dagger superscripts write it as an X gate with superscripts From 11191e08839aff4430a916dada22b4743e06b648 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 5 Apr 2023 16:17:51 -0700 Subject: [PATCH 103/118] Change Lstick to LeftWireLabel. --- src/program/latex/diagram/mod.rs | 2 +- src/program/latex/mod.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index b8fde253..0066de31 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -122,7 +122,7 @@ impl fmt::Display for Diagram { // are labels on in settings? if self.settings.label_qubit_lines { // write the label to the left side of wire - write!(f, "{}", RenderCommand::Lstick(*qubit))?; + write!(f, "{}", RenderCommand::LeftWireLabel(*qubit))?; } else { // write an empty column buffer as the first column write!(f, "{}", RenderCommand::Qw)?; diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index aac41751..1bde8d50 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -72,9 +72,9 @@ impl fmt::Display for Document { /// inside `backticks`. #[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash)] pub(crate) enum RenderCommand { - /// Make a qubit "stick out" from the left. + /// Set a fixed qubit as the label to the left of wire. #[display(fmt = "\\lstick{{\\ket{{q_{{{_0}}}}}}}")] - Lstick(u64), + LeftWireLabel(u64), /// Make a gate on the wire with an optional superscript. #[display(fmt = "\\gate{{{_0}{_1}}}")] Gate(String, String), @@ -432,7 +432,7 @@ mod tests { #[test] fn test_command_left_ket() { - insta::assert_snapshot!(RenderCommand::Lstick(0).to_string()); + insta::assert_snapshot!(RenderCommand::LeftWireLabel(0).to_string()); } #[test] From 05334d18f01ffa9970d7baf780064253655f9d5d Mon Sep 17 00:00:00 2001 From: Ryan Meneses <58452495+hiyaryan@users.noreply.github.com> Date: Fri, 7 Apr 2023 09:48:39 -0700 Subject: [PATCH 104/118] Update docs for Diagram circuit. Co-authored-by: Kalan <22137047+kalzoo@users.noreply.github.com> --- src/program/latex/diagram/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 0066de31..184f867f 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -20,7 +20,7 @@ pub(crate) mod wire; pub(super) struct Diagram { /// customizes how the diagram renders the circuit pub(crate) settings: RenderSettings, - /// a BTreeMap of wires with the name of the wire as the key + /// Wires (diagram rows) keyed by qubit index pub(crate) circuit: BTreeMap>, } From 94872498717e07132be4bb18b63dbe98b23a7bbb Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 7 Apr 2023 14:27:46 -0700 Subject: [PATCH 105/118] Apply review suggestions. --- src/program/latex/diagram/mod.rs | 46 +++++++++++++++++--------- src/program/latex/diagram/wire.rs | 55 +++++++++++-------------------- src/program/latex/mod.rs | 35 +++++++++++++++++++- src/program/latex/settings.rs | 16 ++------- 4 files changed, 86 insertions(+), 66 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 184f867f..9c4d7eb6 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -65,32 +65,44 @@ impl Diagram { } } - // get the names of the qubits in the circuit before circuit is borrowed as mutable - let circuit_qubits: Vec = self.circuit.keys().cloned().collect(); + // circuit needed immutably but also used mutably + let circuit = self.circuit.clone(); - // set gate for each qubit in the instruction + // only unwrap the last qubit once let last_qubit = gate.qubits.last().unwrap(); + + // set gate for each qubit in the instruction for (i, qubit) in gate.qubits.iter().enumerate() { if let Qubit::Fixed(instruction_qubit) = qubit { - if let Some(wire) = self.circuit.get_mut(instruction_qubit) { - // push the gate to the wire - wire.gates.push(wire::T::try_from(gate.clone())?); + let render_gate = wire::QuantikzCellType::try_from(gate.clone())?; - // update the last gate to a control gate if the instruction has more than one qubit + if let Some(wire) = self.circuit.get_mut(instruction_qubit) { + // set control Ctrl QuantikzCellType for multi-qubit gates if gate.qubits.len() > 1 { - if let wire::T::StdGate { + if let wire::QuantikzCellType::Gate { name, dagger_count: _, ctrl_count, - } = wire.gates.last().unwrap() + } = &render_gate { if i != gate.qubits.len() - 1 { - // reset the last gate to a control gate - wire.gates.pop(); - wire.set_ctrl(qubit, last_qubit, &circuit_qubits); - } else if i == gate.qubits.len() - 1 && ctrl_count - i > 1 { + // last qubit is the target + if let Qubit::Fixed(target) = last_qubit { + // get the distance between the instruction qubit and the target qubit + let distance = if instruction_qubit < target { + circuit.range(instruction_qubit..target).count() as i64 + } else { + -(circuit.range(target..instruction_qubit).count() as i64) + }; + + wire.set_ctrl(distance); + continue; + } + } else if gate.qubits.len() - ctrl_count > 1 { // there should be no more than one control modifier from the last qubit in a control and target instruction - return Err(LatexGenError::UnsupportedGate { gate: name.clone() }); + return Err(LatexGenError::UnsupportedGate { + gate: name.to_string(), + }); } } } @@ -102,6 +114,9 @@ impl Diagram { gate: gate.name.clone(), }); } + + // push the gate to the wire + wire.columns.push(render_gate); } } } @@ -132,8 +147,7 @@ impl fmt::Display for Diagram { write!(f, "{wire}")?; // chain an empty column to the end of the line - write!(f, " & ")?; - write!(f, "{}", &RenderCommand::Qw)?; + write!(f, "{}{}", &RenderCommand::Separate, &RenderCommand::Qw)?; // omit a new row if this is the last qubit wire if *qubit != *last { diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs index eb7add6e..fd72ae82 100644 --- a/src/program/latex/diagram/wire.rs +++ b/src/program/latex/diagram/wire.rs @@ -4,7 +4,7 @@ use lazy_regex::{Lazy, Regex}; use crate::{ expression::Expression, - instruction::{Gate, GateModifier, Qubit}, + instruction::{Gate, GateModifier}, }; use super::super::{LatexGenError, Parameter, RenderCommand, Symbol}; @@ -16,21 +16,21 @@ use super::super::{LatexGenError, Parameter, RenderCommand, Symbol}; /// n] matrix where m, is the total number of wires, or length, of the Circuit. #[derive(Clone, Debug, Default)] pub(crate) struct Wire { - /// the Gates on the wire callable by the column - pub(crate) gates: Vec, + /// the columns on the wire that can be serialized into LaTeX + pub(crate) columns: Vec, /// the Parameters on the wire at this column pub(crate) parameters: HashMap, } -/// A T represents a Gate that can be pushed onto a Wire. The ``Gate`` struct -/// in the ``instruction`` module is used to form this T variant which is -/// pushed onto the Wire and then serialized into LaTeX using the associated +/// RenderGate represents a Gate that can be pushed onto a Wire. The ``Gate`` +/// struct in the ``instruction`` module is used to form this T variant which +/// is pushed onto the Wire and then serialized into LaTeX using the associated /// ``Quantikz`` RenderCommand. #[derive(Clone, Debug, Default)] -pub(crate) enum T { +pub(crate) enum QuantikzCellType { #[default] Empty, - StdGate { + Gate { name: String, dagger_count: usize, ctrl_count: usize, @@ -38,7 +38,7 @@ pub(crate) enum T { Ctrl(i64), } -impl TryFrom for T { +impl TryFrom for QuantikzCellType { type Error = LatexGenError; /// Returns a StdGate that can be pushed onto the wire. This gate is first @@ -85,7 +85,7 @@ impl TryFrom for T { } // return the StdGate - Ok(T::StdGate { + Ok(QuantikzCellType::Gate { name: canonical_gate, dagger_count, ctrl_count, @@ -96,7 +96,7 @@ impl TryFrom for T { impl Wire { /// Set empty at the current column. pub(crate) fn set_empty(&mut self) { - self.gates.push(T::Empty); + self.columns.push(QuantikzCellType::Empty); } /// Retrieves a gate's parameters from Expression and matches them with its @@ -124,7 +124,7 @@ impl Wire { Parameter::Symbol(Symbol::Text(text)) }; - self.parameters.insert(self.gates.len(), param); + self.parameters.insert(self.columns.len(), param); } /// Set control qubit at the current column some distance from the target. @@ -135,22 +135,8 @@ impl Wire { /// `ctrl` - the control qubit /// `targ` - the target qubit /// `circuit_qubits` - the qubits in the circuit - pub(crate) fn set_ctrl(&mut self, ctrl: &Qubit, targ: &Qubit, circuit_qubits: &[u64]) { - if let Qubit::Fixed(ctrl) = ctrl { - if let Qubit::Fixed(targ) = targ { - // get the index of the control and target qubits - let ctrl_index = circuit_qubits.iter().position(|&x| x == *ctrl); - let targ_index = circuit_qubits.iter().position(|&x| x == *targ); - - // if the control and target qubits are found - if let Some(ctrl_index) = ctrl_index { - if let Some(targ_index) = targ_index { - self.gates - .push(T::Ctrl(targ_index as i64 - ctrl_index as i64)); - } - } - } - } + pub(crate) fn set_ctrl(&mut self, distance: i64) { + self.columns.push(QuantikzCellType::Ctrl(distance)); } } @@ -158,19 +144,18 @@ impl fmt::Display for Wire { /// Returns a result containing the LaTeX string for the wire fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // write the LaTeX string for each item at each column with a length the total number of instructions in the Wire plus one empty column - for column in 0..self.gates.len() { - write!(f, " & ")?; + for column in 0..self.columns.len() { + write!(f, "{}", &RenderCommand::Separate)?; // appended to the end of the gate name let mut superscript = String::new(); - match &self.gates[column] { - T::Empty => { + match &self.columns[column] { + QuantikzCellType::Empty => { // chain an empty column qw to the end of the line - // write!(f, " & ")?; write!(f, "{}", &RenderCommand::Qw)?; } - T::StdGate { + QuantikzCellType::Gate { name, dagger_count, ctrl_count, @@ -199,7 +184,7 @@ impl fmt::Display for Wire { write!(f, "{}", &RenderCommand::Gate(name.to_string(), superscript))?; } } - T::Ctrl(targ) => { + QuantikzCellType::Ctrl(targ) => { write!(f, "{}", &(RenderCommand::Ctrl(*targ)))?; } } diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 1bde8d50..2854d223 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -90,6 +90,9 @@ pub(crate) enum RenderCommand { /// Start a new row #[display(fmt = "\\\\")] Nr, + /// Separates columns on the same row. + #[display(fmt = " & ")] + Separate, /// Make a control qubit. #[display(fmt = "\\ctrl{{{_0}}}")] Ctrl(i64), @@ -179,7 +182,7 @@ impl Program { for qubit in &qubits { if let Qubit::Fixed(name) = qubit { let wire = Wire { - gates: Vec::with_capacity(instructions.len()), + columns: Vec::with_capacity(instructions.len()), ..Default::default() }; diagram.circuit.insert(*name, Box::new(wire)); @@ -374,6 +377,36 @@ mod tests { fn test_gate_controlled_xy() { get_latex("CONTROLLED XY 0 1 2", RenderSettings::default()); } + + #[test] + #[should_panic] + fn test_gate_swap() { + get_latex("SWAP 0 1", RenderSettings::default()); + } + + #[test] + #[should_panic] + fn test_gate_cswap() { + get_latex("CSWAP 0 1 2", RenderSettings::default()); + } + + #[test] + #[should_panic] + fn test_gate_controlled_swap() { + get_latex("CONTROLLED SWAP 0 1 2", RenderSettings::default()); + } + + #[test] + #[should_panic] + fn test_gate_iswap() { + get_latex("ISWAP 1 2", RenderSettings::default()); + } + + #[test] + #[should_panic] + fn test_gate_pswap() { + get_latex("PSWAP(gamma) 1 2", RenderSettings::default()); + } } /// Test module for modifiers diff --git a/src/program/latex/settings.rs b/src/program/latex/settings.rs index 88108387..51e424e6 100644 --- a/src/program/latex/settings.rs +++ b/src/program/latex/settings.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use super::diagram::wire::{Wire, T}; +use super::diagram::wire::{QuantikzCellType, Wire}; /// RenderSettings contains the metadata that allows the user to customize how /// the circuit is rendered or use the default implementation. @@ -12,12 +12,6 @@ pub struct RenderSettings { pub impute_missing_qubits: bool, /// Label qubit lines. pub label_qubit_lines: bool, - /// Write controlled rotations in compact form. - pub abbreviate_controlled_rotations: bool, - /// Extend the length of open wires at the right of the diagram. - pub qubit_line_open_wire_length: u32, - /// Align measurement operations to appear at the end of the diagram. - pub right_align_terminal_measurements: bool, } impl Default for RenderSettings { @@ -30,12 +24,6 @@ impl Default for RenderSettings { impute_missing_qubits: false, /// false: remove Lstick/Rstick from latex. label_qubit_lines: true, - /// true: `RX(pi)` displayed as `X_{\\pi}` instead of `R_X(\\pi)`. - abbreviate_controlled_rotations: false, - /// 0: condenses the size of subdiagrams. - qubit_line_open_wire_length: 1, - /// false: include Meter in the current column. - right_align_terminal_measurements: true, } } } @@ -86,7 +74,7 @@ impl RenderSettings { // insert empties based on total number of columns for _ in 0..last_column { - wire.gates.push(T::Empty) + wire.columns.push(QuantikzCellType::Empty) } Box::new(wire) From 436ac868dca24c24588f67ce5e89a8a0f48be43e Mon Sep 17 00:00:00 2001 From: marquessv Date: Fri, 7 Apr 2023 16:12:00 -0700 Subject: [PATCH 106/118] Add QuantikzGate struct, unnest apply_gate --- Cargo.lock | 13 +++++ Cargo.toml | 1 + src/instruction.rs | 3 +- src/program/latex/diagram/mod.rs | 89 +++++++++++++++---------------- src/program/latex/diagram/wire.rs | 42 +++++---------- src/program/latex/mod.rs | 4 ++ 6 files changed, 75 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73128f5c..10c60df2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,6 +262,18 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "enum-as-inner" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +dependencies = [ + "heck", + "proc-macro2 1.0.47", + "quote 1.0.21", + "syn 1.0.103", +] + [[package]] name = "fastrand" version = "1.8.0" @@ -807,6 +819,7 @@ dependencies = [ "criterion", "derive_more", "dot-writer", + "enum-as-inner", "indexmap", "insta", "lazy-regex", diff --git a/Cargo.toml b/Cargo.toml index babee73c..a90985c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ categories = ["parser-implementations", "science", "compilers", "emulators"] [dependencies] derive_more = { version = "0.99.17", optional = true } dot-writer = { version = "0.1.2", optional = true } +enum-as-inner = "0.5.1" indexmap = "1.6.1" lazy-regex = { version = "2.5.0", optional = true } lexical = "6.1.1" diff --git a/src/instruction.rs b/src/instruction.rs index e9171167..128a58b2 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use enum_as_inner::EnumAsInner; use nom_locate::LocatedSpan; use serde::{Deserialize, Serialize}; use std::borrow::Cow; @@ -1077,7 +1078,7 @@ mod test_instruction_display { } } -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, EnumAsInner)] pub enum Qubit { Fixed(u64), Variable(String), diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 9c4d7eb6..577299ef 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -68,57 +68,54 @@ impl Diagram { // circuit needed immutably but also used mutably let circuit = self.circuit.clone(); - // only unwrap the last qubit once - let last_qubit = gate.qubits.last().unwrap(); + // Quantum Gates operate on at least one qubit + let last_qubit = gate.qubits[gate.qubits.len() - 1] + .clone() + .into_fixed() + .map_err(|_| LatexGenError::UnsupportedQubit)?; // set gate for each qubit in the instruction - for (i, qubit) in gate.qubits.iter().enumerate() { - if let Qubit::Fixed(instruction_qubit) = qubit { - let render_gate = wire::QuantikzCellType::try_from(gate.clone())?; - - if let Some(wire) = self.circuit.get_mut(instruction_qubit) { - // set control Ctrl QuantikzCellType for multi-qubit gates - if gate.qubits.len() > 1 { - if let wire::QuantikzCellType::Gate { - name, - dagger_count: _, - ctrl_count, - } = &render_gate - { - if i != gate.qubits.len() - 1 { - // last qubit is the target - if let Qubit::Fixed(target) = last_qubit { - // get the distance between the instruction qubit and the target qubit - let distance = if instruction_qubit < target { - circuit.range(instruction_qubit..target).count() as i64 - } else { - -(circuit.range(target..instruction_qubit).count() as i64) - }; - - wire.set_ctrl(distance); - continue; - } - } else if gate.qubits.len() - ctrl_count > 1 { - // there should be no more than one control modifier from the last qubit in a control and target instruction - return Err(LatexGenError::UnsupportedGate { - gate: name.to_string(), - }); - } - } - } + for qubit in gate.qubits.iter() { + let instruction_qubit = qubit + .clone() + .into_fixed() + .map_err(|_| LatexGenError::UnsupportedQubit)?; + + let wire = self + .circuit + .get_mut(&instruction_qubit) + .ok_or(LatexGenError::QubitNotFound(instruction_qubit))?; + + let quantikz_gate = wire::QuantikzGate::try_from(gate.clone())?; + + if gate.qubits.len() - quantikz_gate.ctrl_count > 1 { + return Err(LatexGenError::UnsupportedGate { + gate: gate.name.to_string(), + }); + } - // parameterized non-PHASE gates are unsupported - if !wire.parameters.is_empty() && !gate.name.contains("PHASE") { - // parameterized single qubit gates are unsupported - return Err(LatexGenError::UnsupportedGate { - gate: gate.name.clone(), - }); - } + if quantikz_gate.ctrl_count > 0 && instruction_qubit != last_qubit { + // get the distance between the instruction qubit and the target qubit + let distance = if instruction_qubit < last_qubit { + circuit.range(instruction_qubit..last_qubit).count() as i64 + } else { + -(circuit.range(last_qubit..instruction_qubit).count() as i64) + }; + wire.columns.push(wire::QuantikzCellType::Ctrl(distance)); + continue; + } - // push the gate to the wire - wire.columns.push(render_gate); - } + // parameterized non-PHASE gates are unsupported + if !wire.parameters.is_empty() && !gate.name.contains("PHASE") { + // parameterized single qubit gates are unsupported + return Err(LatexGenError::UnsupportedGate { + gate: gate.name.clone(), + }); } + + // push the gate to the wire + wire.columns + .push(wire::QuantikzCellType::Gate(quantikz_gate)); } Ok(()) diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs index fd72ae82..d9887086 100644 --- a/src/program/latex/diagram/wire.rs +++ b/src/program/latex/diagram/wire.rs @@ -22,6 +22,13 @@ pub(crate) struct Wire { pub(crate) parameters: HashMap, } +#[derive(Clone, Debug, Default)] +pub(crate) struct QuantikzGate { + pub(crate) name: String, + pub(crate) dagger_count: usize, + pub(crate) ctrl_count: usize, +} + /// RenderGate represents a Gate that can be pushed onto a Wire. The ``Gate`` /// struct in the ``instruction`` module is used to form this T variant which /// is pushed onto the Wire and then serialized into LaTeX using the associated @@ -30,25 +37,13 @@ pub(crate) struct Wire { pub(crate) enum QuantikzCellType { #[default] Empty, - Gate { - name: String, - dagger_count: usize, - ctrl_count: usize, - }, + Gate(QuantikzGate), Ctrl(i64), } -impl TryFrom for QuantikzCellType { +impl TryFrom for QuantikzGate { type Error = LatexGenError; - /// Returns a StdGate that can be pushed onto the wire. This gate is first - /// decomposed into canonical form and may contain modifiers. The modifiers - /// are counted and applied to the gate. If the modifier is FORKED, then an - /// error returned. This is because FORKED is not supported by the - /// ``Quantikz`` library. - /// - /// # Arguments - /// `gate` - the Gate of the Instruction from `to_latex`. fn try_from(gate: Gate) -> Result { // if the gate is a composite gate, then apply the composite gate static ABBREVIATED_CONTROLLED_GATE: Lazy = @@ -84,8 +79,7 @@ impl TryFrom for QuantikzCellType { } } - // return the StdGate - Ok(QuantikzCellType::Gate { + Ok(QuantikzGate { name: canonical_gate, dagger_count, ctrl_count, @@ -126,18 +120,6 @@ impl Wire { self.parameters.insert(self.columns.len(), param); } - - /// Set control qubit at the current column some distance from the target. - /// The distance is determined by the relative position of the control and - /// target qubits in the circuit. - /// - /// # Arguments - /// `ctrl` - the control qubit - /// `targ` - the target qubit - /// `circuit_qubits` - the qubits in the circuit - pub(crate) fn set_ctrl(&mut self, distance: i64) { - self.columns.push(QuantikzCellType::Ctrl(distance)); - } } impl fmt::Display for Wire { @@ -155,11 +137,11 @@ impl fmt::Display for Wire { // chain an empty column qw to the end of the line write!(f, "{}", &RenderCommand::Qw)?; } - QuantikzCellType::Gate { + QuantikzCellType::Gate(QuantikzGate { name, dagger_count, ctrl_count, - } => { + }) => { (0..*dagger_count).for_each(|_| { superscript .push_str(&RenderCommand::Super(String::from("dagger")).to_string()); diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 2854d223..3f1bf27e 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -131,12 +131,16 @@ pub(crate) enum Symbol { pub enum LatexGenError { #[error("Found a target qubit with no control qubit.")] FoundTargetWithNoControl, + #[error("Circuit does not have qubit {0}")] + QubitNotFound(u64), #[error("The FORKED modifier is unsupported.")] UnsupportedModifierForked, #[error("This instruction is unsupported: {instruction}.")] UnsupportedInstruction { instruction: String }, #[error("This gate is unsupported: {gate}.")] UnsupportedGate { gate: String }, + #[error("Only fixed qubits are supported")] + UnsupportedQubit, } impl Program { From 41c25389fe6952dc0068e526a1eafc80e9f2ae54 Mon Sep 17 00:00:00 2001 From: marquessv Date: Fri, 7 Apr 2023 16:16:09 -0700 Subject: [PATCH 107/118] apply parameters inside of main apply_gate loop --- src/program/latex/diagram/mod.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 577299ef..13731556 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -54,17 +54,6 @@ impl Diagram { /// # Arguments /// `gate` - the Gate of the Instruction from `to_latex`. pub(crate) fn apply_gate(&mut self, gate: &Gate) -> Result<(), LatexGenError> { - // set the parameters for each qubit in the instruction - for qubit in &gate.qubits { - if let Qubit::Fixed(qubit) = qubit { - if let Some(wire) = self.circuit.get_mut(qubit) { - for expression in &gate.parameters { - wire.set_param(expression, self.settings.texify_numerical_constants); - } - } - } - } - // circuit needed immutably but also used mutably let circuit = self.circuit.clone(); @@ -86,6 +75,11 @@ impl Diagram { .get_mut(&instruction_qubit) .ok_or(LatexGenError::QubitNotFound(instruction_qubit))?; + // set the parameters for each qubit in the instruction + for expression in &gate.parameters { + wire.set_param(expression, self.settings.texify_numerical_constants); + } + let quantikz_gate = wire::QuantikzGate::try_from(gate.clone())?; if gate.qubits.len() - quantikz_gate.ctrl_count > 1 { From e835c61c75af4ce58b6f5ed5c57d08eaa97ff398 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 10 Apr 2023 22:47:39 -0700 Subject: [PATCH 108/118] Couple parameter with a specific cell in Column struct in a Wire of Columns. --- src/program/latex/diagram/mod.rs | 21 ++++--- src/program/latex/diagram/wire.rs | 94 +++++++++++++++++-------------- src/program/latex/mod.rs | 7 ++- src/program/latex/settings.rs | 4 +- 4 files changed, 72 insertions(+), 54 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 13731556..7868f087 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -5,8 +5,8 @@ use std::{ use crate::instruction::{Gate, Qubit}; -use self::wire::Wire; -use super::{LatexGenError, RenderCommand, RenderSettings}; +use self::wire::{Column, Wire}; +use super::{LatexGenError, Parameter, RenderCommand, RenderSettings}; pub(crate) mod wire; @@ -65,6 +65,8 @@ impl Diagram { // set gate for each qubit in the instruction for qubit in gate.qubits.iter() { + let mut column = Column::default(); + let instruction_qubit = qubit .clone() .into_fixed() @@ -77,7 +79,7 @@ impl Diagram { // set the parameters for each qubit in the instruction for expression in &gate.parameters { - wire.set_param(expression, self.settings.texify_numerical_constants); + column.set_param(expression, self.settings.texify_numerical_constants); } let quantikz_gate = wire::QuantikzGate::try_from(gate.clone())?; @@ -88,6 +90,7 @@ impl Diagram { }); } + // if the instruction qubit a control qubit if quantikz_gate.ctrl_count > 0 && instruction_qubit != last_qubit { // get the distance between the instruction qubit and the target qubit let distance = if instruction_qubit < last_qubit { @@ -95,12 +98,15 @@ impl Diagram { } else { -(circuit.range(last_qubit..instruction_qubit).count() as i64) }; - wire.columns.push(wire::QuantikzCellType::Ctrl(distance)); - continue; + column.cell = wire::QuantikzCellType::Ctrl(distance); + + // otherwise, the instruction qubit is the target qubit or a single qubit gate + } else { + column.cell = wire::QuantikzCellType::Gate(quantikz_gate); } // parameterized non-PHASE gates are unsupported - if !wire.parameters.is_empty() && !gate.name.contains("PHASE") { + if column.parameter != Parameter::None && !gate.name.contains("PHASE") { // parameterized single qubit gates are unsupported return Err(LatexGenError::UnsupportedGate { gate: gate.name.clone(), @@ -108,8 +114,7 @@ impl Diagram { } // push the gate to the wire - wire.columns - .push(wire::QuantikzCellType::Gate(quantikz_gate)); + wire.columns.push(column); } Ok(()) diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs index d9887086..365745b9 100644 --- a/src/program/latex/diagram/wire.rs +++ b/src/program/latex/diagram/wire.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, fmt, str::FromStr}; +use std::{fmt, str::FromStr}; use lazy_regex::{Lazy, Regex}; @@ -17,9 +17,47 @@ use super::super::{LatexGenError, Parameter, RenderCommand, Symbol}; #[derive(Clone, Debug, Default)] pub(crate) struct Wire { /// the columns on the wire that can be serialized into LaTeX - pub(crate) columns: Vec, - /// the Parameters on the wire at this column - pub(crate) parameters: HashMap, + pub(crate) columns: Vec, +} + +/// A single column on the wire containing Quantikz items and any associated +/// Parameters. +#[derive(Clone, Debug, Default)] +pub(crate) struct Column { + /// a column on the wire containing renderable items + pub(crate) cell: QuantikzCellType, + /// the Parameter on the wire at some column + pub(crate) parameter: Parameter, +} + +impl Column { + /// Retrieves a gate's parameters from Expression and matches them with its + /// symbolic definition which is then stored into wire at the specific + /// column. + /// + /// # Arguments + /// `expression` - expression from Program to get name of Parameter + /// `texify` - is texify_numerical_constants setting on? + pub(crate) fn set_param(&mut self, expression: &Expression, texify: bool) { + // get the name of the supported expression + let text = match expression { + Expression::Address(mr) => mr.name.to_string(), + Expression::Number(c) => c.re.to_string(), + expression => expression.to_string(), + }; + + // if texify_numerical_constants + let param = if texify { + // set the texified symbol + + Parameter::Symbol(Symbol::from_str(&text).unwrap_or(Symbol::Text(text))) + } else { + // set the symbol as text + Parameter::Symbol(Symbol::Text(text)) + }; + + self.parameter = param; + } } #[derive(Clone, Debug, Default)] @@ -90,35 +128,8 @@ impl TryFrom for QuantikzGate { impl Wire { /// Set empty at the current column. pub(crate) fn set_empty(&mut self) { - self.columns.push(QuantikzCellType::Empty); - } - - /// Retrieves a gate's parameters from Expression and matches them with its - /// symbolic definition which is then stored into wire at the specific - /// column. - /// - /// # Arguments - /// `expression` - expression from Program to get name of Parameter - /// `texify` - is texify_numerical_constants setting on? - pub(crate) fn set_param(&mut self, expression: &Expression, texify: bool) { - // get the name of the supported expression - let text = match expression { - Expression::Address(mr) => mr.name.to_string(), - Expression::Number(c) => c.re.to_string(), - expression => expression.to_string(), - }; - - // if texify_numerical_constants - let param = if texify { - // set the texified symbol - - Parameter::Symbol(Symbol::from_str(&text).unwrap_or(Symbol::Text(text))) - } else { - // set the symbol as text - Parameter::Symbol(Symbol::Text(text)) - }; - - self.parameters.insert(self.columns.len(), param); + // get the current column and set it to empty + self.columns.push(Column::default()) } } @@ -132,7 +143,7 @@ impl fmt::Display for Wire { // appended to the end of the gate name let mut superscript = String::new(); - match &self.columns[column] { + match &self.columns[column].cell { QuantikzCellType::Empty => { // chain an empty column qw to the end of the line write!(f, "{}", &RenderCommand::Qw)?; @@ -148,13 +159,14 @@ impl fmt::Display for Wire { }); if name == "PHASE" { - if let Some(p) = self.parameters.get(&column) { - write!( - f, - "{}", - &RenderCommand::Phase(p.clone(), superscript.clone()) - )?; - } + write!( + f, + "{}", + &RenderCommand::Phase( + self.columns[column].parameter.clone(), + superscript.clone() + ) + )?; } else if name == "X" { // if it is associated with dagger superscripts write it as an X gate with superscripts if dagger_count > &0 || ctrl_count == &0 { diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 3f1bf27e..02b7e1f9 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -102,8 +102,10 @@ pub(crate) enum RenderCommand { } /// Types of parameters passed to commands. -#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash, Default)] pub(crate) enum Parameter { + #[default] + None, /// Symbolic parameters #[display(fmt = "{_0}")] Symbol(Symbol), @@ -187,7 +189,6 @@ impl Program { if let Qubit::Fixed(name) = qubit { let wire = Wire { columns: Vec::with_capacity(instructions.len()), - ..Default::default() }; diagram.circuit.insert(*name, Box::new(wire)); } @@ -253,7 +254,7 @@ mod tests { /// Test functionality of to_latex using default settings. fn test_to_latex() { let latex = get_latex( - "DAGGER CPHASE(alpha) 0 1", + "H 0\nCNOT 0 1", RenderSettings { impute_missing_qubits: true, ..Default::default() diff --git a/src/program/latex/settings.rs b/src/program/latex/settings.rs index 51e424e6..76419c9a 100644 --- a/src/program/latex/settings.rs +++ b/src/program/latex/settings.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use super::diagram::wire::{QuantikzCellType, Wire}; +use super::diagram::wire::{Column, Wire}; /// RenderSettings contains the metadata that allows the user to customize how /// the circuit is rendered or use the default implementation. @@ -74,7 +74,7 @@ impl RenderSettings { // insert empties based on total number of columns for _ in 0..last_column { - wire.columns.push(QuantikzCellType::Empty) + wire.columns.push(Column::default()); } Box::new(wire) From 04c2abf5d1f64e4590f9647ec83b6af6eee81057 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 10 Apr 2023 23:38:00 -0700 Subject: [PATCH 109/118] Make enum-as-inner an optional dependency for latex feature. --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a90985c0..29a0a0cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ categories = ["parser-implementations", "science", "compilers", "emulators"] [dependencies] derive_more = { version = "0.99.17", optional = true } dot-writer = { version = "0.1.2", optional = true } -enum-as-inner = "0.5.1" +enum-as-inner = { version = "0.5.1", optional = true } indexmap = "1.6.1" lazy-regex = { version = "2.5.0", optional = true } lexical = "6.1.1" @@ -32,7 +32,7 @@ rstest = "0.15.0" [features] graphviz-dot = ["dot-writer"] -latex = ["derive_more", "lazy-regex"] +latex = ["derive_more", "lazy-regex", "enum-as-inner"] [profile.release] lto = true From b19f0aadb2fcd8a891841481911bdab310d6aa2f Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 11 Apr 2023 11:32:17 -0700 Subject: [PATCH 110/118] Use enum-as-inner to extract fixed qubits, reorganize code, update docs. --- src/program/latex/diagram/mod.rs | 13 +-- src/program/latex/diagram/wire.rs | 153 ++++++++++++++++-------------- src/program/latex/mod.rs | 12 +-- src/program/latex/settings.rs | 4 +- 4 files changed, 94 insertions(+), 88 deletions(-) diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 7868f087..03947923 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -5,7 +5,7 @@ use std::{ use crate::instruction::{Gate, Qubit}; -use self::wire::{Column, Wire}; +use self::wire::{QuantikzColumn, Wire}; use super::{LatexGenError, Parameter, RenderCommand, RenderSettings}; pub(crate) mod wire; @@ -32,15 +32,12 @@ impl Diagram { /// qubit wire of the circuit indicating a "do nothing" at that column. /// /// # Arguments - /// `qubits` - exposes the qubits used in the Program - /// `instruction` - exposes the qubits in a single Instruction + /// `qubits` - the qubits from the Quil program. + /// `gate` - the Gate of the Instruction from `to_latex`. pub(crate) fn apply_empty(&mut self, qubits: &HashSet, gate: &Gate) { qubits .difference(&gate.qubits.iter().cloned().collect()) - .filter_map(|q| match q { - Qubit::Fixed(index) => Some(index), - _ => None, - }) + .filter_map(|q| q.as_fixed()) .for_each(|index| { if let Some(wire) = self.circuit.get_mut(index) { wire.set_empty() @@ -65,7 +62,7 @@ impl Diagram { // set gate for each qubit in the instruction for qubit in gate.qubits.iter() { - let mut column = Column::default(); + let mut column = QuantikzColumn::default(); let instruction_qubit = qubit .clone() diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs index 365745b9..a770812b 100644 --- a/src/program/latex/diagram/wire.rs +++ b/src/program/latex/diagram/wire.rs @@ -9,92 +9,35 @@ use crate::{ use super::super::{LatexGenError, Parameter, RenderCommand, Symbol}; -/// A Wire represents a single qubit. This is a row vector, or [1 x n] matrix, -/// where n, is the total number of Quil instructions (or columns). Each column -/// on the wire maps to some item that can be serialized into LaTeX using the -/// ``Quantikz`` RenderCommands. A wire is part of the Circuit which is an [m x -/// n] matrix where m, is the total number of wires, or length, of the Circuit. -#[derive(Clone, Debug, Default)] -pub(crate) struct Wire { - /// the columns on the wire that can be serialized into LaTeX - pub(crate) columns: Vec, -} - -/// A single column on the wire containing Quantikz items and any associated -/// Parameters. -#[derive(Clone, Debug, Default)] -pub(crate) struct Column { - /// a column on the wire containing renderable items - pub(crate) cell: QuantikzCellType, - /// the Parameter on the wire at some column - pub(crate) parameter: Parameter, -} - -impl Column { - /// Retrieves a gate's parameters from Expression and matches them with its - /// symbolic definition which is then stored into wire at the specific - /// column. - /// - /// # Arguments - /// `expression` - expression from Program to get name of Parameter - /// `texify` - is texify_numerical_constants setting on? - pub(crate) fn set_param(&mut self, expression: &Expression, texify: bool) { - // get the name of the supported expression - let text = match expression { - Expression::Address(mr) => mr.name.to_string(), - Expression::Number(c) => c.re.to_string(), - expression => expression.to_string(), - }; - - // if texify_numerical_constants - let param = if texify { - // set the texified symbol - - Parameter::Symbol(Symbol::from_str(&text).unwrap_or(Symbol::Text(text))) - } else { - // set the symbol as text - Parameter::Symbol(Symbol::Text(text)) - }; - - self.parameter = param; - } -} - -#[derive(Clone, Debug, Default)] +/// The QuantikzGate is a valid cell of a QuantikzCellType that is the form of +/// the ``Gate`` struct in the ``Instruction`` containing the components that +/// are used to render a gate on the wire in the circuit diagram. +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub(crate) struct QuantikzGate { pub(crate) name: String, pub(crate) dagger_count: usize, pub(crate) ctrl_count: usize, } -/// RenderGate represents a Gate that can be pushed onto a Wire. The ``Gate`` -/// struct in the ``instruction`` module is used to form this T variant which -/// is pushed onto the Wire and then serialized into LaTeX using the associated -/// ``Quantikz`` RenderCommand. -#[derive(Clone, Debug, Default)] -pub(crate) enum QuantikzCellType { - #[default] - Empty, - Gate(QuantikzGate), - Ctrl(i64), -} - +/// Convert a ``Gate`` struct into a ``QuantikzGate`` struct. impl TryFrom for QuantikzGate { type Error = LatexGenError; fn try_from(gate: Gate) -> Result { - // if the gate is a composite gate, then apply the composite gate + // regex to match canonical controlled gates static ABBREVIATED_CONTROLLED_GATE: Lazy = Lazy::new(|| Regex::new("(?PC+)(?PPHASE|X|Y|Z|NOT)").unwrap()); let mut canonical_gate = gate.name.to_string(); let mut ctrl_count = 0; if let Some(captures) = ABBREVIATED_CONTROLLED_GATE.captures(&gate.name) { + // get the base gate and the number of controls let base = captures.name("base").unwrap().as_str(); ctrl_count = captures.name("count").unwrap().as_str().len(); - // convert abbreviated controlled gates to canonical form. If the base is NOT, then this is an X gate. + // set the canonical gate name match base { + // NOT is an alias for X "NOT" => canonical_gate = "X".to_string(), _ => canonical_gate = base.to_string(), } @@ -117,6 +60,7 @@ impl TryFrom for QuantikzGate { } } + // return the QuantikzGate form of Gate Ok(QuantikzGate { name: canonical_gate, dagger_count, @@ -125,27 +69,86 @@ impl TryFrom for QuantikzGate { } } +/// The type of cell that can be stored in a column on the wire. +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +pub(crate) enum QuantikzCellType { + #[default] + Empty, + Gate(QuantikzGate), + Ctrl(i64), +} + +/// A single column on the wire containing cells and associated parameters. +#[derive(Clone, Debug, Default)] +pub(crate) struct QuantikzColumn { + /// a column on the wire containing renderable items + pub(crate) cell: QuantikzCellType, + /// the Parameter on the wire at some column + pub(crate) parameter: Parameter, +} + +impl QuantikzColumn { + /// Retrieves a gate's parameters from Expression and matches them with its + /// symbolic definition which is then stored into wire at the specific + /// column. + /// + /// # Arguments + /// `expression` - expression from Program to get name of Parameter + /// `texify` - is texify_numerical_constants setting on? + pub(crate) fn set_param(&mut self, expression: &Expression, texify: bool) { + // get the name of the supported expression + let text = match expression { + Expression::Address(mr) => mr.name.to_string(), + Expression::Number(c) => c.re.to_string(), + expression => expression.to_string(), + }; + + // if texify_numerical_constants + let param = if texify { + // set the texified symbol + Parameter::Symbol(Symbol::from_str(&text).unwrap_or(Symbol::Text(text))) + } else { + // set the symbol as text + Parameter::Symbol(Symbol::Text(text)) + }; + + self.parameter = param; + } +} + +/// A Wire represents a single qubit. This is a row vector, or [1 x n] matrix, +/// where n, is the total number of Quil instructions (or columns). Each column +/// on the wire maps to some item that can be serialized into LaTeX using the +/// ``Quantikz`` RenderCommands. A wire is part of the Circuit which is an [m x +/// n] matrix where m, is the total number of wires, or length, of the Circuit. +#[derive(Clone, Debug, Default)] +pub(crate) struct Wire { + /// the columns on the wire that can be serialized into LaTeX + pub(crate) columns: Vec, +} + impl Wire { /// Set empty at the current column. pub(crate) fn set_empty(&mut self) { // get the current column and set it to empty - self.columns.push(Column::default()) + self.columns.push(QuantikzColumn::default()) } } impl fmt::Display for Wire { /// Returns a result containing the LaTeX string for the wire fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // write the LaTeX string for each item at each column with a length the total number of instructions in the Wire plus one empty column + // write the LaTeX string for each cell in the column for column in 0..self.columns.len() { write!(f, "{}", &RenderCommand::Separate)?; // appended to the end of the gate name let mut superscript = String::new(); + // match the cell type match &self.columns[column].cell { QuantikzCellType::Empty => { - // chain an empty column qw to the end of the line + // write an empty column write!(f, "{}", &RenderCommand::Qw)?; } QuantikzCellType::Gate(QuantikzGate { @@ -153,11 +156,13 @@ impl fmt::Display for Wire { dagger_count, ctrl_count, }) => { + // build the dagger superscript (0..*dagger_count).for_each(|_| { superscript .push_str(&RenderCommand::Super(String::from("dagger")).to_string()); }); + // write a phase gate with its rotation parameter if name == "PHASE" { write!( f, @@ -167,18 +172,22 @@ impl fmt::Display for Wire { superscript.clone() ) )?; - } else if name == "X" { - // if it is associated with dagger superscripts write it as an X gate with superscripts - if dagger_count > &0 || ctrl_count == &0 { + // the conditional defines a target gate + } else if name == "X" && ctrl_count > &0 { + // if there a daggers then write it as an X gate + if dagger_count > &0 { write!(f, "{}", &RenderCommand::Gate(name.to_string(), superscript))?; + // otherwise write it as a closed dot } else { write!(f, "{}", &RenderCommand::Targ)?; } + // write a gate with its name and superscript } else { write!(f, "{}", &RenderCommand::Gate(name.to_string(), superscript))?; } } QuantikzCellType::Ctrl(targ) => { + // write a control gate pointing to its target write!(f, "{}", &(RenderCommand::Ctrl(*targ)))?; } } diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 02b7e1f9..fbeb2e81 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -186,12 +186,12 @@ impl Program { // initialize circuit with empty wires of all qubits in program let qubits = Program::get_used_qubits(self); for qubit in &qubits { - if let Qubit::Fixed(name) = qubit { - let wire = Wire { - columns: Vec::with_capacity(instructions.len()), - }; - diagram.circuit.insert(*name, Box::new(wire)); - } + let wire = Wire { + columns: Vec::with_capacity(instructions.len()), + }; + diagram + .circuit + .insert(*qubit.as_fixed().unwrap(), Box::new(wire)); } // are implicit qubits required in settings and are there at least two or more qubits in the diagram? diff --git a/src/program/latex/settings.rs b/src/program/latex/settings.rs index 76419c9a..0e2e9dd1 100644 --- a/src/program/latex/settings.rs +++ b/src/program/latex/settings.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use super::diagram::wire::{Column, Wire}; +use super::diagram::wire::{QuantikzColumn, Wire}; /// RenderSettings contains the metadata that allows the user to customize how /// the circuit is rendered or use the default implementation. @@ -74,7 +74,7 @@ impl RenderSettings { // insert empties based on total number of columns for _ in 0..last_column { - wire.columns.push(Column::default()); + wire.columns.push(QuantikzColumn::default()); } Box::new(wire) From 9e6d4bc665298c9b375e2ceb41806576d5cf7a92 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 11 Apr 2023 16:38:06 -0700 Subject: [PATCH 111/118] Revert "Make enum-as-inner an optional dependency for latex feature." This reverts commit 04c2abf5d1f64e4590f9647ec83b6af6eee81057. --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 29a0a0cb..a90985c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ categories = ["parser-implementations", "science", "compilers", "emulators"] [dependencies] derive_more = { version = "0.99.17", optional = true } dot-writer = { version = "0.1.2", optional = true } -enum-as-inner = { version = "0.5.1", optional = true } +enum-as-inner = "0.5.1" indexmap = "1.6.1" lazy-regex = { version = "2.5.0", optional = true } lexical = "6.1.1" @@ -32,7 +32,7 @@ rstest = "0.15.0" [features] graphviz-dot = ["dot-writer"] -latex = ["derive_more", "lazy-regex", "enum-as-inner"] +latex = ["derive_more", "lazy-regex"] [profile.release] lto = true From 3aacde218e109d7785d7997a82931ce1bf4c6b2d Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Tue, 11 Apr 2023 17:19:03 -0700 Subject: [PATCH 112/118] Remove wordy QuantikzGate doc, refactor if-let for instruction to match. --- src/program/latex/diagram/wire.rs | 3 -- src/program/latex/mod.rs | 47 +++++++++++++++++-------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs index a770812b..a045e699 100644 --- a/src/program/latex/diagram/wire.rs +++ b/src/program/latex/diagram/wire.rs @@ -9,9 +9,6 @@ use crate::{ use super::super::{LatexGenError, Parameter, RenderCommand, Symbol}; -/// The QuantikzGate is a valid cell of a QuantikzCellType that is the form of -/// the ``Gate`` struct in the ``Instruction`` containing the components that -/// are used to render a gate on the wire in the circuit diagram. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub(crate) struct QuantikzGate { pub(crate) name: String, diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index fbeb2e81..4531011e 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -200,30 +200,35 @@ impl Program { RenderSettings::impute_missing_qubits(instructions.len(), &mut diagram.circuit); } + // build a circuit from the instructions for instruction in instructions { - // parse gate instructions into a new circuit - if let Instruction::Gate(gate) = instruction { - // if there are any duplicate qubits in the gate return an error - if gate.qubits.len() - != gate - .qubits - .iter() - .cloned() - .collect::>() - .len() - { - return Err(LatexGenError::FoundTargetWithNoControl); + match instruction { + // build the circuit from a gate instruction + Instruction::Gate(gate) => { + // if there are duplicate qubits in the gate return an error + if gate.qubits.len() + != gate + .qubits + .iter() + .cloned() + .collect::>() + .len() + { + return Err(LatexGenError::FoundTargetWithNoControl); + } + + diagram.apply_gate(&gate)?; + diagram.apply_empty(&qubits, &gate); } - - diagram.apply_gate(&gate)?; - diagram.apply_empty(&qubits, &gate); - } else if let Instruction::GateDefinition(_) = instruction { // GateDefinition is supported but inserted into the circuit using its Gate instruction form - continue; - } else { - return Err(LatexGenError::UnsupportedInstruction { - instruction: instruction.to_string(), - }); + Instruction::GateDefinition(_) => continue, + + // all other instructions are not supported + _ => { + return Err(LatexGenError::UnsupportedInstruction { + instruction: instruction.to_string(), + }) + } } } From ea8cd051732ef491e06376b21902914fb0accb1e Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 12 Apr 2023 17:04:53 -0700 Subject: [PATCH 113/118] Refactor using new data model WIP. --- src/program/latex/diagram/cell.rs | 200 ++++++++++++++++++++++++++ src/program/latex/diagram/mod.rs | 232 +++++++++++++++++------------- src/program/latex/diagram/wire.rs | 195 ------------------------- src/program/latex/mod.rs | 110 ++++++++------ src/program/latex/settings.rs | 84 ----------- 5 files changed, 398 insertions(+), 423 deletions(-) create mode 100644 src/program/latex/diagram/cell.rs delete mode 100644 src/program/latex/diagram/wire.rs delete mode 100644 src/program/latex/settings.rs diff --git a/src/program/latex/diagram/cell.rs b/src/program/latex/diagram/cell.rs new file mode 100644 index 00000000..eae5a8de --- /dev/null +++ b/src/program/latex/diagram/cell.rs @@ -0,0 +1,200 @@ +use std::fmt; + +use lazy_regex::{Lazy, Regex}; + +use crate::instruction::{Gate, GateModifier}; + +use super::super::{LatexGenError, RenderCommand, Symbol}; + +// Should this be enum? Before it was `struct` +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +pub(crate) enum QuantikzCell { + Gate(QuantikzGate), + Control(i64), + #[default] + Empty, +} + +pub(crate) struct CellSettings { + pub(crate) texify: bool, +} + +impl QuantikzCell { + pub(crate) fn to_latex(&self, settings: &CellSettings) -> Result { + // match the cell type + match self { + QuantikzCell::Empty => { + // write an empty column + Ok(format!("{}", &RenderCommand::Qw)) + } + QuantikzCell::Control(target) => { + // write a control gate pointing to its target + Ok(format!("{}", &(RenderCommand::Ctrl(*target)))) + } + QuantikzCell::Gate(QuantikzGate { + name, + parameter, + dagger_count, + ctrl_count, + }) => { + // build the dagger superscript + // appended to the end of the gate name + let mut superscript = String::new(); + (0..*dagger_count).for_each(|_| { + superscript.push_str(&RenderCommand::Super(String::from("dagger")).to_string()); + }); + + let symbol = parameter + .clone() + .map(|p| if settings.texify { p.texify() } else { p }); + + // write a phase gate with its rotation parameter + if name == "PHASE" { + Ok(format!( + "{}", + &RenderCommand::Phase(symbol.unwrap(), superscript) + )) + // the conditional defines a target gate + } else if name == "X" && ctrl_count > &0 { + // if there a daggers then write it as an X gate + if dagger_count > &0 { + Ok(format!( + "{}", + &RenderCommand::Gate(name.to_string(), superscript) + )) + // otherwise write it as a closed dot + } else { + Ok(format!("{}", &RenderCommand::Targ)) + } + // write a gate with its name and superscript + } else { + Ok(format!( + "{}", + &RenderCommand::Gate(name.to_string(), superscript) + )) + } + } + } + } +} + +impl fmt::Display for QuantikzCell { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // match the cell type + match self { + QuantikzCell::Empty => { + // write an empty column + write!(f, "{}", &RenderCommand::Qw)?; + } + QuantikzCell::Control(target) => { + // write a control gate pointing to its target + write!(f, "{}", &(RenderCommand::Ctrl(*target)))?; + } + QuantikzCell::Gate(QuantikzGate { + name, + parameter, + dagger_count, + ctrl_count, + }) => { + // build the dagger superscript + // appended to the end of the gate name + let mut superscript = String::new(); + (0..*dagger_count).for_each(|_| { + superscript.push_str(&RenderCommand::Super(String::from("dagger")).to_string()); + }); + + // write a phase gate with its rotation parameter + if name == "PHASE" { + write!( + f, + "{}", + &RenderCommand::Phase(parameter.as_ref().unwrap().clone(), superscript) + )?; + // the conditional defines a target gate + } else if name == "X" && ctrl_count > &0 { + // if there a daggers then write it as an X gate + if dagger_count > &0 { + write!(f, "{}", &RenderCommand::Gate(name.to_string(), superscript))?; + // otherwise write it as a closed dot + } else { + write!(f, "{}", &RenderCommand::Targ)?; + } + // write a gate with its name and superscript + } else { + write!(f, "{}", &RenderCommand::Gate(name.to_string(), superscript))?; + } + } + } + + Ok(()) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +pub(crate) struct QuantikzGate { + pub(crate) name: String, + pub(crate) parameter: Option, + pub(crate) dagger_count: usize, + pub(crate) ctrl_count: usize, +} + +/// Convert a ``Gate`` struct into a ``QuantikzGate`` struct. +impl TryFrom for QuantikzGate { + type Error = LatexGenError; + + fn try_from(gate: Gate) -> Result { + // regex to match canonical controlled gates + static ABBREVIATED_CONTROLLED_GATE: Lazy = + Lazy::new(|| Regex::new("(?PC+)(?PPHASE|X|Y|Z|NOT)").unwrap()); + + let mut canonical_gate = gate.name.to_string(); + let mut ctrl_count = 0; + if let Some(captures) = ABBREVIATED_CONTROLLED_GATE.captures(&gate.name) { + // get the base gate and the number of controls + let base = captures.name("base").unwrap().as_str(); + ctrl_count = captures.name("count").unwrap().as_str().len(); + + // set the canonical gate name + match base { + // NOT is an alias for X + "NOT" => canonical_gate = "X".to_string(), + _ => canonical_gate = base.to_string(), + } + } + + // count the supported modifiers + let mut dagger_count = 0; + for modifier in gate.modifiers { + match modifier { + // return error for unsupported modifier FORKED + GateModifier::Forked => { + return Err(LatexGenError::UnsupportedModifierForked); + } + GateModifier::Dagger => { + dagger_count += 1; + } + GateModifier::Controlled => { + ctrl_count += 1; + } + } + } + + if gate.parameters.len() > 1 { + // TODO: Create separate error for unsupported parameter length? + return Err(LatexGenError::UnsupportedGate { gate: gate.name }); + } + + let mut parameter = None; + if !gate.parameters.is_empty() { + parameter = Some(Symbol::from_expression(gate.parameters[0].clone())) + } + + // return the QuantikzGate form of Gate + Ok(QuantikzGate { + name: canonical_gate, + parameter, + dagger_count, + ctrl_count, + }) + } +} diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 03947923..dd303333 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -1,14 +1,11 @@ -use std::{ - collections::{BTreeMap, HashSet}, - fmt, -}; +use std::{collections::BTreeMap, fmt}; -use crate::instruction::{Gate, Qubit}; +use crate::instruction::Gate; -use self::wire::{QuantikzColumn, Wire}; -use super::{LatexGenError, Parameter, RenderCommand, RenderSettings}; +use self::cell::{CellSettings, QuantikzCell}; +use super::{LatexGenError, RenderCommand, RenderSettings}; -pub(crate) mod wire; +pub(crate) mod cell; /// A Diagram represents a collection of wires in a Circuit. The size of the /// Diagram can be measured by multiplying the number of Instructions in a @@ -16,33 +13,81 @@ pub(crate) mod wire; /// is the number of Quil instructions (or columns), and m, is the number of /// wires (or rows). Each individual element of the matrix represents an item /// that is serializable into LaTeX using the ``Quantikz`` RenderCommands. -#[derive(Clone, Debug, Default)] -pub(super) struct Diagram { - /// customizes how the diagram renders the circuit - pub(crate) settings: RenderSettings, - /// Wires (diagram rows) keyed by qubit index - pub(crate) circuit: BTreeMap>, +pub(crate) struct Diagram { + settings: RenderSettings, + wires: BTreeMap, // qubit index -> column index + columns: Vec, +} + +struct DiagramColumn { + cells: Vec, // number of wires } impl Diagram { - /// Compares qubits from a single instruction associated with a column on - /// the circuit to all of the qubits used in the quil program. If a qubit - /// from the quil program is not found in the qubits in the single - /// instruction line, then an empty slot is added to that column on the - /// qubit wire of the circuit indicating a "do nothing" at that column. + pub(crate) fn new(qubits: Vec, settings: RenderSettings) -> Result { + // Initialize a new `Diagram` with the given qubits + // If settings.impute_missing qubits, then pad the gaps. + + let mut diagram = Self { + settings, + wires: BTreeMap::new(), + columns: Vec::new(), + }; + + diagram.build_wire_map(qubits)?; + + Ok(diagram) + } + + /// Adds missing qubits between the first qubit and last qubit in a + /// diagram's circuit. If a missing qubit is found, a new wire is created + /// and pushed to the diagram's circuit. /// - /// # Arguments - /// `qubits` - the qubits from the Quil program. - /// `gate` - the Gate of the Instruction from `to_latex`. - pub(crate) fn apply_empty(&mut self, qubits: &HashSet, gate: &Gate) { - qubits - .difference(&gate.qubits.iter().cloned().collect()) - .filter_map(|q| q.as_fixed()) - .for_each(|index| { - if let Some(wire) = self.circuit.get_mut(index) { - wire.set_empty() - } - }); + /// # Arguments + /// `last_column` - total number of instructions from Program + /// `circuit` - the circuit of the diagram + /// + /// # Examples + /// ``` + /// use quil_rs::{Program, program::latex::settings::RenderSettings}; + /// use std::str::FromStr; + /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); + /// let settings = RenderSettings { + /// impute_missing_qubits: true, + /// ..Default::default() + /// }; + /// program.to_latex(settings).expect(""); + /// ``` + pub(crate) fn build_wire_map(&mut self, mut qubits: Vec) -> Result<(), LatexGenError> { + // sort qubits so that wire mapping contains the correct row number for each qubit + // program.get_qubits() => [3, 5, 1] + // qubits.sort() => [1, 3, 5] + // column_indices = [0, 1, 2] + // expected wires = { + // 3: 1 + // 5: 2 + // 1: 0 + // } + + // TODO: Return error + if qubits.is_empty() { + return Err(LatexGenError::RequiresQubits); + } + + qubits.sort(); + if self.settings.impute_missing_qubits { + for (i, qubit) in + (qubits[0]..*qubits.last().expect("qubits is not empty") + 1).enumerate() + { + self.wires.insert(qubit, i); + } + } else { + for (i, qubit) in qubits.iter().enumerate() { + self.wires.insert(*qubit, i); + } + } + + Ok(()) } /// Applies a gate from an instruction to the associated wires in the @@ -50,70 +95,53 @@ impl Diagram { /// /// # Arguments /// `gate` - the Gate of the Instruction from `to_latex`. - pub(crate) fn apply_gate(&mut self, gate: &Gate) -> Result<(), LatexGenError> { - // circuit needed immutably but also used mutably - let circuit = self.circuit.clone(); - - // Quantum Gates operate on at least one qubit - let last_qubit = gate.qubits[gate.qubits.len() - 1] - .clone() - .into_fixed() - .map_err(|_| LatexGenError::UnsupportedQubit)?; - - // set gate for each qubit in the instruction - for qubit in gate.qubits.iter() { - let mut column = QuantikzColumn::default(); - - let instruction_qubit = qubit - .clone() - .into_fixed() - .map_err(|_| LatexGenError::UnsupportedQubit)?; - - let wire = self - .circuit - .get_mut(&instruction_qubit) - .ok_or(LatexGenError::QubitNotFound(instruction_qubit))?; - - // set the parameters for each qubit in the instruction - for expression in &gate.parameters { - column.set_param(expression, self.settings.texify_numerical_constants); - } - - let quantikz_gate = wire::QuantikzGate::try_from(gate.clone())?; + pub(crate) fn add_gate(&mut self, gate: &Gate) -> Result<(), LatexGenError> { + // Initialize a column full of empty cells to be filled in. + let mut cells: Vec = vec![QuantikzCell::Empty; self.wires.len()]; - if gate.qubits.len() - quantikz_gate.ctrl_count > 1 { - return Err(LatexGenError::UnsupportedGate { - gate: gate.name.to_string(), - }); - } + let quantikz_gate = cell::QuantikzGate::try_from(gate.clone())?; - // if the instruction qubit a control qubit - if quantikz_gate.ctrl_count > 0 && instruction_qubit != last_qubit { - // get the distance between the instruction qubit and the target qubit - let distance = if instruction_qubit < last_qubit { - circuit.range(instruction_qubit..last_qubit).count() as i64 - } else { - -(circuit.range(last_qubit..instruction_qubit).count() as i64) - }; - column.cell = wire::QuantikzCellType::Ctrl(distance); - - // otherwise, the instruction qubit is the target qubit or a single qubit gate - } else { - column.cell = wire::QuantikzCellType::Gate(quantikz_gate); - } + if gate.qubits.len() - quantikz_gate.ctrl_count > 1 { + return Err(LatexGenError::UnsupportedGate { + gate: gate.name.to_string(), + }); + } - // parameterized non-PHASE gates are unsupported - if column.parameter != Parameter::None && !gate.name.contains("PHASE") { - // parameterized single qubit gates are unsupported - return Err(LatexGenError::UnsupportedGate { - gate: gate.name.clone(), - }); - } + // parameterized non-PHASE gates are unsupported + if quantikz_gate.parameter.is_some() && !gate.name.contains("PHASE") { + // parameterized single qubit gates are unsupported + return Err(LatexGenError::UnsupportedGate { + gate: gate.name.clone(), + }); + } - // push the gate to the wire - wire.columns.push(column); + // TODO: Error handling instead of unwrap + let mut qubit_iterator = gate + .qubits + .iter() + .map(|q| q.clone().into_fixed().unwrap()) + .rev(); + + // start iterator at target qubit + let target_qubit = qubit_iterator.next().unwrap(); + + // TODO: Return error instead of unwrap + let target_index = *self.wires.get(&target_qubit).unwrap(); + + cells[target_index] = QuantikzCell::Gate(quantikz_gate); + + // This is oversimplified for the example. There will be a little + // bit of pre-processing necessary to get your "canonical gate", + // and it probably makes sense to go in reverse order so you can start + // with the target qubit. + for qubit in qubit_iterator { + // TODO: Error handling instead of unwrap + let column_index = *self.wires.get(&qubit).unwrap(); + cells[column_index] = QuantikzCell::Control(target_index as i64 - column_index as i64) } + self.columns.push(DiagramColumn { cells }); + Ok(()) } } @@ -121,12 +149,13 @@ impl Diagram { impl fmt::Display for Diagram { /// Returns a result containing the body of the Document. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // write a newline between the body and the Document header - writeln!(f)?; - // write the LaTeX string for each wire in the circuit - let last = self.circuit.keys().last().unwrap_or(&0); - for (qubit, wire) in &self.circuit { + let last = self.wires.keys().last().unwrap_or(&0); + let cell_settings = CellSettings { + texify: self.settings.texify_numerical_constants, + }; + + for (qubit, cell_index) in &self.wires { // are labels on in settings? if self.settings.label_qubit_lines { // write the label to the left side of wire @@ -136,17 +165,22 @@ impl fmt::Display for Diagram { write!(f, "{}", RenderCommand::Qw)?; } - // write the LaTeX string for the wire - write!(f, "{wire}")?; + for column in &self.columns { + let command = &column.cells[*cell_index]; + write!( + f, + "{}{}", + &RenderCommand::Separate, + command.to_latex(&cell_settings).map_err(|_| fmt::Error)? + )?; + } - // chain an empty column to the end of the line write!(f, "{}{}", &RenderCommand::Separate, &RenderCommand::Qw)?; // omit a new row if this is the last qubit wire if *qubit != *last { // otherwise, write a new row to the end of the line - write!(f, " ")?; - write!(f, "{}", &RenderCommand::Nr)?; + write!(f, " {}", &RenderCommand::Nr)?; } // write a newline between each row and or the body and the document footer diff --git a/src/program/latex/diagram/wire.rs b/src/program/latex/diagram/wire.rs deleted file mode 100644 index a045e699..00000000 --- a/src/program/latex/diagram/wire.rs +++ /dev/null @@ -1,195 +0,0 @@ -use std::{fmt, str::FromStr}; - -use lazy_regex::{Lazy, Regex}; - -use crate::{ - expression::Expression, - instruction::{Gate, GateModifier}, -}; - -use super::super::{LatexGenError, Parameter, RenderCommand, Symbol}; - -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -pub(crate) struct QuantikzGate { - pub(crate) name: String, - pub(crate) dagger_count: usize, - pub(crate) ctrl_count: usize, -} - -/// Convert a ``Gate`` struct into a ``QuantikzGate`` struct. -impl TryFrom for QuantikzGate { - type Error = LatexGenError; - - fn try_from(gate: Gate) -> Result { - // regex to match canonical controlled gates - static ABBREVIATED_CONTROLLED_GATE: Lazy = - Lazy::new(|| Regex::new("(?PC+)(?PPHASE|X|Y|Z|NOT)").unwrap()); - - let mut canonical_gate = gate.name.to_string(); - let mut ctrl_count = 0; - if let Some(captures) = ABBREVIATED_CONTROLLED_GATE.captures(&gate.name) { - // get the base gate and the number of controls - let base = captures.name("base").unwrap().as_str(); - ctrl_count = captures.name("count").unwrap().as_str().len(); - - // set the canonical gate name - match base { - // NOT is an alias for X - "NOT" => canonical_gate = "X".to_string(), - _ => canonical_gate = base.to_string(), - } - } - - // count the supported modifiers - let mut dagger_count = 0; - for modifier in gate.modifiers { - match modifier { - // return error for unsupported modifier FORKED - GateModifier::Forked => { - return Err(LatexGenError::UnsupportedModifierForked); - } - GateModifier::Dagger => { - dagger_count += 1; - } - GateModifier::Controlled => { - ctrl_count += 1; - } - } - } - - // return the QuantikzGate form of Gate - Ok(QuantikzGate { - name: canonical_gate, - dagger_count, - ctrl_count, - }) - } -} - -/// The type of cell that can be stored in a column on the wire. -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -pub(crate) enum QuantikzCellType { - #[default] - Empty, - Gate(QuantikzGate), - Ctrl(i64), -} - -/// A single column on the wire containing cells and associated parameters. -#[derive(Clone, Debug, Default)] -pub(crate) struct QuantikzColumn { - /// a column on the wire containing renderable items - pub(crate) cell: QuantikzCellType, - /// the Parameter on the wire at some column - pub(crate) parameter: Parameter, -} - -impl QuantikzColumn { - /// Retrieves a gate's parameters from Expression and matches them with its - /// symbolic definition which is then stored into wire at the specific - /// column. - /// - /// # Arguments - /// `expression` - expression from Program to get name of Parameter - /// `texify` - is texify_numerical_constants setting on? - pub(crate) fn set_param(&mut self, expression: &Expression, texify: bool) { - // get the name of the supported expression - let text = match expression { - Expression::Address(mr) => mr.name.to_string(), - Expression::Number(c) => c.re.to_string(), - expression => expression.to_string(), - }; - - // if texify_numerical_constants - let param = if texify { - // set the texified symbol - Parameter::Symbol(Symbol::from_str(&text).unwrap_or(Symbol::Text(text))) - } else { - // set the symbol as text - Parameter::Symbol(Symbol::Text(text)) - }; - - self.parameter = param; - } -} - -/// A Wire represents a single qubit. This is a row vector, or [1 x n] matrix, -/// where n, is the total number of Quil instructions (or columns). Each column -/// on the wire maps to some item that can be serialized into LaTeX using the -/// ``Quantikz`` RenderCommands. A wire is part of the Circuit which is an [m x -/// n] matrix where m, is the total number of wires, or length, of the Circuit. -#[derive(Clone, Debug, Default)] -pub(crate) struct Wire { - /// the columns on the wire that can be serialized into LaTeX - pub(crate) columns: Vec, -} - -impl Wire { - /// Set empty at the current column. - pub(crate) fn set_empty(&mut self) { - // get the current column and set it to empty - self.columns.push(QuantikzColumn::default()) - } -} - -impl fmt::Display for Wire { - /// Returns a result containing the LaTeX string for the wire - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // write the LaTeX string for each cell in the column - for column in 0..self.columns.len() { - write!(f, "{}", &RenderCommand::Separate)?; - - // appended to the end of the gate name - let mut superscript = String::new(); - - // match the cell type - match &self.columns[column].cell { - QuantikzCellType::Empty => { - // write an empty column - write!(f, "{}", &RenderCommand::Qw)?; - } - QuantikzCellType::Gate(QuantikzGate { - name, - dagger_count, - ctrl_count, - }) => { - // build the dagger superscript - (0..*dagger_count).for_each(|_| { - superscript - .push_str(&RenderCommand::Super(String::from("dagger")).to_string()); - }); - - // write a phase gate with its rotation parameter - if name == "PHASE" { - write!( - f, - "{}", - &RenderCommand::Phase( - self.columns[column].parameter.clone(), - superscript.clone() - ) - )?; - // the conditional defines a target gate - } else if name == "X" && ctrl_count > &0 { - // if there a daggers then write it as an X gate - if dagger_count > &0 { - write!(f, "{}", &RenderCommand::Gate(name.to_string(), superscript))?; - // otherwise write it as a closed dot - } else { - write!(f, "{}", &RenderCommand::Targ)?; - } - // write a gate with its name and superscript - } else { - write!(f, "{}", &RenderCommand::Gate(name.to_string(), superscript))?; - } - } - QuantikzCellType::Ctrl(targ) => { - // write a control gate pointing to its target - write!(f, "{}", &(RenderCommand::Ctrl(*targ)))?; - } - } - } - - Ok(()) - } -} diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index 4531011e..f5ad459d 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -23,16 +23,14 @@ mod diagram; +use std::str::FromStr; use std::{collections::HashSet, fmt}; +use crate::expression::Expression; use crate::instruction::{Instruction, Qubit}; use crate::Program; -use self::settings::RenderSettings; -use diagram::{wire::Wire, Diagram}; - -pub mod settings; - +use diagram::Diagram; /// The structure of a LaTeX document. Typically a LaTeX document contains /// metadata defining the setup and packages used in a document within a header /// and footer while the body contains content and controls its presentation. @@ -63,7 +61,7 @@ impl Default for Document { impl fmt::Display for Document { /// Returns the entire document in LaTeX string. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}{}{}", self.header, self.body, self.footer) + write!(f, "{}\n{}{}", self.header, self.body, self.footer) } } @@ -80,7 +78,7 @@ pub(crate) enum RenderCommand { Gate(String, String), /// Make a phase on the wire with a rotation and optional superscript #[display(fmt = "\\phase{{{_0}{_1}}}")] - Phase(Parameter, String), + Phase(Symbol, String), /// Add a superscript to a gate #[display(fmt = "^{{\\{_0}}}")] Super(String), @@ -101,14 +99,25 @@ pub(crate) enum RenderCommand { Targ, } -/// Types of parameters passed to commands. -#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Hash, Default)] -pub(crate) enum Parameter { - #[default] - None, - /// Symbolic parameters - #[display(fmt = "{_0}")] - Symbol(Symbol), +impl Symbol { + pub(crate) fn from_expression(expression: Expression) -> Self { + // get the name of the supported expression + let text = match expression { + Expression::Address(mr) => mr.name, + Expression::Number(c) => c.re.to_string(), + expression => expression.to_string(), + }; + + Self::Text(text) + } + + /// Convert a text symbol to a LaTeX symbol. + pub(crate) fn texify(self) -> Self { + match &self { + Symbol::Text(text) => Symbol::from_str(text).unwrap_or(self), + _ => self, + } + } } /// Supported Greek and alphanumeric symbols. @@ -129,10 +138,38 @@ pub(crate) enum Symbol { Text(String), } +/// RenderSettings contains the metadata that allows the user to customize how +/// the circuit is rendered or use the default implementation. +#[derive(Clone, Copy, Debug)] +pub struct RenderSettings { + /// Convert numerical constants, e.g. pi, to LaTeX form. + pub texify_numerical_constants: bool, + /// Include all qubits implicitly referenced in the Quil program. + pub impute_missing_qubits: bool, + /// Label qubit lines. + pub label_qubit_lines: bool, +} + +impl Default for RenderSettings { + /// Returns the default RenderSettings. + fn default() -> Self { + Self { + /// false: pi is π. + texify_numerical_constants: true, + /// true: `CNOT 0 2` would have three qubit lines: 0, 1, 2. + impute_missing_qubits: false, + /// false: remove Lstick/Rstick from latex. + label_qubit_lines: true, + } + } +} + #[derive(Clone, Debug, thiserror::Error, PartialEq, Eq, Hash)] pub enum LatexGenError { #[error("Found a target qubit with no control qubit.")] FoundTargetWithNoControl, + #[error("Qubits are required to build a circuit.")] + RequiresQubits, #[error("Circuit does not have qubit {0}")] QubitNotFound(u64), #[error("The FORKED modifier is unsupported.")] @@ -177,28 +214,15 @@ impl Program { // get a reference to the current program let instructions = self.to_instructions(false); - // initialize a new diagram - let mut diagram = Diagram { - settings, - ..Default::default() - }; - - // initialize circuit with empty wires of all qubits in program - let qubits = Program::get_used_qubits(self); - for qubit in &qubits { - let wire = Wire { - columns: Vec::with_capacity(instructions.len()), - }; - diagram - .circuit - .insert(*qubit.as_fixed().unwrap(), Box::new(wire)); - } + // get fixed qubits as vector + let qubits = self + .get_used_qubits() + .into_iter() + .map(|q| q.into_fixed().unwrap()) + .collect(); - // are implicit qubits required in settings and are there at least two or more qubits in the diagram? - if diagram.settings.impute_missing_qubits { - // add implicit qubits to circuit - RenderSettings::impute_missing_qubits(instructions.len(), &mut diagram.circuit); - } + // initialize a new diagram + let mut diagram = Diagram::new(qubits, settings)?; // build a circuit from the instructions for instruction in instructions { @@ -217,8 +241,7 @@ impl Program { return Err(LatexGenError::FoundTargetWithNoControl); } - diagram.apply_gate(&gate)?; - diagram.apply_empty(&qubits, &gate); + diagram.add_gate(&gate)?; } // GateDefinition is supported but inserted into the circuit using its Gate instruction form Instruction::GateDefinition(_) => continue, @@ -274,7 +297,8 @@ mod tests { use crate::program::latex::{tests::get_latex, Document, RenderSettings}; #[test] - fn test_template() { + #[should_panic] + fn test_empty_program() { insta::assert_snapshot!(get_latex("", RenderSettings::default())); } @@ -471,7 +495,7 @@ mod tests { /// Test module for ``Quantikz`` Commands mod commands { - use crate::program::latex::{Parameter, RenderCommand, Symbol}; + use crate::program::latex::{RenderCommand, Symbol}; #[test] fn test_command_left_ket() { @@ -485,11 +509,7 @@ mod tests { #[test] fn test_command_phase() { - insta::assert_snapshot!(RenderCommand::Phase( - Parameter::Symbol(Symbol::Pi), - String::new() - ) - .to_string()); + insta::assert_snapshot!(RenderCommand::Phase(Symbol::Pi, String::new()).to_string()); } #[test] diff --git a/src/program/latex/settings.rs b/src/program/latex/settings.rs deleted file mode 100644 index 0e2e9dd1..00000000 --- a/src/program/latex/settings.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::collections::BTreeMap; - -use super::diagram::wire::{QuantikzColumn, Wire}; - -/// RenderSettings contains the metadata that allows the user to customize how -/// the circuit is rendered or use the default implementation. -#[derive(Clone, Copy, Debug)] -pub struct RenderSettings { - /// Convert numerical constants, e.g. pi, to LaTeX form. - pub texify_numerical_constants: bool, - /// Include all qubits implicitly referenced in the Quil program. - pub impute_missing_qubits: bool, - /// Label qubit lines. - pub label_qubit_lines: bool, -} - -impl Default for RenderSettings { - /// Returns the default RenderSettings. - fn default() -> Self { - Self { - /// false: pi is π. - texify_numerical_constants: true, - /// true: `CNOT 0 2` would have three qubit lines: 0, 1, 2. - impute_missing_qubits: false, - /// false: remove Lstick/Rstick from latex. - label_qubit_lines: true, - } - } -} - -impl RenderSettings { - /// Adds missing qubits between the first qubit and last qubit in a - /// diagram's circuit. If a missing qubit is found, a new wire is created - /// and pushed to the diagram's circuit. - /// - /// # Arguments - /// `last_column` - total number of instructions from Program - /// `circuit` - the circuit of the diagram - /// - /// # Examples - /// ``` - /// use quil_rs::{Program, program::latex::settings::RenderSettings}; - /// use std::str::FromStr; - /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); - /// let settings = RenderSettings { - /// impute_missing_qubits: true, - /// ..Default::default() - /// }; - /// program.to_latex(settings).expect(""); - /// ``` - pub(crate) fn impute_missing_qubits( - last_column: usize, - circuit: &mut BTreeMap>, - ) { - let mut keys_iter = circuit.keys(); - - // get the first qubit in the BTreeMap - let Some(first) = keys_iter - .next() - .map(|wire| wire + 1) else { return; }; - - // get the last qubit in the BTreeMap - let Some(last) = keys_iter - .last() - .map(|wire| wire - 1) else { return; }; - - // search through the range of qubits - for qubit in first..=last { - // if the qubit is not found impute it - circuit.entry(qubit).or_insert_with(|| { - let mut wire = Wire { - ..Default::default() - }; - - // insert empties based on total number of columns - for _ in 0..last_column { - wire.columns.push(QuantikzColumn::default()); - } - - Box::new(wire) - }); - } - } -} From b07ff0f907f45a2c34c840bdffd375c572d0d93a Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 12 Apr 2023 17:44:41 -0700 Subject: [PATCH 114/118] Fix doc tests, remove dev notes. --- src/program/latex/diagram/cell.rs | 1 - src/program/latex/diagram/mod.rs | 14 ++------------ src/program/latex/mod.rs | 6 +++--- ..._program__latex__tests__document__template.snap | 13 ------------- 4 files changed, 5 insertions(+), 29 deletions(-) delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap diff --git a/src/program/latex/diagram/cell.rs b/src/program/latex/diagram/cell.rs index eae5a8de..921ecb73 100644 --- a/src/program/latex/diagram/cell.rs +++ b/src/program/latex/diagram/cell.rs @@ -6,7 +6,6 @@ use crate::instruction::{Gate, GateModifier}; use super::super::{LatexGenError, RenderCommand, Symbol}; -// Should this be enum? Before it was `struct` #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub(crate) enum QuantikzCell { Gate(QuantikzGate), diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index dd303333..b5bb9f82 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -49,7 +49,7 @@ impl Diagram { /// /// # Examples /// ``` - /// use quil_rs::{Program, program::latex::settings::RenderSettings}; + /// use quil_rs::{Program, program::latex::RenderSettings}; /// use std::str::FromStr; /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); /// let settings = RenderSettings { @@ -59,21 +59,11 @@ impl Diagram { /// program.to_latex(settings).expect(""); /// ``` pub(crate) fn build_wire_map(&mut self, mut qubits: Vec) -> Result<(), LatexGenError> { - // sort qubits so that wire mapping contains the correct row number for each qubit - // program.get_qubits() => [3, 5, 1] - // qubits.sort() => [1, 3, 5] - // column_indices = [0, 1, 2] - // expected wires = { - // 3: 1 - // 5: 2 - // 1: 0 - // } - - // TODO: Return error if qubits.is_empty() { return Err(LatexGenError::RequiresQubits); } + // sort so that mapping contains the correct row number for each qubit qubits.sort(); if self.settings.impute_missing_qubits { for (i, qubit) in diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index f5ad459d..ab01eb39 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -197,7 +197,7 @@ impl Program { /// # Examples /// ``` /// // To LaTeX for the Bell State Program. - /// use quil_rs::{Program, program::latex::settings::RenderSettings}; + /// use quil_rs::{Program, program::latex::RenderSettings}; /// use std::str::FromStr; /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); /// let latex = program.to_latex(RenderSettings::default()).expect(""); @@ -205,7 +205,7 @@ impl Program { /// /// ``` /// // To LaTeX for the Toffoli Gate Program. - /// use quil_rs::{Program, program::latex::settings::RenderSettings}; + /// use quil_rs::{Program, program::latex::RenderSettings}; /// use std::str::FromStr; /// let program = Program::from_str("CONTROLLED CNOT 2 1 0").expect(""); /// let latex = program.to_latex(RenderSettings::default()).expect(""); @@ -299,7 +299,7 @@ mod tests { #[test] #[should_panic] fn test_empty_program() { - insta::assert_snapshot!(get_latex("", RenderSettings::default())); + get_latex("", RenderSettings::default()); } #[test] diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap deleted file mode 100644 index baa6556b..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap +++ /dev/null @@ -1,13 +0,0 @@ ---- -source: src/program/latex/mod.rs -assertion_line: 397 -expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\end{tikzcd}\n\\end{document}\"#" ---- -\documentclass[convert={density=300,outext=.png}]{standalone} -\usepackage[margin=1in]{geometry} -\usepackage{tikz} -\usetikzlibrary{quantikz} -\begin{document} -\begin{tikzcd} -\end{tikzcd} -\end{document} From 12b5ee3feff8648f4553b51a46fbc1d972c1f4e8 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Wed, 12 Apr 2023 17:44:41 -0700 Subject: [PATCH 115/118] Fix doc tests, remove dev notes and template snapshot test. --- src/program/latex/diagram/cell.rs | 1 - src/program/latex/diagram/mod.rs | 14 ++------------ src/program/latex/mod.rs | 6 +++--- ..._program__latex__tests__document__template.snap | 13 ------------- 4 files changed, 5 insertions(+), 29 deletions(-) delete mode 100644 src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap diff --git a/src/program/latex/diagram/cell.rs b/src/program/latex/diagram/cell.rs index eae5a8de..921ecb73 100644 --- a/src/program/latex/diagram/cell.rs +++ b/src/program/latex/diagram/cell.rs @@ -6,7 +6,6 @@ use crate::instruction::{Gate, GateModifier}; use super::super::{LatexGenError, RenderCommand, Symbol}; -// Should this be enum? Before it was `struct` #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub(crate) enum QuantikzCell { Gate(QuantikzGate), diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index dd303333..b5bb9f82 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -49,7 +49,7 @@ impl Diagram { /// /// # Examples /// ``` - /// use quil_rs::{Program, program::latex::settings::RenderSettings}; + /// use quil_rs::{Program, program::latex::RenderSettings}; /// use std::str::FromStr; /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); /// let settings = RenderSettings { @@ -59,21 +59,11 @@ impl Diagram { /// program.to_latex(settings).expect(""); /// ``` pub(crate) fn build_wire_map(&mut self, mut qubits: Vec) -> Result<(), LatexGenError> { - // sort qubits so that wire mapping contains the correct row number for each qubit - // program.get_qubits() => [3, 5, 1] - // qubits.sort() => [1, 3, 5] - // column_indices = [0, 1, 2] - // expected wires = { - // 3: 1 - // 5: 2 - // 1: 0 - // } - - // TODO: Return error if qubits.is_empty() { return Err(LatexGenError::RequiresQubits); } + // sort so that mapping contains the correct row number for each qubit qubits.sort(); if self.settings.impute_missing_qubits { for (i, qubit) in diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index f5ad459d..ab01eb39 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -197,7 +197,7 @@ impl Program { /// # Examples /// ``` /// // To LaTeX for the Bell State Program. - /// use quil_rs::{Program, program::latex::settings::RenderSettings}; + /// use quil_rs::{Program, program::latex::RenderSettings}; /// use std::str::FromStr; /// let program = Program::from_str("H 0\nCNOT 0 1").expect(""); /// let latex = program.to_latex(RenderSettings::default()).expect(""); @@ -205,7 +205,7 @@ impl Program { /// /// ``` /// // To LaTeX for the Toffoli Gate Program. - /// use quil_rs::{Program, program::latex::settings::RenderSettings}; + /// use quil_rs::{Program, program::latex::RenderSettings}; /// use std::str::FromStr; /// let program = Program::from_str("CONTROLLED CNOT 2 1 0").expect(""); /// let latex = program.to_latex(RenderSettings::default()).expect(""); @@ -299,7 +299,7 @@ mod tests { #[test] #[should_panic] fn test_empty_program() { - insta::assert_snapshot!(get_latex("", RenderSettings::default())); + get_latex("", RenderSettings::default()); } #[test] diff --git a/src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap b/src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap deleted file mode 100644 index baa6556b..00000000 --- a/src/program/latex/snapshots/quil_rs__program__latex__tests__document__template.snap +++ /dev/null @@ -1,13 +0,0 @@ ---- -source: src/program/latex/mod.rs -assertion_line: 397 -expression: "r#\"\\documentclass[convert={density=300,outext=.png}]{standalone}\n\\usepackage[margin=1in]{geometry}\n\\usepackage{tikz}\n\\usetikzlibrary{quantikz}\n\\begin{document}\n\\begin{tikzcd}\n\\end{tikzcd}\n\\end{document}\"#" ---- -\documentclass[convert={density=300,outext=.png}]{standalone} -\usepackage[margin=1in]{geometry} -\usepackage{tikz} -\usetikzlibrary{quantikz} -\begin{document} -\begin{tikzcd} -\end{tikzcd} -\end{document} From b504fecf943dd7ed1a5ea8b484339b8e6974665d Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Fri, 14 Apr 2023 17:06:13 -0700 Subject: [PATCH 116/118] Add error handling, clean up code and comments, remove sorting with BTreeSet. --- src/program/latex/diagram/cell.rs | 132 ++++++++++-------------------- src/program/latex/diagram/mod.rs | 70 ++++++++-------- src/program/latex/mod.rs | 28 +++---- 3 files changed, 88 insertions(+), 142 deletions(-) diff --git a/src/program/latex/diagram/cell.rs b/src/program/latex/diagram/cell.rs index 921ecb73..da073b98 100644 --- a/src/program/latex/diagram/cell.rs +++ b/src/program/latex/diagram/cell.rs @@ -1,10 +1,8 @@ -use std::fmt; - use lazy_regex::{Lazy, Regex}; use crate::instruction::{Gate, GateModifier}; -use super::super::{LatexGenError, RenderCommand, Symbol}; +use super::super::{LatexGenError, RenderCommand, RenderSettings, Symbol}; #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub(crate) enum QuantikzCell { @@ -15,57 +13,58 @@ pub(crate) enum QuantikzCell { } pub(crate) struct CellSettings { - pub(crate) texify: bool, + pub(crate) texify_numerical_constants: bool, +} + +impl From for CellSettings { + fn from(render_settings: RenderSettings) -> Self { + Self { + texify_numerical_constants: render_settings.texify_numerical_constants, + } + } } impl QuantikzCell { pub(crate) fn to_latex(&self, settings: &CellSettings) -> Result { - // match the cell type match self { - QuantikzCell::Empty => { - // write an empty column - Ok(format!("{}", &RenderCommand::Qw)) - } - QuantikzCell::Control(target) => { - // write a control gate pointing to its target - Ok(format!("{}", &(RenderCommand::Ctrl(*target)))) - } + QuantikzCell::Empty => Ok(format!("{}", &RenderCommand::Qw)), + QuantikzCell::Control(target) => Ok(format!("{}", &(RenderCommand::Ctrl(*target)))), QuantikzCell::Gate(QuantikzGate { name, parameter, dagger_count, ctrl_count, }) => { - // build the dagger superscript - // appended to the end of the gate name let mut superscript = String::new(); (0..*dagger_count).for_each(|_| { superscript.push_str(&RenderCommand::Super(String::from("dagger")).to_string()); }); - let symbol = parameter - .clone() - .map(|p| if settings.texify { p.texify() } else { p }); - - // write a phase gate with its rotation parameter + let symbol = parameter.clone().map(|p| { + if settings.texify_numerical_constants { + p.texify() + } else { + p + } + }); if name == "PHASE" { Ok(format!( "{}", - &RenderCommand::Phase(symbol.unwrap(), superscript) + &RenderCommand::Phase( + symbol.ok_or(LatexGenError::MissingParameter)?, + superscript + ) )) // the conditional defines a target gate - } else if name == "X" && ctrl_count > &0 { - // if there a daggers then write it as an X gate - if dagger_count > &0 { + } else if name == "X" && *ctrl_count > 0 { + if *dagger_count > 0 { Ok(format!( "{}", &RenderCommand::Gate(name.to_string(), superscript) )) - // otherwise write it as a closed dot } else { Ok(format!("{}", &RenderCommand::Targ)) } - // write a gate with its name and superscript } else { Ok(format!( "{}", @@ -77,58 +76,6 @@ impl QuantikzCell { } } -impl fmt::Display for QuantikzCell { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // match the cell type - match self { - QuantikzCell::Empty => { - // write an empty column - write!(f, "{}", &RenderCommand::Qw)?; - } - QuantikzCell::Control(target) => { - // write a control gate pointing to its target - write!(f, "{}", &(RenderCommand::Ctrl(*target)))?; - } - QuantikzCell::Gate(QuantikzGate { - name, - parameter, - dagger_count, - ctrl_count, - }) => { - // build the dagger superscript - // appended to the end of the gate name - let mut superscript = String::new(); - (0..*dagger_count).for_each(|_| { - superscript.push_str(&RenderCommand::Super(String::from("dagger")).to_string()); - }); - - // write a phase gate with its rotation parameter - if name == "PHASE" { - write!( - f, - "{}", - &RenderCommand::Phase(parameter.as_ref().unwrap().clone(), superscript) - )?; - // the conditional defines a target gate - } else if name == "X" && ctrl_count > &0 { - // if there a daggers then write it as an X gate - if dagger_count > &0 { - write!(f, "{}", &RenderCommand::Gate(name.to_string(), superscript))?; - // otherwise write it as a closed dot - } else { - write!(f, "{}", &RenderCommand::Targ)?; - } - // write a gate with its name and superscript - } else { - write!(f, "{}", &RenderCommand::Gate(name.to_string(), superscript))?; - } - } - } - - Ok(()) - } -} - #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub(crate) struct QuantikzGate { pub(crate) name: String, @@ -137,23 +84,29 @@ pub(crate) struct QuantikzGate { pub(crate) ctrl_count: usize, } -/// Convert a ``Gate`` struct into a ``QuantikzGate`` struct. impl TryFrom for QuantikzGate { type Error = LatexGenError; fn try_from(gate: Gate) -> Result { // regex to match canonical controlled gates - static ABBREVIATED_CONTROLLED_GATE: Lazy = - Lazy::new(|| Regex::new("(?PC+)(?PPHASE|X|Y|Z|NOT)").unwrap()); + static ABBREVIATED_CONTROLLED_GATE: Lazy = Lazy::new(|| { + Regex::new("(?PC+)(?PPHASE|X|Y|Z|NOT)").expect("regex should be valid") + }); let mut canonical_gate = gate.name.to_string(); let mut ctrl_count = 0; if let Some(captures) = ABBREVIATED_CONTROLLED_GATE.captures(&gate.name) { - // get the base gate and the number of controls - let base = captures.name("base").unwrap().as_str(); - ctrl_count = captures.name("count").unwrap().as_str().len(); + let base = captures + .name("base") + .expect("capture group should have value") + .as_str(); + + ctrl_count = captures + .name("count") + .expect("capture group should have value") + .as_str() + .len(); - // set the canonical gate name match base { // NOT is an alias for X "NOT" => canonical_gate = "X".to_string(), @@ -161,11 +114,9 @@ impl TryFrom for QuantikzGate { } } - // count the supported modifiers let mut dagger_count = 0; for modifier in gate.modifiers { match modifier { - // return error for unsupported modifier FORKED GateModifier::Forked => { return Err(LatexGenError::UnsupportedModifierForked); } @@ -179,8 +130,10 @@ impl TryFrom for QuantikzGate { } if gate.parameters.len() > 1 { - // TODO: Create separate error for unsupported parameter length? - return Err(LatexGenError::UnsupportedGate { gate: gate.name }); + return Err(LatexGenError::UnsupportedParameterLength { + gate: gate.name, + length: gate.parameters.len(), + }); } let mut parameter = None; @@ -188,7 +141,6 @@ impl TryFrom for QuantikzGate { parameter = Some(Symbol::from_expression(gate.parameters[0].clone())) } - // return the QuantikzGate form of Gate Ok(QuantikzGate { name: canonical_gate, parameter, diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index b5bb9f82..b8a59207 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -1,4 +1,7 @@ -use std::{collections::BTreeMap, fmt}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt, +}; use crate::instruction::Gate; @@ -15,19 +18,19 @@ pub(crate) mod cell; /// that is serializable into LaTeX using the ``Quantikz`` RenderCommands. pub(crate) struct Diagram { settings: RenderSettings, - wires: BTreeMap, // qubit index -> column index + wires: BTreeMap, columns: Vec, } struct DiagramColumn { - cells: Vec, // number of wires + cells: Vec, } impl Diagram { - pub(crate) fn new(qubits: Vec, settings: RenderSettings) -> Result { - // Initialize a new `Diagram` with the given qubits - // If settings.impute_missing qubits, then pad the gaps. - + pub(crate) fn new( + qubits: BTreeSet, + settings: RenderSettings, + ) -> Result { let mut diagram = Self { settings, wires: BTreeMap::new(), @@ -58,17 +61,15 @@ impl Diagram { /// }; /// program.to_latex(settings).expect(""); /// ``` - pub(crate) fn build_wire_map(&mut self, mut qubits: Vec) -> Result<(), LatexGenError> { + pub(crate) fn build_wire_map(&mut self, qubits: BTreeSet) -> Result<(), LatexGenError> { if qubits.is_empty() { return Err(LatexGenError::RequiresQubits); } - // sort so that mapping contains the correct row number for each qubit - qubits.sort(); if self.settings.impute_missing_qubits { - for (i, qubit) in - (qubits[0]..*qubits.last().expect("qubits is not empty") + 1).enumerate() - { + let first = *qubits.first().expect("qubits is not empty"); + let last = *qubits.last().expect("qubits is not empty"); + for (i, qubit) in (first..last + 1).enumerate() { self.wires.insert(qubit, i); } } else { @@ -86,7 +87,6 @@ impl Diagram { /// # Arguments /// `gate` - the Gate of the Instruction from `to_latex`. pub(crate) fn add_gate(&mut self, gate: &Gate) -> Result<(), LatexGenError> { - // Initialize a column full of empty cells to be filled in. let mut cells: Vec = vec![QuantikzCell::Empty; self.wires.len()]; let quantikz_gate = cell::QuantikzGate::try_from(gate.clone())?; @@ -99,34 +99,37 @@ impl Diagram { // parameterized non-PHASE gates are unsupported if quantikz_gate.parameter.is_some() && !gate.name.contains("PHASE") { - // parameterized single qubit gates are unsupported return Err(LatexGenError::UnsupportedGate { gate: gate.name.clone(), }); } - // TODO: Error handling instead of unwrap let mut qubit_iterator = gate .qubits .iter() - .map(|q| q.clone().into_fixed().unwrap()) + .map(|q| { + q.clone() + .into_fixed() + .map_err(|_| LatexGenError::UnsupportedQubit) + }) .rev(); - // start iterator at target qubit - let target_qubit = qubit_iterator.next().unwrap(); + let target_qubit = qubit_iterator + .next() + .ok_or(LatexGenError::RequiresQubits)??; - // TODO: Return error instead of unwrap - let target_index = *self.wires.get(&target_qubit).unwrap(); + let target_index = *self + .wires + .get(&target_qubit) + .ok_or(LatexGenError::QubitNotFound(target_qubit))?; cells[target_index] = QuantikzCell::Gate(quantikz_gate); - // This is oversimplified for the example. There will be a little - // bit of pre-processing necessary to get your "canonical gate", - // and it probably makes sense to go in reverse order so you can start - // with the target qubit. for qubit in qubit_iterator { - // TODO: Error handling instead of unwrap - let column_index = *self.wires.get(&qubit).unwrap(); + let column_index = *self + .wires + .get(&qubit?) + .ok_or(LatexGenError::QubitNotFound(target_qubit))?; cells[column_index] = QuantikzCell::Control(target_index as i64 - column_index as i64) } @@ -137,21 +140,14 @@ impl Diagram { } impl fmt::Display for Diagram { - /// Returns a result containing the body of the Document. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // write the LaTeX string for each wire in the circuit let last = self.wires.keys().last().unwrap_or(&0); - let cell_settings = CellSettings { - texify: self.settings.texify_numerical_constants, - }; + let cell_settings = CellSettings::from(self.settings); for (qubit, cell_index) in &self.wires { - // are labels on in settings? if self.settings.label_qubit_lines { - // write the label to the left side of wire write!(f, "{}", RenderCommand::LeftWireLabel(*qubit))?; } else { - // write an empty column buffer as the first column write!(f, "{}", RenderCommand::Qw)?; } @@ -167,13 +163,11 @@ impl fmt::Display for Diagram { write!(f, "{}{}", &RenderCommand::Separate, &RenderCommand::Qw)?; - // omit a new row if this is the last qubit wire + // omit LaTeX line break (`\\`) if this is the last qubit if *qubit != *last { - // otherwise, write a new row to the end of the line write!(f, " {}", &RenderCommand::Nr)?; } - // write a newline between each row and or the body and the document footer writeln!(f)?; } diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index ab01eb39..113dc919 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -24,7 +24,10 @@ mod diagram; use std::str::FromStr; -use std::{collections::HashSet, fmt}; +use std::{ + collections::{BTreeSet, HashSet}, + fmt, +}; use crate::expression::Expression; use crate::instruction::{Instruction, Qubit}; @@ -166,18 +169,22 @@ impl Default for RenderSettings { #[derive(Clone, Debug, thiserror::Error, PartialEq, Eq, Hash)] pub enum LatexGenError { - #[error("Found a target qubit with no control qubit.")] - FoundTargetWithNoControl, + #[error("Gate instruction has duplicate qubits.")] + DuplicateQubits, #[error("Qubits are required to build a circuit.")] RequiresQubits, #[error("Circuit does not have qubit {0}")] QubitNotFound(u64), + #[error("Parameterized gate has no parameter.")] + MissingParameter, #[error("The FORKED modifier is unsupported.")] UnsupportedModifierForked, #[error("This instruction is unsupported: {instruction}.")] UnsupportedInstruction { instruction: String }, #[error("This gate is unsupported: {gate}.")] UnsupportedGate { gate: String }, + #[error("The parameter length {length} of gate {gate} is unsupported")] + UnsupportedParameterLength { gate: String, length: usize }, #[error("Only fixed qubits are supported")] UnsupportedQubit, } @@ -211,25 +218,20 @@ impl Program { /// let latex = program.to_latex(RenderSettings::default()).expect(""); /// ``` pub fn to_latex(&self, settings: RenderSettings) -> Result { - // get a reference to the current program let instructions = self.to_instructions(false); - // get fixed qubits as vector let qubits = self .get_used_qubits() .into_iter() - .map(|q| q.into_fixed().unwrap()) - .collect(); + .map(|q| q.into_fixed()) + .collect::, _>>() + .map_err(|_| LatexGenError::UnsupportedQubit)?; - // initialize a new diagram let mut diagram = Diagram::new(qubits, settings)?; - // build a circuit from the instructions for instruction in instructions { match instruction { - // build the circuit from a gate instruction Instruction::Gate(gate) => { - // if there are duplicate qubits in the gate return an error if gate.qubits.len() != gate .qubits @@ -238,15 +240,13 @@ impl Program { .collect::>() .len() { - return Err(LatexGenError::FoundTargetWithNoControl); + return Err(LatexGenError::DuplicateQubits); } diagram.add_gate(&gate)?; } // GateDefinition is supported but inserted into the circuit using its Gate instruction form Instruction::GateDefinition(_) => continue, - - // all other instructions are not supported _ => { return Err(LatexGenError::UnsupportedInstruction { instruction: instruction.to_string(), From c4001355944aa1890478a03286cdef7eda9a4a43 Mon Sep 17 00:00:00 2001 From: Ryan Meneses <58452495+hiyaryan@users.noreply.github.com> Date: Mon, 24 Apr 2023 11:47:31 -0700 Subject: [PATCH 117/118] Apply suggestions from code review. Co-authored-by: Michael Bryant --- src/program/latex/diagram/cell.rs | 4 ++-- src/program/latex/diagram/mod.rs | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/program/latex/diagram/cell.rs b/src/program/latex/diagram/cell.rs index da073b98..400a5c16 100644 --- a/src/program/latex/diagram/cell.rs +++ b/src/program/latex/diagram/cell.rs @@ -27,8 +27,8 @@ impl From for CellSettings { impl QuantikzCell { pub(crate) fn to_latex(&self, settings: &CellSettings) -> Result { match self { - QuantikzCell::Empty => Ok(format!("{}", &RenderCommand::Qw)), - QuantikzCell::Control(target) => Ok(format!("{}", &(RenderCommand::Ctrl(*target)))), + QuantikzCell::Empty => Ok(RenderCommand::Qw.to_string()), + QuantikzCell::Control(target) => Ok(RenderCommand::Ctrl(*target).to_string()), QuantikzCell::Gate(QuantikzGate { name, parameter, diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index b8a59207..33e1c25f 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -107,8 +107,9 @@ impl Diagram { let mut qubit_iterator = gate .qubits .iter() + .cloned() .map(|q| { - q.clone() + q .into_fixed() .map_err(|_| LatexGenError::UnsupportedQubit) }) @@ -126,9 +127,10 @@ impl Diagram { cells[target_index] = QuantikzCell::Gate(quantikz_gate); for qubit in qubit_iterator { - let column_index = *self + let column_index = self .wires .get(&qubit?) + .copied() .ok_or(LatexGenError::QubitNotFound(target_qubit))?; cells[column_index] = QuantikzCell::Control(target_index as i64 - column_index as i64) } @@ -141,7 +143,7 @@ impl Diagram { impl fmt::Display for Diagram { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let last = self.wires.keys().last().unwrap_or(&0); + let last = self.wires.keys().last().copied().unwrap_or(0); let cell_settings = CellSettings::from(self.settings); for (qubit, cell_index) in &self.wires { From a837cf47f9614917543ac2e609b3b7cb9efdc016 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 24 Apr 2023 12:12:18 -0700 Subject: [PATCH 118/118] Amend previous commit. --- src/program/latex/diagram/cell.rs | 23 +++++++++-------------- src/program/latex/diagram/mod.rs | 4 ++-- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/program/latex/diagram/cell.rs b/src/program/latex/diagram/cell.rs index 400a5c16..c5bc3976 100644 --- a/src/program/latex/diagram/cell.rs +++ b/src/program/latex/diagram/cell.rs @@ -48,28 +48,23 @@ impl QuantikzCell { } }); if name == "PHASE" { - Ok(format!( - "{}", - &RenderCommand::Phase( + Ok(RenderCommand::Phase( symbol.ok_or(LatexGenError::MissingParameter)?, superscript - ) - )) + ).to_string() + ) // the conditional defines a target gate } else if name == "X" && *ctrl_count > 0 { if *dagger_count > 0 { - Ok(format!( - "{}", - &RenderCommand::Gate(name.to_string(), superscript) - )) + Ok( + RenderCommand::Gate(name.to_string(), superscript).to_string() + ) } else { - Ok(format!("{}", &RenderCommand::Targ)) + Ok(RenderCommand::Targ.to_string()) } } else { - Ok(format!( - "{}", - &RenderCommand::Gate(name.to_string(), superscript) - )) + Ok(RenderCommand::Gate(name.to_string(), superscript).to_string() + ) } } } diff --git a/src/program/latex/diagram/mod.rs b/src/program/latex/diagram/mod.rs index 33e1c25f..a7310eae 100644 --- a/src/program/latex/diagram/mod.rs +++ b/src/program/latex/diagram/mod.rs @@ -143,7 +143,7 @@ impl Diagram { impl fmt::Display for Diagram { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let last = self.wires.keys().last().copied().unwrap_or(0); + let last = self.wires.keys().last().copied().unwrap_or_default(); let cell_settings = CellSettings::from(self.settings); for (qubit, cell_index) in &self.wires { @@ -166,7 +166,7 @@ impl fmt::Display for Diagram { write!(f, "{}{}", &RenderCommand::Separate, &RenderCommand::Qw)?; // omit LaTeX line break (`\\`) if this is the last qubit - if *qubit != *last { + if *qubit != last { write!(f, " {}", &RenderCommand::Nr)?; }