From 7be2094537b0a2b0a087739584acfbc803e28a66 Mon Sep 17 00:00:00 2001 From: Ryan Meneses Date: Mon, 6 Feb 2023 13:30:59 -0800 Subject: [PATCH 01/75] 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 02/75] 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 03/75] 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 04/75] 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 05/75] 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 06/75] 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 07/75] 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 08/75] 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 09/75] 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 10/75] 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 11/75] 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 12/75] 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 13/75] 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 14/75] 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 15/75] 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 16/75] 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 17/75] 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 18/75] 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 19/75] 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 20/75] 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 21/75] 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 22/75] 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 23/75] 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 24/75] 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 25/75] 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 26/75] 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 27/75] 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 28/75] 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 29/75] 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 30/75] 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 31/75] 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 32/75] 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 33/75] 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 34/75] 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 35/75] 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 36/75] 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 37/75] 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 38/75] 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 39/75] 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 40/75] 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 41/75] 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 42/75] 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 43/75] 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 44/75] 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 45/75] 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 46/75] 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 47/75] 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 48/75] 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 49/75] 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 50/75] 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 51/75] 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 52/75] 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 53/75] 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 54/75] 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 55/75] 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 56/75] 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 57/75] 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 58/75] 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 59/75] 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 60/75] 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 61/75] 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 62/75] 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 63/75] 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 64/75] 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 65/75] 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 66/75] 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 67/75] 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 68/75] 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 69/75] 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 70/75] 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 71/75] 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 fcf9215b5f253cfac0540da47999b1b12f6a38d8 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Thu, 23 Mar 2023 22:38:53 -0700 Subject: [PATCH 72/75] WIP: refactor: LaTeX generation --- Cargo.lock | 25 +- Cargo.toml | 2 + src/program/latex/circuit.rs | 246 ++++++++++++++++++++ src/program/latex/diagram.rs | 430 +++++++++++++++++++++++++++++++++++ src/program/latex/mod.rs | 68 ++---- 5 files changed, 715 insertions(+), 56 deletions(-) create mode 100644 src/program/latex/circuit.rs create mode 100644 src/program/latex/diagram.rs diff --git a/Cargo.lock b/Cargo.lock index 9f323d46..da024494 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" @@ -616,9 +625,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" @@ -761,9 +770,11 @@ dependencies = [ "nom", "nom_locate", "num-complex", + "once_cell", "petgraph", "proptest", "proptest-derive", + "regex", "rstest", "serde", "strum", @@ -862,18 +873,20 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "cce168fea28d3e05f158bda4576cf0c844d5045bc2cc3620fa0292ed5bb5814c" 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 25ef4130..4cd3cedf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,9 @@ lexical = "6.1.1" nom = "7.1.1" nom_locate = "4.0.0" num-complex = "0.4.0" +once_cell = "1.17.1" petgraph = "0.6.2" +regex = "1.7.2" serde = { version = "1.0.125", features = ["derive"] } strum = { version = "0.24.1", features = ["derive"] } thiserror = "1.0.37" diff --git a/src/program/latex/circuit.rs b/src/program/latex/circuit.rs new file mode 100644 index 00000000..e5da73e4 --- /dev/null +++ b/src/program/latex/circuit.rs @@ -0,0 +1,246 @@ +//! Higher-level construct which represents a program in a way that can be converted to a diagram. + +use std::collections::BTreeSet; + +use once_cell::sync::Lazy; +use regex::Regex; + +use crate::instruction::{Gate, GateModifier, Instruction, Measurement, Qubit}; + +use super::RenderSettings; + +#[derive(Debug)] +pub(super) enum CircuitElement { + SingleQubitGate { + dagger_count: u64, + name: String, + parameters: Vec, + qubit: u64, + controls: BTreeSet, + }, + Measurement { + qubit: u64, + }, +} + +impl Gate { + // TODO: use Cow to prevent allocation when the gate is already in canonical form + fn to_canonical_form(&self) -> Self { + let mut new = self.clone(); + + static ABBREVIATED_CONTROLLED_GATE: Lazy = + Lazy::new(|| Regex::new("(?PC+)(?PPHASE|X|Y|Z|NOT)").unwrap()); + + if let Some(captures) = ABBREVIATED_CONTROLLED_GATE.captures(&new.name) { + let count = captures.name("count").unwrap().as_str().len(); + let base = captures.name("base").unwrap().as_str(); + + new.name = base.to_string(); + new.modifiers.extend(vec![GateModifier::Controlled; count]); // TODO: also invert qubit order for these qubits. Yikes, Quil + } + + new + } + + fn extract_fixed_controls(&mut self) -> BuildCircuitResult> { + let mut control_count = 0; + + self.modifiers.retain(|modifier| { + if modifier == &GateModifier::Controlled { + control_count += 1; + false + } else { + true + } + }); + + let new_qubits = self.qubits.split_off(control_count); + let controls = self + .qubits + .iter() + .map(|qubit| match qubit { + Qubit::Fixed(index) => Ok(*index), + Qubit::Variable(_) => todo!(), + }) + .collect::, _>>()?; + + self.qubits = new_qubits; + + for qubit in &self.qubits { + if let Qubit::Fixed(index) = qubit { + assert!(!controls.contains(index)) + } + } + + Ok(controls) + } + + fn extract_dagger_count(&mut self) -> u64 { + let mut count = 0; + + self.modifiers.retain(|modifier| { + if modifier == &GateModifier::Dagger { + count += 1; + false + } else { + true + } + }); + + count + } +} + +impl TryFrom<&Gate> for CircuitElement { + type Error = BuildCircuitError; + + fn try_from(gate: &Gate) -> Result { + let mut canonical_gate = gate.to_canonical_form(); + let controls = canonical_gate.extract_fixed_controls()?; + let dagger_count = canonical_gate.extract_dagger_count(); + + match canonical_gate.qubits.as_slice() { + &[Qubit::Fixed(qubit)] if canonical_gate.modifiers.is_empty() => { + Ok(Self::SingleQubitGate { + controls, + parameters: gate.parameters.iter().map(|p| p.to_string()).collect(), + dagger_count, + name: canonical_gate.name.clone(), + qubit, + }) + } + _ => Err(BuildCircuitError::UnsupportedGate { gate: gate.clone() }), + } + } +} + +impl TryFrom<&Measurement> for CircuitElement { + type Error = BuildCircuitError; + + fn try_from(measurement: &Measurement) -> Result { + match measurement.qubit { + Qubit::Fixed(qubit) => Ok(Self::Measurement { qubit }), + Qubit::Variable(_) => Err(BuildCircuitError::UnsupportedMeasurement { + measurement: measurement.clone(), + }), + } + } +} + +#[derive(Debug)] +pub(super) struct CircuitColumn { + // TODO: this could be Vec if we compress columns + pub element: CircuitElement, +} + +#[derive(Debug, Default)] +pub(super) struct Circuit { + pub columns: Vec, + + pub settings: RenderSettings, +} + +impl Circuit { + pub fn get_qubit_indices(&self) -> BTreeSet { + let mut indices = BTreeSet::new(); + + for column in &self.columns { + match &column.element { + CircuitElement::SingleQubitGate { + qubit, controls, .. + } => { + indices.insert(*qubit); + indices.extend(controls) + } + CircuitElement::Measurement { qubit } => { + indices.insert(*qubit); + } + } + } + + indices + } +} + +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum BuildCircuitError { + // TODO: use references with 'program lifetime + #[error("cannot render gate as LaTeX: {gate:?}")] + UnsupportedGate { gate: Gate }, + + #[error("cannot render measurement as LaTeX: {measurement:?}")] + UnsupportedMeasurement { measurement: Measurement }, + + #[error("cannot render instruction as LaTeX: {instruction}")] + UnsupportedInstruction { instruction: Instruction }, +} + +pub type BuildCircuitResult = Result; + +impl Circuit { + pub(super) fn try_from_program( + program: &crate::Program, + settings: RenderSettings, + ) -> BuildCircuitResult { + let mut circuit = Circuit::default(); + circuit.settings = settings; // TODO not ideal, reconsider how and when these should be used. Not all settings are inherent to the circuit's construction + + for instruction in &program.to_instructions(true) { + let element = match instruction { + Instruction::Gate(gate) => gate.try_into().map(Some), + Instruction::Measurement(measurement) => measurement.try_into().map(Some), + Instruction::GateDefinition(_) | Instruction::Reset(_) => Ok(None), + _ => Err(BuildCircuitError::UnsupportedInstruction { + instruction: instruction.clone(), + }), + }?; + + if let Some(element) = element { + let column = CircuitColumn { element }; + + circuit.columns.push(column); + } + } + + Ok(circuit) + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeSet; + + use rstest::rstest; + + use crate::instruction::Gate; + + #[rstest] + #[case("CPHASE 0 1", "CONTROLLED PHASE 0 1")] + #[case("CZ 0 1", "CONTROLLED Z 0 1")] + #[case("DAGGER CZ 0 1", "DAGGER CONTROLLED Z 0 1")] + fn canonicalize_gate(#[case] input: &str, #[case] expected: &str) { + let input_gate = ::from_str(input).unwrap(); + let canonical = input_gate.to_canonical_form(); + assert_eq!(canonical.to_string().as_str(), expected); + } + + #[rstest] + #[case("CONTROLLED PHASE 1 0", "PHASE 0", vec![1])] + #[case("CONTROLLED CONTROLLED PHASE 2 1 0", "PHASE 0", vec![1, 2])] + #[case("CNOT 1 2", "NOT 2", vec![1])] + fn extract_controls( + #[case] input: &str, + #[case] expected: &str, + #[case] expected_controls: Vec, + ) { + let input_gate = ::from_str(input).unwrap(); + let mut canonical = input_gate.to_canonical_form(); + let controls = canonical.extract_fixed_controls().unwrap(); + assert_eq!(canonical.to_string().as_str(), expected); + assert_eq!( + controls, + expected_controls.into_iter().collect::>() + ); + } +} diff --git a/src/program/latex/diagram.rs b/src/program/latex/diagram.rs new file mode 100644 index 00000000..9a6f6a80 --- /dev/null +++ b/src/program/latex/diagram.rs @@ -0,0 +1,430 @@ +use std::{ + collections::{BTreeMap, BTreeSet, HashSet}, + ops::RangeInclusive, +}; + +use super::{ + circuit::{Circuit, CircuitElement}, + RenderSettings, +}; + +// Write every element from an iterable +macro_rules! write_many { + ($f:expr, $iter:expr) => {{ + for item in $iter { + write!($f, "{item}")?; + } + + Ok(()) + }}; +} + +// Write every element from an iterable, wrapped with a left and right element +macro_rules! write_many_delimited { + ($f:expr, $left:expr, $iter:expr, $right: expr) => {{ + write!($f, "{}", $left)?; + + write_many!($f, $iter)?; + + write!($f, "{}", $right) + }}; +} + +/// 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 +/// Ryan: derive_more::Display would be great here. All of the symbols are repeated between here and the display implementation +/// for discussion: tradeoffs of literal representation of Quantikz vs more ergonomics (both are acceptable, just consider the audience) +/// in our case, we don't expect this to be a general-purpose quantikz library, so it should be whatever makes this module most +/// readable. +#[derive(Clone, Debug)] +enum DiagramElement { + /// `\lstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the left. + /// Ryan: what's inside is not a string itself but more diagram elements + Lstick(Vec), + /// `\gate{name}`: Make a gate on the wire. + Gate(Vec), + /// `\phase{symbol}`: Make a phase on the wire with a rotation + Phase(Vec), + /// `^{\script}`: Add a superscript to a gate + Super(Vec), + /// `\qw`: Connect the current cell to the previous cell i.e. "do nothing". + Qw, + /// `\\`: Start a new row + Nr, + /// `\ctrl{wire}`: Make a control qubit--different from Control. + /// // Ryan: this makes more sense as an i64 since that's how it's used + Ctrl { + distance: i64, + }, + /// `\targ{}`: Make a controlled-not gate. + Targ, + + /// `\vqw{distance}`: Vertical quantum (single-line) wire + VerticalQuantumWire { + distance: i64, + }, + + /// `&`: Start a new cell in the same row/wire + Ampersand, + + /// `\control{}` + Control, + + /// `\meter{}` - Measurement + Meter, + + Ket(String), + + Text(String), + + String(String), + + Dagger, + + Symbol(String), +} + +impl DiagramElement { + fn daggers(count: u64) -> Vec { + (0..count) + .map(|_| Self::Super(vec![Self::Dagger])) + .collect() + + // This would work fine IIUC but would not pass snapshot + // Self::Super(vec![Self::Dagger; count as usize]) + } + + fn texify_parameter(name: String) -> Self { + // TODO: make static + let supported = [ + "alpha", "beta", "gamma", "theta", "pi", //TODO + ] + .into_iter() + .collect::>(); + + if supported.contains(&name.as_str()) { + Self::Symbol(name) + } else { + Self::Text(name) + } + } + + fn parameters(parameters: Vec, settings: &RenderSettings) -> Vec { + if settings.texify_numerical_constants { + parameters.into_iter().map(Self::texify_parameter).collect() + } else { + parameters.into_iter().map(Self::Text).collect() + } + } + + fn gate( + name: String, + parameters: Vec, + dagger_count: u64, + controlled: bool, + settings: &RenderSettings, + ) -> Self { + match name.as_str() { + // TODO: DAGGER CPHASE? + "PHASE" if dagger_count == 0 => Self::Phase(Self::parameters(parameters, settings)), + // TODO: DAGGER NOT? + "NOT" | "X" if dagger_count == 0 && controlled => Self::Targ, + _ => { + let mut gate_elements = vec![DiagramElement::String(name)]; + gate_elements.append(&mut DiagramElement::daggers(dagger_count)); + // if !parameters.is_empty() //TODO + + Self::Gate(gate_elements) + } + } + } + + fn qubit(index: u64) -> Self { + Self::Ket(format!("q_{{{index}}}")) + } +} + +impl std::fmt::Display for DiagramElement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DiagramElement::Lstick(wire) => { + write!(f, r#"\lstick{{"#)?; + write_many!(f, wire)?; + write!(f, "}}") + } + DiagramElement::Gate(elements) => { + write!(f, r"\gate{{")?; + write_many!(f, elements)?; + write!(f, "}}") + } + DiagramElement::Phase(inner) => write_many_delimited!(f, r"\phase{", inner, "}"), + DiagramElement::Super(inner) => write_many_delimited!(f, r"^{", inner, "}"), + DiagramElement::Qw => write!(f, r"\qw"), + DiagramElement::Nr => write!(f, r"\\"), + DiagramElement::Ctrl { distance } => write!(f, r#"\ctrl{{{distance}}}"#), + DiagramElement::Targ => write!(f, r"\targ{{}}"), + DiagramElement::VerticalQuantumWire { distance } => write!(f, r#"\vqw{{{distance}}}"#), + DiagramElement::Ampersand => write!(f, "&"), + DiagramElement::Control => write!(f, r"\control{{}}"), + DiagramElement::Meter => write!(f, r"\meter{{}}"), + DiagramElement::Ket(contents) => write!(f, r"\ket{{{contents}}}"), + DiagramElement::Text(string) => write!(f, r"\text{{{string}}}"), + DiagramElement::Dagger => write!(f, r"\dagger"), + DiagramElement::String(string) => write!(f, "{string}"), + DiagramElement::Symbol(inner) => write!(f, r#"\{inner}"#), + } + } +} + +#[derive(Debug)] +pub struct Diagram { + footer: Option, + header: Option, + wires: BTreeMap, +} + +impl Diagram { + const DEFAULT_FOOTER: &str = r"\end{tikzcd} +\end{document}"; + + const DEFAULT_HEADER: &str = r"\documentclass[convert={density=300,outext=.png}]{standalone} +\usepackage[margin=1in]{geometry} +\usepackage{tikz} +\usetikzlibrary{quantikz} +\begin{document} +\begin{tikzcd}"; + + fn extend_cell(&mut self, row: u64, column: u64, elements: Vec) { + self.wires + .entry(row) + .or_default() + .extend_at_column(column, elements); + } + + fn push_to_cell(&mut self, row: u64, column: u64, element: DiagramElement) { + self.wires + .entry(row) + .or_default() + .push_at_column(column, element); + } + + fn from_qubit_indices(qubits: I) -> Self + where + I: IntoIterator, + { + let mut diagram = Self::default(); + for qubit in qubits { + diagram.wires.insert(qubit, DiagramWire::default()); + } + + diagram + } + + fn with_default_header_and_footer(&mut self) { + self.with_footer(Self::DEFAULT_FOOTER); + self.with_header(Self::DEFAULT_HEADER); + } + + fn with_header(&mut self, header: S) + where + S: Into, + { + self.header = Some(header.into()) + } + + fn with_footer(&mut self, footer: S) + where + S: Into, + { + self.footer = Some(footer.into()) + } +} + +impl Default for Diagram { + fn default() -> Self { + let mut new = Self { + footer: None, + header: None, + wires: Default::default(), + }; + new.with_default_header_and_footer(); + new + } +} + +impl std::fmt::Display for Diagram { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(header) = &self.header { + writeln!(f, "{header}")?; + } + + let column_count = self + .wires + .values() + .filter_map(|wire| wire.elements.last_key_value().map(|(k, _)| *k)) + .max() + .unwrap_or(0); + + if !self.wires.is_empty() { + let mut wires = self.wires.values().peekable(); + + while let Some(wire) = wires.next() { + wire.fmt(f, column_count)?; + + if wires.peek().is_some() { + writeln!(f, " {}", DiagramElement::Nr)?; + } + } + + writeln!(f)?; + } + + if let Some(footer) = &self.footer { + write!(f, "{footer}")?; + } + + Ok(()) + } +} + +impl From for Diagram { + fn from(circuit: Circuit) -> Self { + let qubit_indices = circuit.get_qubit_indices(); + + let mut diagram = if circuit.settings.impute_missing_qubits { + get_set_min_and_max_values(&qubit_indices) + .map(|(&min, &max)| Diagram::from_qubit_indices(min..=max)) + .unwrap_or_default() + } else { + Diagram::from_qubit_indices(qubit_indices.iter().cloned()) + }; + + for (column_index, column) in circuit.columns.into_iter().enumerate() { + match column.element { + CircuitElement::SingleQubitGate { + dagger_count, + parameters, + name, + qubit, + controls, + } => { + let gate_element = DiagramElement::gate( + name, + parameters, + dagger_count, + !controls.is_empty(), + &circuit.settings, + ); + diagram.push_to_cell(qubit, column_index as u64, gate_element); + + for control in controls { + let distance = if qubit > control { + diagram.wires.range(control..qubit).count() as i64 + } else { + -(diagram.wires.range(qubit..control).count() as i64) + }; + + diagram.push_to_cell( + control, + column_index as u64, + DiagramElement::Ctrl { distance }, + ); + } + } + CircuitElement::Measurement { qubit } => { + diagram.push_to_cell(qubit, column_index as u64, DiagramElement::Meter) + } + } + + if circuit.settings.label_qubit_lines { + for (qubit, wire) in &mut diagram.wires { + wire.label_left = vec![DiagramElement::qubit(*qubit)] + } + } + } + + diagram + } +} + +/// Wire == row +#[derive(Debug, Default)] +struct DiagramWire { + pub label_left: Vec, + pub elements: BTreeMap>, // TODO: this could result in an invalid state, ie with lstick in the middle of a diagram (that's invalid, right?) +} + +impl DiagramWire { + fn extend_at_column(&mut self, column: u64, elements: Vec) { + self.elements.entry(column).or_default().extend(elements); + } + + fn push_at_column(&mut self, column: u64, element: DiagramElement) { + self.elements.entry(column).or_default().push(element); + } +} + +impl DiagramWire { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>, column_count: u64) -> std::fmt::Result { + if self.label_left.is_empty() { + write!(f, "{} &", DiagramElement::Qw)?; + } else { + write!(f, "{} &", DiagramElement::Lstick(self.label_left.clone()))?; + } + + let mut last_column_index: i64 = -1; + let mut elements = self.elements.iter().peekable(); + + while let Some((&column, cell_elements)) = elements.next() { + let gap = column as i64 - last_column_index; + + if cell_elements.is_empty() { + continue; + } + + for _ in 1..gap { + write!(f, " {} {}", DiagramElement::Qw, DiagramElement::Ampersand)?; + } + + for element in cell_elements { + write!(f, " {element}")?; + } + + if elements.peek().is_some() { + write!(f, " {}", DiagramElement::Ampersand)?; + } + + last_column_index = column as i64; + } + + if last_column_index <= column_count as i64 { + // Handle the first column specially; no leading ampersand needed + if last_column_index == -1 { + write!(f, " {}", DiagramElement::Qw)?; + last_column_index = 0; + } + + for _ in last_column_index..=(column_count as i64) { + write!(f, " {} {}", DiagramElement::Ampersand, DiagramElement::Qw,)?; + } + } + + Ok(()) + } +} + +fn get_set_min_and_max_values(set: &BTreeSet) -> Option<(&T, &T)> { + if set.is_empty() { + None + } else { + let min = set.iter().next().unwrap(); + let max = set.iter().next_back().unwrap(); + Some((min, max)) + } +} diff --git a/src/program/latex/mod.rs b/src/program/latex/mod.rs index ff634836..da7b1a7f 100644 --- a/src/program/latex/mod.rs +++ b/src/program/latex/mod.rs @@ -22,6 +22,9 @@ //! //! [`Quantikz`]: https://arxiv.org/pdf/1809.03842.pdf +mod circuit; +mod diagram; + use std::collections::{BTreeMap, HashMap, HashSet}; use std::fmt::Display; use std::str::FromStr; @@ -30,6 +33,14 @@ use crate::expression::Expression; use crate::instruction::{Gate, GateModifier, Instruction, Qubit}; use crate::Program; +use self::circuit::Circuit; + +// Overall intent: +// Iterate program gates -> LatexCircuit. This represents the program in a representation +// that can infallibly produce a valid diagram. +// Then Circuit -> Diagram + + /// Available commands used for building circuits with the same names taken /// from the Quantikz documentation for easy reference. LaTeX string denoted /// inside `backticks`. @@ -58,6 +69,7 @@ enum Command { Targ, } +// For Ryan: this should be std::fmt::Display impl ToString for Command { fn to_string(&self) -> String { match self { @@ -786,57 +798,13 @@ impl ToLatex for Program { /// let program = Program::from_str("CONTROLLED CNOT 2 1 0").expect(""); /// let latex = program.to_latex(RenderSettings::default()).expect(""); /// ``` + /// + /// Ryan: this should take &self, no need for latex generation to consume a program + /// esp if it fails to render fn to_latex(self, settings: RenderSettings) -> Result { - // 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 { - if let Qubit::Fixed(name) = qubit { - let wire = Wire { - ..Default::default() - }; - diagram.circuit.insert(*name, Box::new(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 { - // add implicit qubits to circuit - RenderSettings::impute_missing_qubits(instructions.len() as u32, &mut diagram.circuit); - } - - 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 { - diagram.parse_gate(&gate)?; - diagram.locate_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(), - }); - } - } - - let body = diagram.to_string(); - let document = Document { - body, - ..Default::default() - }; - Ok(document.to_string()) + let circuit = Circuit::try_from_program(&self, settings).unwrap(); // TODO + let diagram = diagram::Diagram::from(circuit); + Ok(diagram.to_string()) } } From 0b268ab9bff201c0a38860912a7e1f252adc4162 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Fri, 24 Mar 2023 11:59:56 -0700 Subject: [PATCH 73/75] feat: don't display memory reference index if 0 --- src/instruction.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/instruction.rs b/src/instruction.rs index e9171167..47e18f9a 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -333,7 +333,11 @@ impl Eq for MemoryReference {} impl fmt::Display for MemoryReference { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}[{}]", self.name, self.index) + if self.index == 0 { + write!(f, "{}", self.name) + } else { + write!(f, "{}[{}]", self.name, self.index) + } } } From 5a443777849ab32fef24ac51f804bdabadbb7bf6 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Fri, 24 Mar 2023 12:01:08 -0700 Subject: [PATCH 74/75] feat: parse and display Gate directly --- src/instruction.rs | 47 ++++++++++++++++++++++++++------------- src/parser/gate.rs | 12 +++++----- src/parser/instruction.rs | 4 ++-- src/parser/mod.rs | 2 +- 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/instruction.rs b/src/instruction.rs index 47e18f9a..3a6839a2 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -20,6 +20,7 @@ use std::str::FromStr; use std::{collections::HashMap, fmt}; use crate::expression::Expression; +use crate::parser::gate::parse_gate; use crate::parser::{common::parse_memory_reference, lex, ParseError}; use crate::program::{disallow_leftover, frame::FrameMatchCondition, SyntaxError}; @@ -361,6 +362,35 @@ pub struct Gate { pub modifiers: Vec, } +impl std::fmt::Display for Gate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let parameter_str = get_expression_parameter_string(&self.parameters); + + let qubit_str = format_qubits(&self.qubits); + let modifier_str = self + .modifiers + .iter() + .map(|m| format!("{} ", m)) + .collect::>() + .join(""); + write!( + f, + "{}{}{} {}", + modifier_str, self.name, parameter_str, qubit_str + ) + } +} + +impl FromStr for Gate { + type Err = SyntaxError; + + fn from_str(s: &str) -> Result { + let input = LocatedSpan::new(s); + let tokens = lex(input)?; + disallow_leftover(parse_gate(&tokens).map_err(ParseError::from_nom_internal_err)) + } +} + #[derive(Clone, Debug, PartialEq)] pub struct CircuitDefinition { pub name: String, @@ -837,21 +867,8 @@ impl fmt::Display for Instruction { .map(|(k, v)| format!("\n\t{}: {}", k, v)) .collect::() ), - Instruction::Gate(Gate { - name, - parameters, - qubits, - modifiers, - }) => { - let parameter_str = get_expression_parameter_string(parameters); - - let qubit_str = format_qubits(qubits); - let modifier_str = modifiers - .iter() - .map(|m| format!("{} ", m)) - .collect::>() - .join(""); - write!(f, "{}{}{} {}", modifier_str, name, parameter_str, qubit_str) + Instruction::Gate(gate) => { + write!(f, "{gate}") } Instruction::GateDefinition(GateDefinition { name, diff --git a/src/parser/gate.rs b/src/parser/gate.rs index cf63079a..e94ef83b 100644 --- a/src/parser/gate.rs +++ b/src/parser/gate.rs @@ -14,7 +14,7 @@ use nom::{combinator::opt, multi::many0, multi::separated_list0, sequence::delimited}; -use crate::{instruction::Instruction, token}; +use crate::token; use super::{ common::{self, parse_gate_modifier}, @@ -25,7 +25,7 @@ use crate::instruction::Gate; use crate::parser::InternalParserResult; /// Parse a gate instruction. -pub(crate) fn parse_gate<'a>(input: ParserInput<'a>) -> InternalParserResult<'a, Instruction> { +pub(crate) fn parse_gate<'a>(input: ParserInput<'a>) -> InternalParserResult<'a, Gate> { let (input, modifiers) = many0(parse_gate_modifier)(input)?; let (input, name) = token!(Identifier(v))(input)?; let (input, parameters) = opt(delimited( @@ -37,12 +37,12 @@ pub(crate) fn parse_gate<'a>(input: ParserInput<'a>) -> InternalParserResult<'a, let (input, qubits) = many0(common::parse_qubit)(input)?; Ok(( input, - Instruction::Gate(Gate { + Gate { name, parameters, qubits, modifiers, - }), + }, )) } @@ -58,11 +58,11 @@ mod test { test_modifiers, parse_gate, "DAGGER CONTROLLED RX(pi) 0 1", - Instruction::Gate(Gate { + Gate { name: "RX".to_string(), parameters: vec![Expression::PiConstant], qubits: vec![Qubit::Fixed(0), Qubit::Fixed(1)], modifiers: vec![GateModifier::Dagger, GateModifier::Controlled], - }) + } ); } diff --git a/src/parser/instruction.rs b/src/parser/instruction.rs index 3f72ab78..fa8bc832 100644 --- a/src/parser/instruction.rs +++ b/src/parser/instruction.rs @@ -13,7 +13,7 @@ // limitations under the License. use nom::{ - combinator::all_consuming, + combinator::{all_consuming, map}, multi::{many0, many1}, sequence::{delimited, preceded}, }; @@ -111,7 +111,7 @@ pub(crate) fn parse_instruction(input: ParserInput) -> InternalParserResult todo!(), }, - Some((Token::Identifier(_), _)) | Some((Token::Modifier(_), _)) => gate::parse_gate(input), + Some((Token::Identifier(_), _)) | Some((Token::Modifier(_), _)) => map(gate::parse_gate, Instruction::Gate)(input), Some((_, _)) => Err(nom::Err::Failure(InternalParseError::from_kind( &input[..1], ParserErrorKind::NotACommandOrGate, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 86778b09..f3f039e4 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -19,7 +19,7 @@ pub(crate) use instruction::parse_instructions; pub(crate) use lexer::lex; mod command; -mod gate; +pub(crate) mod gate; mod macros; pub(crate) mod common; From 1e07548482b294b34e730b93029753608cb9cd10 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Mon, 24 Jun 2024 14:32:24 -0700 Subject: [PATCH 75/75] chore: cleanup --- quil-rs/src/parser/gate.rs | 2 +- quil-rs/src/program/latex/circuit.rs | 36 +- quil-rs/src/program/latex/diagram.rs | 44 +- quil-rs/src/program/latex/mod.rs | 745 +-------------------------- quil-rs/src/waveform/templates.rs | 6 +- 5 files changed, 55 insertions(+), 778 deletions(-) diff --git a/quil-rs/src/parser/gate.rs b/quil-rs/src/parser/gate.rs index e94ef83b..061f24fa 100644 --- a/quil-rs/src/parser/gate.rs +++ b/quil-rs/src/parser/gate.rs @@ -50,7 +50,7 @@ pub(crate) fn parse_gate<'a>(input: ParserInput<'a>) -> InternalParserResult<'a, mod test { use super::parse_gate; use crate::expression::Expression; - use crate::instruction::{Gate, GateModifier, Instruction, Qubit}; + use crate::instruction::{Gate, GateModifier, Qubit}; use crate::make_test; use crate::parser::lexer::lex; diff --git a/quil-rs/src/program/latex/circuit.rs b/quil-rs/src/program/latex/circuit.rs index 084a0117..731e2ff4 100644 --- a/quil-rs/src/program/latex/circuit.rs +++ b/quil-rs/src/program/latex/circuit.rs @@ -27,7 +27,8 @@ pub(super) enum CircuitElement { } impl Gate { - // TODO: use Cow to prevent allocation when the gate is already in canonical form + /// A single gate can be represented in multiple ways. This function converts the gate to one canonical form + /// for easier processing. fn to_canonical_form(&self) -> Self { let mut new = self.clone(); @@ -39,12 +40,16 @@ impl Gate { let base = captures.name("base").unwrap().as_str(); new.name = base.to_string(); - new.modifiers.extend(vec![GateModifier::Controlled; count]); // TODO: also invert qubit order for these qubits. Yikes, Quil + new.modifiers.extend(vec![GateModifier::Controlled; count]); } new } + /// Remove the qubits from the gate which are used as its control qubits, and + /// return them as a set. + /// + /// Return an error if any control qubit is also a target qubit. fn extract_fixed_controls(&mut self) -> BuildCircuitResult> { let mut control_count = 0; @@ -67,17 +72,21 @@ impl Gate { }) .collect::, _>>()?; - self.qubits = new_qubits; - - for qubit in &self.qubits { - if let Qubit::Fixed(index) = qubit { - assert!(!controls.contains(index)) - } + if let Some(overlap) = controls + .iter() + .find(|control| new_qubits.contains(&Qubit::Fixed(**control))) + { + return Err(BuildCircuitError::ControlAndTargetQubitOverlap { + qubit: Qubit::Fixed(*overlap), + }); } + self.qubits = new_qubits; + Ok(controls) } + /// Remove the dagger modifiers from the gate and return their count. fn extract_dagger_count(&mut self) -> u64 { let mut count = 0; @@ -173,8 +182,11 @@ impl Circuit { #[derive(Debug, thiserror::Error)] #[non_exhaustive] +#[allow(clippy::enum_variant_names)] pub enum BuildCircuitError { - // TODO: use references with 'program lifetime + #[error("control qubit {} is also a target qubit", qubit.to_quil_or_debug())] + ControlAndTargetQubitOverlap { qubit: Qubit }, + #[error("cannot render gate as LaTeX: {gate:?}")] UnsupportedGate { gate: Gate }, @@ -192,8 +204,10 @@ impl Circuit { program: &crate::Program, settings: RenderSettings, ) -> BuildCircuitResult { - let mut circuit = Circuit::default(); - circuit.settings = settings; // TODO not ideal, reconsider how and when these should be used. Not all settings are inherent to the circuit's construction + let mut circuit = Circuit { + settings, // TODO not ideal, reconsider how and when these should be used. Not all settings are inherent to the circuit's construction + ..Default::default() + }; for instruction in &program.to_instructions() { let element = match instruction { diff --git a/quil-rs/src/program/latex/diagram.rs b/quil-rs/src/program/latex/diagram.rs index 9a6f6a80..8c134cd4 100644 --- a/quil-rs/src/program/latex/diagram.rs +++ b/quil-rs/src/program/latex/diagram.rs @@ -1,7 +1,4 @@ -use std::{ - collections::{BTreeMap, BTreeSet, HashSet}, - ops::RangeInclusive, -}; +use std::collections::{BTreeMap, BTreeSet, HashSet}; use super::{ circuit::{Circuit, CircuitElement}, @@ -44,38 +41,30 @@ macro_rules! write_many_delimited { /// readable. #[derive(Clone, Debug)] enum DiagramElement { - /// `\lstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the left. - /// Ryan: what's inside is not a string itself but more diagram elements + /// `\lstick{}`: Make a qubit "stick out" from the left. + /// + /// Example: `\lstick{\ket{q_{u32}}}` Lstick(Vec), - /// `\gate{name}`: Make a gate on the wire. + /// `\gate{}`: Make a gate on the wire. Gate(Vec), - /// `\phase{symbol}`: Make a phase on the wire with a rotation + /// `\phase{}`: Make a phase on the wire with a rotation Phase(Vec), - /// `^{\script}`: Add a superscript to a gate + /// `^{}`: Add a superscript to a gate Super(Vec), /// `\qw`: Connect the current cell to the previous cell i.e. "do nothing". Qw, /// `\\`: Start a new row Nr, - /// `\ctrl{wire}`: Make a control qubit--different from Control. - /// // Ryan: this makes more sense as an i64 since that's how it's used + /// `\ctrl{wire}`: Make a control qubit Ctrl { distance: i64, }, /// `\targ{}`: Make a controlled-not gate. Targ, - /// `\vqw{distance}`: Vertical quantum (single-line) wire - VerticalQuantumWire { - distance: i64, - }, - /// `&`: Start a new cell in the same row/wire Ampersand, - /// `\control{}` - Control, - /// `\meter{}` - Measurement Meter, @@ -169,9 +158,7 @@ impl std::fmt::Display for DiagramElement { DiagramElement::Nr => write!(f, r"\\"), DiagramElement::Ctrl { distance } => write!(f, r#"\ctrl{{{distance}}}"#), DiagramElement::Targ => write!(f, r"\targ{{}}"), - DiagramElement::VerticalQuantumWire { distance } => write!(f, r#"\vqw{{{distance}}}"#), DiagramElement::Ampersand => write!(f, "&"), - DiagramElement::Control => write!(f, r"\control{{}}"), DiagramElement::Meter => write!(f, r"\meter{{}}"), DiagramElement::Ket(contents) => write!(f, r"\ket{{{contents}}}"), DiagramElement::Text(string) => write!(f, r"\text{{{string}}}"), @@ -190,23 +177,16 @@ pub struct Diagram { } impl Diagram { - const DEFAULT_FOOTER: &str = r"\end{tikzcd} + const DEFAULT_FOOTER: &'static str = r"\end{tikzcd} \end{document}"; - const DEFAULT_HEADER: &str = r"\documentclass[convert={density=300,outext=.png}]{standalone} + const DEFAULT_HEADER: &'static str = r"\documentclass[convert={density=300,outext=.png}]{standalone} \usepackage[margin=1in]{geometry} \usepackage{tikz} \usetikzlibrary{quantikz} \begin{document} \begin{tikzcd}"; - fn extend_cell(&mut self, row: u64, column: u64, elements: Vec) { - self.wires - .entry(row) - .or_default() - .extend_at_column(column, elements); - } - fn push_to_cell(&mut self, row: u64, column: u64, element: DiagramElement) { self.wires .entry(row) @@ -361,10 +341,6 @@ struct DiagramWire { } impl DiagramWire { - fn extend_at_column(&mut self, column: u64, elements: Vec) { - self.elements.entry(column).or_default().extend(elements); - } - fn push_at_column(&mut self, column: u64, element: DiagramElement) { self.elements.entry(column).or_default().push(element); } diff --git a/quil-rs/src/program/latex/mod.rs b/quil-rs/src/program/latex/mod.rs index 53d3aef7..1b197519 100644 --- a/quil-rs/src/program/latex/mod.rs +++ b/quil-rs/src/program/latex/mod.rs @@ -25,81 +25,10 @@ mod circuit; mod diagram; -use std::collections::{BTreeMap, HashMap, HashSet}; -use std::fmt::Display; -use std::str::FromStr; - -use crate::expression::Expression; -use crate::instruction::{Gate, GateModifier, Instruction, Qubit}; -use crate::quil::Quil; use crate::Program; use self::circuit::Circuit; -// Overall intent: -// Iterate program gates -> LatexCircuit. This represents the program in a representation -// that can infallibly produce a valid diagram. -// Then Circuit -> Diagram - -/// 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)] -enum Command { - /// `\lstick{\ket{q_{u32}}}`: Make a qubit "stick out" from the left. - Lstick(String), - /// `\gate{name}`: Make a gate on the wire. - 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 - Nr, - /// `\ctrl{wire}`: Make a control qubit--different from Control. - Ctrl(String), - /// `\targ{}`: Make a controlled-not gate. - Targ, -} - -// For Ryan: this should be std::fmt::Display -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)] -enum Parameter { - /// Symbolic parameters - 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)] enum Symbol { @@ -111,15 +40,15 @@ enum Symbol { Text(String), } -impl ToString for Symbol { - fn to_string(&self) -> String { +impl std::fmt::Display for Symbol { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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}}}"#), + Symbol::Alpha => write!(f, r"\alpha"), + Symbol::Beta => write!(f, r"\beta"), + Symbol::Gamma => write!(f, r"\gamma"), + Symbol::Phi => write!(f, r"\phi"), + Symbol::Pi => write!(f, r"\pi"), + Symbol::Text(text) => write!(f, r#"\text{{{}}}"#, text), } } } @@ -137,33 +66,6 @@ impl From 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. -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 - None, -} - -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")), - "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)] @@ -186,574 +88,22 @@ impl Default for RenderSettings { /// Returns the default RenderSettings. fn default() -> Self { Self { - /// false: pi is π. + // true: pi is π. texify_numerical_constants: true, - /// true: `CNOT 0 2` would have three qubit lines: 0, 1, 2. + // true: `CNOT 0 2` would have three qubit lines: 0, 1, 2. impute_missing_qubits: false, - /// false: remove Lstick/Rstick from latex. + // false: remove Lstick/Rstick from latex. label_qubit_lines: true, - /// true: `RX(pi)` displayed as `X_{\\pi}` instead of `R_X(\\pi)`. + // true: `RX(pi)` displayed as `X_{\\pi}` instead of `R_X(\\pi)`. abbreviate_controlled_rotations: false, - /// 0: condenses the size of subdiagrams. + // 0: condenses the size of subdiagrams. qubit_line_open_wire_length: 1, - /// false: include Meter in the current column. + // 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, ToLatex}}; - /// 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>) { - // requires at least two qubits to impute missing qubits - if circuit.len() < 2 { - return; - } - - // get the first qubit in the BTreeMap - let first = circuit - .first_key_value() - .expect("previously checked that circuit is not empty") - .0 - + 1; - - // get the last qubit in the BTreeMap - 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 - 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, Command::Qw); - } - - 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. -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: String::new(), - footer: r"\end{tikzcd} -\end{document}" - .to_string(), - } - } -} - -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) - } -} - -/// 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, - /// 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>, -} - -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) { - '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); - } - } - } - } - } - - /// Utility function to insert modifiers of wires in this Circuit at the - /// current column. Returns an Err for unsupported modifiers. - /// - /// # Arguments - /// `wire` - an exposed wire on the Circuit - /// `column` - the current column of the Circuit - /// `modifiers` - the modifiers from the Gate - fn set_modifiers( - 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 for CONTROLLED and DAGGER - _ => { - wire.modifiers - .entry(*column) - .and_modify(|m| m.push(modifier.clone())) - .or_insert_with(|| vec![modifier.clone()]); - } - } - } - - 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. - /// - /// # 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()); - - // 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)?; - - // set parameters at this column for all qubits - for expression in &gate.parameters { - wire.set_param( - expression, - self.column, - self.settings.texify_numerical_constants, - ); - } - } - } - } - - // parse the gate to a canonical gate if supported - 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), - 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 - }; - - // get gate from new program of canonical instruction - if let Some(instruction) = instruction { - let program = - Program::from_str(&instruction).expect("should return program {instruction}"); - let instructions = program.body_instructions(); - - 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 - } else { - // 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)?; - } - } - } - } - - Ok(()) - } -} - -impl 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, "{}", Command::Lstick(key.to_string()).to_string())?; - } else { - // add qw buffer to first column - write!(f, "{}", Command::Qw.to_string())?; - } - - // 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::from(""); - // attach modifiers to gate name if any - if let Some(modifiers) = wire.modifiers.get(&c) { - for modifier in modifiers { - if let GateModifier::Dagger = modifier { - superscript.push_str( - &Command::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, "{}", &(Command::Ctrl(targ.to_string())).to_string())?; - } - 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() { - _gate.push_str(&superscript); - write!(f, "{}", &Command::Gate(_gate).to_string())?; - // else display X target as an open dot - } else { - write!(f, "{}", &Command::Targ.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::Phase(param.to_string()).to_string() - )?; - } - } - continue; - } - } - // all other gates display as `\gate{name}` - let mut _gate = gate.name.clone(); - - // concatenate superscripts - if !superscript.is_empty() { - _gate.push_str(&superscript); - } - - 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::Qw.to_string())?; - } - } - } - - // chain an empty column qw to the end of the line - write!(f, " & ")?; - 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::Nr.to_string())?; - 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 Modifiers on the wire callable by the column - modifiers: 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_quil_or_debug(), - }; - - // if texify_numerical_constants - let param = if texify { - // get the matching symbol from text - vec![Parameter::Symbol(text.into())] - } else { - // set the symbol as text - vec![Parameter::Symbol(Symbol::Text(text))] - }; - - 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)] pub enum LatexGenError { #[error("Found a target qubit with no control qubit.")] @@ -802,7 +152,7 @@ impl ToLatex for Program { /// Ryan: this should take &self, no need for latex generation to consume a program /// esp if it fails to render fn to_latex(self, settings: RenderSettings) -> Result { - let circuit = Circuit::try_from_program(&self, settings).unwrap(); // TODO + let circuit = Circuit::try_from_program(&self, settings).unwrap(); // TODO: error let diagram = diagram::Diagram::from(circuit); Ok(diagram.to_string()) } @@ -839,30 +189,12 @@ mod tests { /// Test module for the Document mod document { - use crate::program::latex::{tests::get_latex, Document, RenderSettings}; + use crate::program::latex::{tests::get_latex, RenderSettings}; #[test] fn test_template() { insta::assert_snapshot!(get_latex("", RenderSettings::default())); } - - #[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); - } } /// Test module for gates @@ -982,51 +314,6 @@ mod tests { } } - /// Test module for Quantikz Commands - mod commands { - use crate::program::latex::{Command, Symbol}; - - #[test] - fn test_command_left_ket() { - insta::assert_snapshot!(Command::Lstick("0".to_string()).to_string()); - } - - #[test] - fn test_command_gate() { - insta::assert_snapshot!(Command::Gate("X".to_string()).to_string()); - } - - #[test] - fn test_command_phase() { - insta::assert_snapshot!(Command::Phase(Symbol::Pi.to_string()).to_string()); - } - - #[test] - fn test_command_super() { - insta::assert_snapshot!(Command::Super("dagger".to_string()).to_string()); - } - - #[test] - fn test_command_qw() { - insta::assert_snapshot!(Command::Qw.to_string()); - } - - #[test] - fn test_command_nr() { - insta::assert_snapshot!(Command::Nr.to_string()); - } - - #[test] - fn test_command_control() { - insta::assert_snapshot!(Command::Ctrl("0".to_string()).to_string()); - } - - #[test] - fn test_command_cnot_target() { - insta::assert_snapshot!(Command::Targ.to_string()); - } - } - /// Test module for RenderSettings mod settings { use crate::program::latex::{tests::get_latex, RenderSettings}; diff --git a/quil-rs/src/waveform/templates.rs b/quil-rs/src/waveform/templates.rs index 0cc3b5a1..c6f49cc5 100644 --- a/quil-rs/src/waveform/templates.rs +++ b/quil-rs/src/waveform/templates.rs @@ -30,7 +30,7 @@ pub fn apply_phase_and_detuning( /// To handle accumulated floating point errors in sweeps above typical floating point imprecision /// we make epsilon 10x larger than floating point epsilon. fn ceiling_with_epsilon(value: f64) -> f64 { - let truncated = value - (value * 10.0 * std::f64::EPSILON); + let truncated = value - (value * 10.0 * f64::EPSILON); truncated.ceil() } @@ -297,8 +297,8 @@ mod tests { #[rstest::rstest] #[case(0.0, 0.0)] - #[case(0.0-std::f64::EPSILON, 0.0)] - #[case(0.0+std::f64::EPSILON, 1.0)] + #[case(0.0 - f64::EPSILON, 0.0)] + #[case(0.0 + f64::EPSILON, 1.0)] // Based on a past edge case #[case(8.800_000_000_000_001e-8 * 1.0e9, 88.0)] fn ceiling_with_epsilon(#[case] value: f64, #[case] expected: f64) {