From 0490b10629d03c9db713d4a7b138c7af61a84a25 Mon Sep 17 00:00:00 2001 From: filipw Date: Wed, 19 Nov 2025 22:29:25 +0100 Subject: [PATCH 1/7] pull in main QDK (post 1.22) --- Cargo.lock | 53 ++++++++++++++++++++++++++++------------------------- Cargo.toml | 6 ++++-- src/qasm.rs | 14 ++++++-------- 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49db768..1367b6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ dependencies = [ [[package]] name = "allocator" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "mimalloc-sys", ] @@ -523,7 +523,7 @@ checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "library" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" [[package]] name = "linux-raw-sys" @@ -581,7 +581,7 @@ dependencies = [ [[package]] name = "mimalloc-sys" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "cc", ] @@ -719,7 +719,7 @@ dependencies = [ [[package]] name = "qdk_simulators" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "quantum-sparse-sim", ] @@ -727,7 +727,7 @@ dependencies = [ [[package]] name = "qsc" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "allocator", "env_logger", @@ -760,7 +760,7 @@ dependencies = [ [[package]] name = "qsc_ast" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "indenter", "miette", @@ -771,7 +771,7 @@ dependencies = [ [[package]] name = "qsc_circuit" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "log", "miette", @@ -783,6 +783,7 @@ dependencies = [ "qsc_fir", "qsc_frontend", "qsc_hir", + "qsc_lowerer", "regex-lite", "rustc-hash 1.1.0", "serde", @@ -793,7 +794,7 @@ dependencies = [ [[package]] name = "qsc_codegen" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "num-bigint", "num-complex", @@ -814,7 +815,7 @@ dependencies = [ [[package]] name = "qsc_data_structures" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "bitflags", "miette", @@ -825,7 +826,7 @@ dependencies = [ [[package]] name = "qsc_doc_gen" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "qsc_ast", "qsc_data_structures", @@ -838,7 +839,7 @@ dependencies = [ [[package]] name = "qsc_eval" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "miette", "ndarray", @@ -858,7 +859,7 @@ dependencies = [ [[package]] name = "qsc_fir" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "indenter", "num-bigint", @@ -869,7 +870,7 @@ dependencies = [ [[package]] name = "qsc_formatter" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "clap", "qsc_data_structures", @@ -879,7 +880,7 @@ dependencies = [ [[package]] name = "qsc_frontend" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "library", "miette", @@ -894,7 +895,7 @@ dependencies = [ [[package]] name = "qsc_hir" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "indenter", "num-bigint", @@ -905,7 +906,7 @@ dependencies = [ [[package]] name = "qsc_linter" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "miette", "qsc_ast", @@ -921,7 +922,7 @@ dependencies = [ [[package]] name = "qsc_lowerer" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "qsc_data_structures", "qsc_fir", @@ -931,7 +932,7 @@ dependencies = [ [[package]] name = "qsc_parse" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "bitflags", "enum-iterator", @@ -946,7 +947,7 @@ dependencies = [ [[package]] name = "qsc_partial_eval" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "miette", "num-bigint", @@ -964,7 +965,7 @@ dependencies = [ [[package]] name = "qsc_passes" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "miette", "qsc_data_structures", @@ -981,7 +982,7 @@ dependencies = [ [[package]] name = "qsc_project" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "async-trait", "futures", @@ -1002,7 +1003,7 @@ dependencies = [ [[package]] name = "qsc_qasm" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "bitflags", "enum-iterator", @@ -1023,7 +1024,7 @@ dependencies = [ [[package]] name = "qsc_rca" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "bitflags", "indenter", @@ -1039,7 +1040,7 @@ dependencies = [ [[package]] name = "qsc_rir" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "indenter", "qsc_data_structures", @@ -1053,6 +1054,8 @@ dependencies = [ "num-bigint", "num-complex", "qsc", + "qsc_circuit", + "qsc_eval", "resource_estimator", "serde", "serde_json", @@ -1170,7 +1173,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "resource_estimator" version = "0.0.0" -source = "git+https://github.com/microsoft/qdk.git?tag=v1.22.0#2095df676ba8c5a504dd2fd35eb149c8872cbd9f" +source = "git+https://github.com/microsoft/qdk.git?branch=main#c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa" dependencies = [ "fasteval", "miette", diff --git a/Cargo.toml b/Cargo.toml index e712b65..4cf72e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,10 @@ edition = "2024" crate-type = ["lib", "cdylib", "staticlib"] [dependencies] -qsc = { git = "https://github.com/microsoft/qdk.git", tag = "v1.22.0" } -resource_estimator = { git = "https://github.com/microsoft/qdk.git", tag = "v1.22.0" } +qsc = { git = "https://github.com/microsoft/qdk.git", branch = "main" } +qsc_eval = { git = "https://github.com/microsoft/qdk.git", branch = "main" } +qsc_circuit = { git = "https://github.com/microsoft/qdk.git", branch = "main" } +resource_estimator = { git = "https://github.com/microsoft/qdk.git", branch = "main" } thiserror = "1.0" num-bigint = "0.4.5" num-complex = "0.4" diff --git a/src/qasm.rs b/src/qasm.rs index 8101b99..2dc2eae 100644 --- a/src/qasm.rs +++ b/src/qasm.rs @@ -85,8 +85,6 @@ impl Qasm2Backend { } impl Backend for Qasm2Backend { - type ResultType = bool; - fn qubit_allocate(&mut self) -> usize { let q = self.next_qubit_id; self.next_qubit_id += 1; @@ -160,31 +158,31 @@ impl Backend for Qasm2Backend { .push(format!("ccx q[{}], q[{}], q[{}];", ctl0, ctl1, q)); } - fn m(&mut self, q: usize) -> Self::ResultType { + fn m(&mut self, q: usize) -> qsc_eval::val::Result { let c = self.cbit_counter; self.cbit_counter += 1; self.code.push(format!("measure q[{}] -> c[{}];", q, c)); - false + qsc_eval::val::Result::Val(false) } - fn mresetz(&mut self, q: usize) -> Self::ResultType { + fn mresetz(&mut self, q: usize) -> qsc_eval::val::Result { match self.generation_options.reset_behavior { QasmResetBehavior::Supported => { let c = self.cbit_counter; self.cbit_counter += 1; self.code.push(format!("measure q[{}] -> c[{}];", q, c)); self.code.push(format!("reset q[{}];", q)); - false + qsc_eval::val::Result::Val(false) } QasmResetBehavior::Ignored => { let c = self.cbit_counter; self.cbit_counter += 1; self.code.push(format!("measure q[{}] -> c[{}];", q, c)); - false + qsc_eval::val::Result::Val(false) } QasmResetBehavior::Error => { self.errors.push("Reset is not supported".to_string()); - false + qsc_eval::val::Result::Val(false) } } } From 060597160745f0c99f143ca9abb46dd6cb987e78 Mon Sep 17 00:00:00 2001 From: filipw Date: Wed, 19 Nov 2025 22:37:02 +0100 Subject: [PATCH 2/7] added support for quantikz (no FFI yet) --- Cargo.lock | 17 +++ Cargo.toml | 1 + src/lib.rs | 1 + src/quantikz.rs | 275 ++++++++++++++++++++++++++++++++++++++++ tests/tests_quantikz.rs | 271 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 565 insertions(+) create mode 100644 src/quantikz.rs create mode 100644 tests/tests_quantikz.rs diff --git a/Cargo.lock b/Cargo.lock index 1367b6b..0c75178 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,6 +253,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + [[package]] name = "enum-iterator" version = "2.1.0" @@ -312,6 +318,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "expect-test" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63af43ff4431e848fb47472a920f14fa71c24de13255a5692e93d4e90302acb0" +dependencies = [ + "dissimilar", + "once_cell", +] + [[package]] name = "fasteval" version = "0.2.4" @@ -1051,6 +1067,7 @@ dependencies = [ name = "qsharp-bridge" version = "0.2.0" dependencies = [ + "expect-test", "num-bigint", "num-complex", "qsc", diff --git a/Cargo.toml b/Cargo.toml index 4cf72e6..ad02f06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ thiserror = "1.0" num-bigint = "0.4.5" num-complex = "0.4" uniffi = { version = "0.29.4", features=["build"] } +expect-test = "1.4" [dev-dependencies] serde = { version = "1.0", features = ["derive"] } diff --git a/src/lib.rs b/src/lib.rs index 79844ea..42441f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,3 +19,4 @@ use crate::sim::run_qs_with_options; pub mod noise; pub mod qasm; pub mod sim; +pub mod quantikz; \ No newline at end of file diff --git a/src/quantikz.rs b/src/quantikz.rs new file mode 100644 index 0000000..f9e0f61 --- /dev/null +++ b/src/quantikz.rs @@ -0,0 +1,275 @@ +use qsc_circuit::{Circuit, Operation}; +use std::collections::{HashMap, HashSet}; + +type RegisterMap = HashMap<(usize, Option), usize>; + +#[derive(Clone)] +struct Row { + label: Option, + is_classical: bool, +} + +pub fn circuit_to_quantikz(c: &Circuit) -> String { + let (mut rows, register_to_row) = build_rows(c); + let col_count = c.component_grid.len(); + let mut table = initialize_table(rows.len(), col_count, &rows); + + populate_table(c, ®ister_to_row, &mut table, &mut rows); + + render_latex(&rows, &table) +} + +fn build_rows(c: &Circuit) -> (Vec, RegisterMap) { + let mut rows: Vec = Vec::new(); + let mut register_to_row = HashMap::new(); + let qubits_with_gap_row_below = identify_gaps(c); + + for q in &c.qubits { + let label = format!("\\lstick{{\\ket{{0}}_{{{}}}}}", q.id); + register_to_row.insert((q.id, None), rows.len()); + rows.push(Row { + label: Some(label), + is_classical: false, + }); + + let extra_rows = if qubits_with_gap_row_below.contains(&q.id) { + std::cmp::max(1, q.num_results) + } else { + q.num_results + }; + + for i in 0..extra_rows { + register_to_row.insert((q.id, Some(i)), rows.len()); + rows.push(Row { + label: None, + is_classical: true, + }); + } + } + (rows, register_to_row) +} + +fn identify_gaps(c: &Circuit) -> HashSet { + let mut gaps = HashSet::new(); + for col in &c.component_grid { + for op in &col.components { + let targets = match op { + Operation::Measurement(m) => &m.qubits, + Operation::Unitary(u) => &u.targets, + Operation::Ket(k) => &k.targets, + }; + for target in targets { + let q = target.qubit; + if gaps.contains(&q) { + continue; + } + let next_q = q + 1; + if targets.iter().any(|t| t.qubit == next_q) { + gaps.insert(q); + } + } + } + } + gaps +} + +fn initialize_table(row_count: usize, col_count: usize, rows: &[Row]) -> Vec> { + let mut table = vec![vec![String::new(); col_count + 1]; row_count]; + for (r_idx, row) in rows.iter().enumerate() { + for c_idx in 0..=col_count { + table[r_idx][c_idx] = if row.is_classical { + String::from("\\cw") + } else { + String::from("\\qw") + }; + } + } + table +} + +fn populate_table( + c: &Circuit, + register_to_row: &RegisterMap, + table: &mut [Vec], + rows: &mut [Row], +) { + for (col_index, col) in c.component_grid.iter().enumerate() { + let table_col = col_index; + for op in &col.components { + let targets = get_rows_for_operation(op, register_to_row, true); + let controls = get_rows_for_operation(op, register_to_row, false); + + process_operation(op, table_col, &targets, &controls, table, rows); + } + } +} + +fn process_operation( + op: &Operation, + col: usize, + targets: &[usize], + controls: &[usize], + table: &mut [Vec], + rows: &mut [Row], +) { + match op { + Operation::Unitary(u) => { + process_unitary( + &u.gate, + &op.args(), + u.is_adjoint, + col, + targets, + controls, + table, + ); + } + Operation::Measurement(_) => { + for &t in targets { + table[t][col] = String::from("\\meter{}"); + rows[t].is_classical = true; + for next_c in (col + 1)..table[t].len() { + table[t][next_c] = String::from("\\cw"); + } + } + } + Operation::Ket(_) => { + for &t in targets { + table[t][col] = String::from("\\gate{\\ket{0}}"); + rows[t].is_classical = false; + for next_c in (col + 1)..table[t].len() { + table[t][next_c] = String::from("\\qw"); + } + } + } + } +} + +fn process_unitary( + name: &str, + args: &[String], + is_adjoint: bool, + col: usize, + targets: &[usize], + controls: &[usize], + table: &mut [Vec], +) { + let simple_name = name.split('.').last().unwrap_or(name); + let is_swap = simple_name.eq_ignore_ascii_case("swap"); + + if is_swap && targets.len() == 2 { + let t1 = targets[0]; + let t2 = targets[1]; + let offset = t2 as isize - t1 as isize; + table[t1][col] = format!("\\swap{{{}}}", offset); + table[t2][col] = String::from("\\targX{}"); + + for &ctrl in controls { + let offset = t1 as isize - ctrl as isize; + table[ctrl][col] = format!("\\ctrl{{{}}}", offset); + } + return; + } + + if (simple_name == "X" || simple_name == "CNOT") && !controls.is_empty() { + for &t in targets { + table[t][col] = String::from("\\targ{}"); + } + } else if simple_name == "Z" && !controls.is_empty() { + for &t in targets { + let ctrl_ref = controls[0]; + let diff = t as isize - ctrl_ref as isize; + table[t][col] = format!("\\ctrl{{{}}}", -diff); + } + } else { + let label = operation_label(simple_name, args, is_adjoint); + if targets.len() > 1 { + let first_row = targets[0]; + table[first_row][col] = format!("\\gate[wires={}]{{{}}}", targets.len(), label); + } else if targets.len() == 1 { + let r = targets[0]; + table[r][col] = format!("\\gate{{{}}}", label); + } + } + + if !controls.is_empty() { + for &ctrl in controls { + let target = targets.get(0).copied().unwrap_or(ctrl); + let offset = target as isize - ctrl as isize; + table[ctrl][col] = format!("\\ctrl{{{}}}", offset); + } + } +} + +fn render_latex(rows: &[Row], table: &[Vec]) -> String { + let mut out = String::new(); + out.push_str("\\begin{quantikz}\n"); + for (row_idx, row) in rows.iter().enumerate() { + if let Some(l) = &row.label { + out.push_str(l); + } + out.push_str(" & "); + out.push_str(&table[row_idx].join(" & ")); + out.push_str(" \\\\\n"); + } + out.push_str("\\end{quantikz}\n"); + out +} + +fn operation_label(name: &str, args: &[String], is_adjoint: bool) -> String { + let mut lbl = match name { + "Rx" => "R_x".to_string(), + "Ry" => "R_y".to_string(), + "Rz" => "R_z".to_string(), + "R1" => "R_1".to_string(), + n => n.to_string(), + }; + + if is_adjoint { + lbl.push_str("^\\dagger"); + } + + if !args.is_empty() { + let joined = args.join(", "); + lbl = format!("{}({})", lbl, joined); + } + lbl +} + +fn get_rows_for_operation( + op: &Operation, + register_to_row: &RegisterMap, + is_target: bool, +) -> Vec { + let registers = match op { + Operation::Measurement(m) => { + if is_target { + &m.results + } else { + &m.qubits + } + } + Operation::Unitary(u) => { + if is_target { + &u.targets + } else { + &u.controls + } + } + Operation::Ket(k) => { + if is_target { + &k.targets + } else { + &vec![] + } + } + }; + + let mut rows: Vec = registers + .iter() + .filter_map(|reg| register_to_row.get(&(reg.qubit, reg.result)).copied()) + .collect(); + + rows.sort(); + rows +} diff --git a/tests/tests_quantikz.rs b/tests/tests_quantikz.rs new file mode 100644 index 0000000..4145bc9 --- /dev/null +++ b/tests/tests_quantikz.rs @@ -0,0 +1,271 @@ +use qsc::{ + LanguageFeatures, PackageType, SourceMap, interpret::{CircuitEntryPoint, CircuitGenerationMethod, Interpreter}, target::Profile +}; +use expect_test::expect; +use qsc_circuit::TracerConfig; +use qsharp_bridge::quantikz; + +fn interpreter_with_circuit_trace(code: &str, profile: Profile) -> Interpreter { + let sources = SourceMap::new([("test.qs".into(), code.into())], None); + let (std_id, store) = qsc::compile::package_store_with_stdlib(profile.into()); + Interpreter::with_circuit_trace( + sources, + PackageType::Exe, + profile.into(), + LanguageFeatures::default(), + store, + &[(std_id, None)], + Default::default(), + ) + .expect("interpreter creation should succeed") +} + +fn get_quantikz( + interpreter: &mut Interpreter, + entry: CircuitEntryPoint, + method: CircuitGenerationMethod, + config: TracerConfig, +) -> String { + let circuit = interpreter + .circuit(entry, method, config) + .expect("circuit generation should succeed"); + + quantikz::circuit_to_quantikz(&circuit) +} + +#[test] +fn quantikz_one_gate() { + let mut interpreter = interpreter_with_circuit_trace( + r" + namespace Test { + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + H(q); + M(q); + } + } + ", + Profile::Unrestricted, + ); + + let tex = get_quantikz( + &mut interpreter, + CircuitEntryPoint::EntryPoint, + CircuitGenerationMethod::ClassicalEval, + TracerConfig::default(), + ); + + expect![[r#" + \begin{quantikz} + \lstick{\ket{0}_{0}} & \gate{H} & \qw & \qw \\ + & \cw & \meter{} & \cw \\ + \end{quantikz} + "#]] + .assert_eq(&tex); +} + +#[test] +fn quantikz_toffoli() { + let mut interpreter = interpreter_with_circuit_trace( + r" + namespace Test { + @EntryPoint() + operation Main() : Unit { + use q = Qubit[3]; + CCNOT(q[0], q[1], q[2]); + } + } + ", + Profile::Unrestricted, + ); + + let tex = get_quantikz( + &mut interpreter, + CircuitEntryPoint::EntryPoint, + CircuitGenerationMethod::ClassicalEval, + TracerConfig::default(), + ); + + expect![[r#" + \begin{quantikz} + \lstick{\ket{0}_{0}} & \ctrl{2} & \qw \\ + \lstick{\ket{0}_{1}} & \ctrl{1} & \qw \\ + \lstick{\ket{0}_{2}} & \targ{} & \qw \\ + \end{quantikz} + "#]] + .assert_eq(&tex); +} + +#[test] +fn quantikz_swap_gate() { + let mut interpreter = interpreter_with_circuit_trace( + r" + namespace Test { + @EntryPoint() + operation Main() : Unit { + use q = Qubit[2]; + SWAP(q[0], q[1]); + } + } + ", + Profile::Unrestricted, + ); + + let tex = get_quantikz( + &mut interpreter, + CircuitEntryPoint::EntryPoint, + CircuitGenerationMethod::ClassicalEval, + TracerConfig::default(), + ); + + expect![[r#" + \begin{quantikz} + \lstick{\ket{0}_{0}} & \swap{2} & \qw \\ + & \cw & \cw \\ + \lstick{\ket{0}_{1}} & \targX{} & \qw \\ + \end{quantikz} + "#]] + .assert_eq(&tex); +} + +#[test] +fn quantikz_complex_sample() { + let mut interpreter = interpreter_with_circuit_trace( + r#" + namespace Test { + @EntryPoint() + operation Main() : Unit { + use (q0, q1, q2) = (Qubit(), Qubit(), Qubit()); + H(q2); + Controlled S([q1], q2); + Controlled T([q0], q2); + H(q1); + Controlled S([q0], q1); + H(q0); + SWAP(q0, q2); + let r0 = M(q0); + let r1 = M(q1); + let r2 = M(q2); + } + } + "#, + Profile::Unrestricted, + ); + + let tex = get_quantikz( + &mut interpreter, + CircuitEntryPoint::EntryPoint, + CircuitGenerationMethod::ClassicalEval, + TracerConfig::default(), + ); + + expect![[r#" + \begin{quantikz} + \lstick{\ket{0}_{0}} & \gate{R_z(0.3927)} & \qw & \qw & \qw & \qw & \qw & \ctrl{4} & \qw & \ctrl{4} & \gate{T} & \qw & \ctrl{2} & \qw & \ctrl{2} & \gate{H} & \swap{4} & \qw & \qw \\ + & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw \\ + \lstick{\ket{0}_{1}} & \gate{T} & \qw & \ctrl{2} & \qw & \ctrl{2} & \qw & \qw & \qw & \qw & \gate{H} & \gate{T} & \targ{} & \gate{T^\dagger} & \targ{} & \qw & \qw & \qw & \qw \\ + & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw \\ + \lstick{\ket{0}_{2}} & \gate{H} & \gate{T} & \targ{} & \gate{T^\dagger} & \targ{} & \gate{R_z(0.3927)} & \targ{} & \gate{R_z(-0.3927)} & \targ{} & \qw & \qw & \qw & \qw & \qw & \qw & \targX{} & \qw & \qw \\ + & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw \\ + \end{quantikz} + "#]].assert_eq(&tex); +} + +#[test] +fn quantikz_rotation_circuit() { + let mut interpreter = interpreter_with_circuit_trace( + r#" + namespace Test { + open Microsoft.Quantum.Math; + @EntryPoint() + operation Main() : Unit { + use (q0, q1) = (Qubit(), Qubit()); + X(q0); + X(q1); + H(q0); + CNOT(q0, q1); + Rz(2.0 * PI() / 3.0, q1); + H(q0); + H(q1); + M(q0); + M(q1); + } + } + "#, + Profile::Unrestricted, + ); + + let tex = get_quantikz( + &mut interpreter, + CircuitEntryPoint::EntryPoint, + CircuitGenerationMethod::ClassicalEval, + TracerConfig::default(), + ); + + expect![[r#" + \begin{quantikz} + \lstick{\ket{0}_{0}} & \gate{X} & \gate{H} & \ctrl{2} & \gate{H} & \qw & \qw & \qw \\ + & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ + \lstick{\ket{0}_{1}} & \gate{X} & \qw & \targ{} & \gate{R_z(2.0944)} & \gate{H} & \qw & \qw \\ + & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw \\ + \end{quantikz} + "#]] + .assert_eq(&tex); +} + +#[test] +fn quantikz_cat_state() { + let mut interpreter = interpreter_with_circuit_trace( + r" + namespace Test { + import Std.Measurement.*; + @EntryPoint() + operation Main() : Result[] { + use qubits = Qubit[8]; + + // apply Hadamard gate to the first qubit + H(qubits[0]); + + // apply CNOT gates to create entanglement + for qubit in qubits[1..Length(qubits) - 1] { + CNOT(qubits[0], qubit); + } + + // return measurement results + MResetEachZ(qubits) + } + } + ", + Profile::Unrestricted, + ); + + let tex = get_quantikz( + &mut interpreter, + CircuitEntryPoint::EntryPoint, + CircuitGenerationMethod::ClassicalEval, + TracerConfig::default(), + ); + + expect![[r#" + \begin{quantikz} + \lstick{\ket{0}_{0}} & \gate{H} & \ctrl{2} & \ctrl{4} & \ctrl{6} & \ctrl{8} & \ctrl{10} & \ctrl{12} & \ctrl{14} & \qw & \gate{\ket{0}} & \qw \\ + & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ + \lstick{\ket{0}_{1}} & \qw & \targ{} & \qw & \qw & \qw & \qw & \qw & \qw & \qw & \gate{\ket{0}} & \qw \\ + & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ + \lstick{\ket{0}_{2}} & \qw & \qw & \targ{} & \qw & \qw & \qw & \qw & \qw & \qw & \gate{\ket{0}} & \qw \\ + & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ + \lstick{\ket{0}_{3}} & \qw & \qw & \qw & \targ{} & \qw & \qw & \qw & \qw & \qw & \gate{\ket{0}} & \qw \\ + & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ + \lstick{\ket{0}_{4}} & \qw & \qw & \qw & \qw & \targ{} & \qw & \qw & \qw & \qw & \gate{\ket{0}} & \qw \\ + & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ + \lstick{\ket{0}_{5}} & \qw & \qw & \qw & \qw & \qw & \targ{} & \qw & \qw & \qw & \gate{\ket{0}} & \qw \\ + & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ + \lstick{\ket{0}_{6}} & \qw & \qw & \qw & \qw & \qw & \qw & \targ{} & \qw & \qw & \gate{\ket{0}} & \qw \\ + & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ + \lstick{\ket{0}_{7}} & \qw & \qw & \qw & \qw & \qw & \qw & \qw & \targ{} & \qw & \gate{\ket{0}} & \qw \\ + & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ + \end{quantikz} + "#]] + .assert_eq(&tex); +} \ No newline at end of file From 48090f38aa12ec726f5f0b225b0fbd2b3069e59a Mon Sep 17 00:00:00 2001 From: filipw Date: Thu, 20 Nov 2025 07:23:20 +0100 Subject: [PATCH 3/7] now with FFI --- src/lib.rs | 1 + src/qsharp-bridge.udl | 3 + src/quantikz.rs | 37 ++++++++++++- tests/tests_quantikz.rs | 118 +++++++--------------------------------- 4 files changed, 59 insertions(+), 100 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 42441f5..7d9d0de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ use crate::sim::estimate_expression; use crate::sim::qir; use crate::sim::run_qs; use crate::sim::run_qs_with_options; +use crate::quantikz::quantikz; pub mod noise; pub mod qasm; diff --git a/src/qsharp-bridge.udl b/src/qsharp-bridge.udl index deb0504..2f5e93f 100644 --- a/src/qsharp-bridge.udl +++ b/src/qsharp-bridge.udl @@ -15,6 +15,9 @@ namespace qsharp_bridge { [Throws=QsError] string qasm2_expression([ByRef]string expression, QasmGenerationOptions generation_options); + [Throws=QsError] + string quantikz([ByRef]string source); + [Throws=QsError] string estimate([ByRef]string source, string? job_params); diff --git a/src/quantikz.rs b/src/quantikz.rs index f9e0f61..987aaf3 100644 --- a/src/quantikz.rs +++ b/src/quantikz.rs @@ -1,6 +1,12 @@ -use qsc_circuit::{Circuit, Operation}; +use qsc::{ + LanguageFeatures, PackageType, SourceMap, + interpret::{CircuitEntryPoint, CircuitGenerationMethod, Interpreter}, target::Profile, +}; +use qsc_circuit::{Circuit, Operation, TracerConfig}; use std::collections::{HashMap, HashSet}; +use crate::sim::QsError; + type RegisterMap = HashMap<(usize, Option), usize>; #[derive(Clone)] @@ -9,6 +15,35 @@ struct Row { is_classical: bool, } +pub fn quantikz(code: &str) -> Result { + let sources = SourceMap::new([("test.qs".into(), code.into())], None); + let (std_id, store) = qsc::compile::package_store_with_stdlib(Profile::Unrestricted.into()); + + let mut interpreter = match Interpreter::with_circuit_trace( + sources, + PackageType::Exe, + Profile::Unrestricted.into(), + LanguageFeatures::default(), + store, + &[(std_id, None)], + Default::default(), + ) { + Ok(interpreter) => interpreter, + Err(errors) => { + return Err(errors.into()); + } + }; + + let circuit = interpreter + .circuit( + CircuitEntryPoint::EntryPoint, + CircuitGenerationMethod::ClassicalEval, + TracerConfig::default(), + )?; + + Ok(circuit_to_quantikz(&circuit)) +} + pub fn circuit_to_quantikz(c: &Circuit) -> String { let (mut rows, register_to_row) = build_rows(c); let col_count = c.component_grid.len(); diff --git a/tests/tests_quantikz.rs b/tests/tests_quantikz.rs index 4145bc9..09d6d86 100644 --- a/tests/tests_quantikz.rs +++ b/tests/tests_quantikz.rs @@ -1,41 +1,9 @@ -use qsc::{ - LanguageFeatures, PackageType, SourceMap, interpret::{CircuitEntryPoint, CircuitGenerationMethod, Interpreter}, target::Profile -}; use expect_test::expect; -use qsc_circuit::TracerConfig; -use qsharp_bridge::quantikz; - -fn interpreter_with_circuit_trace(code: &str, profile: Profile) -> Interpreter { - let sources = SourceMap::new([("test.qs".into(), code.into())], None); - let (std_id, store) = qsc::compile::package_store_with_stdlib(profile.into()); - Interpreter::with_circuit_trace( - sources, - PackageType::Exe, - profile.into(), - LanguageFeatures::default(), - store, - &[(std_id, None)], - Default::default(), - ) - .expect("interpreter creation should succeed") -} - -fn get_quantikz( - interpreter: &mut Interpreter, - entry: CircuitEntryPoint, - method: CircuitGenerationMethod, - config: TracerConfig, -) -> String { - let circuit = interpreter - .circuit(entry, method, config) - .expect("circuit generation should succeed"); - - quantikz::circuit_to_quantikz(&circuit) -} +use qsharp_bridge::quantikz::quantikz; #[test] fn quantikz_one_gate() { - let mut interpreter = interpreter_with_circuit_trace( + let tex = quantikz( r" namespace Test { @EntryPoint() @@ -45,16 +13,8 @@ fn quantikz_one_gate() { M(q); } } - ", - Profile::Unrestricted, - ); - - let tex = get_quantikz( - &mut interpreter, - CircuitEntryPoint::EntryPoint, - CircuitGenerationMethod::ClassicalEval, - TracerConfig::default(), - ); + " + ).expect("quantikz generation should succeed"); expect![[r#" \begin{quantikz} @@ -67,7 +27,7 @@ fn quantikz_one_gate() { #[test] fn quantikz_toffoli() { - let mut interpreter = interpreter_with_circuit_trace( + let tex = quantikz( r" namespace Test { @EntryPoint() @@ -76,16 +36,8 @@ fn quantikz_toffoli() { CCNOT(q[0], q[1], q[2]); } } - ", - Profile::Unrestricted, - ); - - let tex = get_quantikz( - &mut interpreter, - CircuitEntryPoint::EntryPoint, - CircuitGenerationMethod::ClassicalEval, - TracerConfig::default(), - ); + " + ).expect("quantikz generation should succeed"); expect![[r#" \begin{quantikz} @@ -99,7 +51,7 @@ fn quantikz_toffoli() { #[test] fn quantikz_swap_gate() { - let mut interpreter = interpreter_with_circuit_trace( + let tex = quantikz( r" namespace Test { @EntryPoint() @@ -108,16 +60,8 @@ fn quantikz_swap_gate() { SWAP(q[0], q[1]); } } - ", - Profile::Unrestricted, - ); - - let tex = get_quantikz( - &mut interpreter, - CircuitEntryPoint::EntryPoint, - CircuitGenerationMethod::ClassicalEval, - TracerConfig::default(), - ); + " + ).expect("quantikz generation should succeed"); expect![[r#" \begin{quantikz} @@ -131,7 +75,7 @@ fn quantikz_swap_gate() { #[test] fn quantikz_complex_sample() { - let mut interpreter = interpreter_with_circuit_trace( + let tex = quantikz( r#" namespace Test { @EntryPoint() @@ -149,16 +93,8 @@ fn quantikz_complex_sample() { let r2 = M(q2); } } - "#, - Profile::Unrestricted, - ); - - let tex = get_quantikz( - &mut interpreter, - CircuitEntryPoint::EntryPoint, - CircuitGenerationMethod::ClassicalEval, - TracerConfig::default(), - ); + "# + ).expect("quantikz generation should succeed"); expect![[r#" \begin{quantikz} @@ -174,7 +110,7 @@ fn quantikz_complex_sample() { #[test] fn quantikz_rotation_circuit() { - let mut interpreter = interpreter_with_circuit_trace( + let tex = quantikz( r#" namespace Test { open Microsoft.Quantum.Math; @@ -192,16 +128,8 @@ fn quantikz_rotation_circuit() { M(q1); } } - "#, - Profile::Unrestricted, - ); - - let tex = get_quantikz( - &mut interpreter, - CircuitEntryPoint::EntryPoint, - CircuitGenerationMethod::ClassicalEval, - TracerConfig::default(), - ); + "# + ).expect("quantikz generation should succeed"); expect![[r#" \begin{quantikz} @@ -216,7 +144,7 @@ fn quantikz_rotation_circuit() { #[test] fn quantikz_cat_state() { - let mut interpreter = interpreter_with_circuit_trace( + let tex = quantikz( r" namespace Test { import Std.Measurement.*; @@ -236,16 +164,8 @@ fn quantikz_cat_state() { MResetEachZ(qubits) } } - ", - Profile::Unrestricted, - ); - - let tex = get_quantikz( - &mut interpreter, - CircuitEntryPoint::EntryPoint, - CircuitGenerationMethod::ClassicalEval, - TracerConfig::default(), - ); + " + ).expect("quantikz generation should succeed"); expect![[r#" \begin{quantikz} From 97e25c51e91b2b4dd77e8ee7697828b35e27e690 Mon Sep 17 00:00:00 2001 From: filipw Date: Thu, 20 Nov 2025 16:47:34 +0100 Subject: [PATCH 4/7] fixed visualizations and added jupyter sample for quantikz --- .gitignore | 5 +- examples/python/jupyter/quantikz.ipynb | 95 ++++++++++++++++++++++++ examples/python/jupyter/requirements.txt | 3 +- src/quantikz.rs | 68 ++++++----------- tests/tests_quantikz.rs | 87 +++++++++++++--------- 5 files changed, 175 insertions(+), 83 deletions(-) create mode 100644 examples/python/jupyter/quantikz.ipynb diff --git a/.gitignore b/.gitignore index cef5f09..cd4e08e 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,7 @@ platforms/swift/Qsharp.Bridge/Libs/qsharp_bridge_framework.xcframework platforms/swift/Qsharp.Bridge/.swiftpm/xcode/xcuserdata/filipw.xcuserdatad/xcschemes/xcschememanagement.plist platforms/swift/Qsharp.Bridge/Sources/Qsharp.Bridge/qsharp_bridge.swift qsharp-bridge.sln -platforms/python/qsharp-bridge/__pycache__/ \ No newline at end of file +platforms/python/qsharp-bridge/__pycache__/ +*.aux +*.tex +*.log diff --git a/examples/python/jupyter/quantikz.ipynb b/examples/python/jupyter/quantikz.ipynb new file mode 100644 index 0000000..2e7e532 --- /dev/null +++ b/examples/python/jupyter/quantikz.ipynb @@ -0,0 +1,95 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from qsharp_bridge import *\n", + "from IPython.core.getipython import get_ipython\n", + "from IPython.core.interactiveshell import InteractiveShell\n", + "\n", + "os.environ[\"PATH\"] += os.pathsep + \"/Library/TeX/texbin\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext jupyter_tikz" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "code = \"\"\"\n", + "namespace MyQuantumApp {\n", + " @EntryPoint()\n", + " operation Run() : (Result, Result) {\n", + " use (control, target) = (Qubit(), Qubit());\n", + " PrepareBellState(control, target);\n", + " \n", + " let resultControl = MResetZ(control);\n", + " let resultTarget = MResetZ(target);\n", + " return (resultControl, resultTarget);\n", + " }\n", + "\n", + " operation PrepareBellState(q1 : Qubit, q2: Qubit) : Unit {\n", + " H(q1);\n", + " CNOT(q1, q2);\n", + " }\n", + "}\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "quantikz_diagram = quantikz(code)\n", + "print(quantikz_diagram)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "shell = get_ipython()\n", + "assert shell is not None\n", + "\n", + "shell.run_cell_magic('tikz', '-l quantikz', quantikz_diagram)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/python/jupyter/requirements.txt b/examples/python/jupyter/requirements.txt index 1f5780f..6aab6f4 100644 --- a/examples/python/jupyter/requirements.txt +++ b/examples/python/jupyter/requirements.txt @@ -4,4 +4,5 @@ qsharp qsharp_widgets matplotlib requests -../../../platforms/python/qsharp-bridge \ No newline at end of file +../../../platforms/python/qsharp-bridge +jupyter-tikz \ No newline at end of file diff --git a/src/quantikz.rs b/src/quantikz.rs index 987aaf3..b656b66 100644 --- a/src/quantikz.rs +++ b/src/quantikz.rs @@ -3,7 +3,7 @@ use qsc::{ interpret::{CircuitEntryPoint, CircuitGenerationMethod, Interpreter}, target::Profile, }; use qsc_circuit::{Circuit, Operation, TracerConfig}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use crate::sim::QsError; @@ -12,7 +12,7 @@ type RegisterMap = HashMap<(usize, Option), usize>; #[derive(Clone)] struct Row { label: Option, - is_classical: bool, + is_classical: bool, } pub fn quantikz(code: &str) -> Result { @@ -57,57 +57,27 @@ pub fn circuit_to_quantikz(c: &Circuit) -> String { fn build_rows(c: &Circuit) -> (Vec, RegisterMap) { let mut rows: Vec = Vec::new(); let mut register_to_row = HashMap::new(); - let qubits_with_gap_row_below = identify_gaps(c); for q in &c.qubits { - let label = format!("\\lstick{{\\ket{{0}}_{{{}}}}}", q.id); - register_to_row.insert((q.id, None), rows.len()); + let label = format!("\\lstick{{$\\ket{{0}}_{{{}}}$}}", q.id); + let row_idx = rows.len(); + + // Map the Qubit ID to this row + register_to_row.insert((q.id, None), row_idx); + + // Map all result registers associated with this qubit to the SAME row. + for i in 0..q.num_results { + register_to_row.insert((q.id, Some(i)), row_idx); + } + rows.push(Row { label: Some(label), is_classical: false, }); - - let extra_rows = if qubits_with_gap_row_below.contains(&q.id) { - std::cmp::max(1, q.num_results) - } else { - q.num_results - }; - - for i in 0..extra_rows { - register_to_row.insert((q.id, Some(i)), rows.len()); - rows.push(Row { - label: None, - is_classical: true, - }); - } } (rows, register_to_row) } -fn identify_gaps(c: &Circuit) -> HashSet { - let mut gaps = HashSet::new(); - for col in &c.component_grid { - for op in &col.components { - let targets = match op { - Operation::Measurement(m) => &m.qubits, - Operation::Unitary(u) => &u.targets, - Operation::Ket(k) => &k.targets, - }; - for target in targets { - let q = target.qubit; - if gaps.contains(&q) { - continue; - } - let next_q = q + 1; - if targets.iter().any(|t| t.qubit == next_q) { - gaps.insert(q); - } - } - } - } - gaps -} - fn initialize_table(row_count: usize, col_count: usize, rows: &[Row]) -> Vec> { let mut table = vec![vec![String::new(); col_count + 1]; row_count]; for (r_idx, row) in rows.iter().enumerate() { @@ -131,7 +101,8 @@ fn populate_table( for (col_index, col) in c.component_grid.iter().enumerate() { let table_col = col_index; for op in &col.components { - let targets = get_rows_for_operation(op, register_to_row, true); + // For measurements, we want to draw on the qubit line, so we treat qubits as targets for visual placement + let targets = get_rows_for_operation(op, register_to_row, true); let controls = get_rows_for_operation(op, register_to_row, false); process_operation(op, table_col, &targets, &controls, table, rows); @@ -163,6 +134,7 @@ fn process_operation( for &t in targets { table[t][col] = String::from("\\meter{}"); rows[t].is_classical = true; + // Switch the rest of the wire to classical for next_c in (col + 1)..table[t].len() { table[t][next_c] = String::from("\\cw"); } @@ -172,6 +144,7 @@ fn process_operation( for &t in targets { table[t][col] = String::from("\\gate{\\ket{0}}"); rows[t].is_classical = false; + // Switch the rest of the wire back to quantum for next_c in (col + 1)..table[t].len() { table[t][next_c] = String::from("\\qw"); } @@ -279,9 +252,9 @@ fn get_rows_for_operation( let registers = match op { Operation::Measurement(m) => { if is_target { - &m.results - } else { &m.qubits + } else { + &m.qubits } } Operation::Unitary(u) => { @@ -306,5 +279,6 @@ fn get_rows_for_operation( .collect(); rows.sort(); + rows.dedup(); // Remove duplicates if result/qubit mapped to same row rows -} +} \ No newline at end of file diff --git a/tests/tests_quantikz.rs b/tests/tests_quantikz.rs index 09d6d86..3a2a604 100644 --- a/tests/tests_quantikz.rs +++ b/tests/tests_quantikz.rs @@ -18,8 +18,7 @@ fn quantikz_one_gate() { expect![[r#" \begin{quantikz} - \lstick{\ket{0}_{0}} & \gate{H} & \qw & \qw \\ - & \cw & \meter{} & \cw \\ + \lstick{$\ket{0}_{0}$} & \gate{H} & \meter{} & \cw \\ \end{quantikz} "#]] .assert_eq(&tex); @@ -41,9 +40,9 @@ fn quantikz_toffoli() { expect![[r#" \begin{quantikz} - \lstick{\ket{0}_{0}} & \ctrl{2} & \qw \\ - \lstick{\ket{0}_{1}} & \ctrl{1} & \qw \\ - \lstick{\ket{0}_{2}} & \targ{} & \qw \\ + \lstick{$\ket{0}_{0}$} & \ctrl{2} & \qw \\ + \lstick{$\ket{0}_{1}$} & \ctrl{1} & \qw \\ + \lstick{$\ket{0}_{2}$} & \targ{} & \qw \\ \end{quantikz} "#]] .assert_eq(&tex); @@ -65,9 +64,8 @@ fn quantikz_swap_gate() { expect![[r#" \begin{quantikz} - \lstick{\ket{0}_{0}} & \swap{2} & \qw \\ - & \cw & \cw \\ - \lstick{\ket{0}_{1}} & \targX{} & \qw \\ + \lstick{$\ket{0}_{0}$} & \swap{1} & \qw \\ + \lstick{$\ket{0}_{1}$} & \targX{} & \qw \\ \end{quantikz} "#]] .assert_eq(&tex); @@ -98,12 +96,9 @@ fn quantikz_complex_sample() { expect![[r#" \begin{quantikz} - \lstick{\ket{0}_{0}} & \gate{R_z(0.3927)} & \qw & \qw & \qw & \qw & \qw & \ctrl{4} & \qw & \ctrl{4} & \gate{T} & \qw & \ctrl{2} & \qw & \ctrl{2} & \gate{H} & \swap{4} & \qw & \qw \\ - & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw \\ - \lstick{\ket{0}_{1}} & \gate{T} & \qw & \ctrl{2} & \qw & \ctrl{2} & \qw & \qw & \qw & \qw & \gate{H} & \gate{T} & \targ{} & \gate{T^\dagger} & \targ{} & \qw & \qw & \qw & \qw \\ - & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw \\ - \lstick{\ket{0}_{2}} & \gate{H} & \gate{T} & \targ{} & \gate{T^\dagger} & \targ{} & \gate{R_z(0.3927)} & \targ{} & \gate{R_z(-0.3927)} & \targ{} & \qw & \qw & \qw & \qw & \qw & \qw & \targX{} & \qw & \qw \\ - & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw \\ + \lstick{$\ket{0}_{0}$} & \gate{R_z(0.3927)} & \qw & \qw & \qw & \qw & \qw & \ctrl{2} & \qw & \ctrl{2} & \gate{T} & \qw & \ctrl{1} & \qw & \ctrl{1} & \gate{H} & \swap{2} & \meter{} & \cw \\ + \lstick{$\ket{0}_{1}$} & \gate{T} & \qw & \ctrl{1} & \qw & \ctrl{1} & \qw & \qw & \qw & \qw & \gate{H} & \gate{T} & \targ{} & \gate{T^\dagger} & \targ{} & \qw & \qw & \meter{} & \cw \\ + \lstick{$\ket{0}_{2}$} & \gate{H} & \gate{T} & \targ{} & \gate{T^\dagger} & \targ{} & \gate{R_z(0.3927)} & \targ{} & \gate{R_z(-0.3927)} & \targ{} & \qw & \qw & \qw & \qw & \qw & \qw & \targX{} & \meter{} & \cw \\ \end{quantikz} "#]].assert_eq(&tex); } @@ -133,10 +128,8 @@ fn quantikz_rotation_circuit() { expect![[r#" \begin{quantikz} - \lstick{\ket{0}_{0}} & \gate{X} & \gate{H} & \ctrl{2} & \gate{H} & \qw & \qw & \qw \\ - & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ - \lstick{\ket{0}_{1}} & \gate{X} & \qw & \targ{} & \gate{R_z(2.0944)} & \gate{H} & \qw & \qw \\ - & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw \\ + \lstick{$\ket{0}_{0}$} & \gate{X} & \gate{H} & \ctrl{1} & \gate{H} & \meter{} & \cw & \cw \\ + \lstick{$\ket{0}_{1}$} & \gate{X} & \qw & \targ{} & \gate{R_z(2.0944)} & \gate{H} & \meter{} & \cw \\ \end{quantikz} "#]] .assert_eq(&tex); @@ -169,22 +162,48 @@ fn quantikz_cat_state() { expect![[r#" \begin{quantikz} - \lstick{\ket{0}_{0}} & \gate{H} & \ctrl{2} & \ctrl{4} & \ctrl{6} & \ctrl{8} & \ctrl{10} & \ctrl{12} & \ctrl{14} & \qw & \gate{\ket{0}} & \qw \\ - & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ - \lstick{\ket{0}_{1}} & \qw & \targ{} & \qw & \qw & \qw & \qw & \qw & \qw & \qw & \gate{\ket{0}} & \qw \\ - & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ - \lstick{\ket{0}_{2}} & \qw & \qw & \targ{} & \qw & \qw & \qw & \qw & \qw & \qw & \gate{\ket{0}} & \qw \\ - & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ - \lstick{\ket{0}_{3}} & \qw & \qw & \qw & \targ{} & \qw & \qw & \qw & \qw & \qw & \gate{\ket{0}} & \qw \\ - & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ - \lstick{\ket{0}_{4}} & \qw & \qw & \qw & \qw & \targ{} & \qw & \qw & \qw & \qw & \gate{\ket{0}} & \qw \\ - & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ - \lstick{\ket{0}_{5}} & \qw & \qw & \qw & \qw & \qw & \targ{} & \qw & \qw & \qw & \gate{\ket{0}} & \qw \\ - & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ - \lstick{\ket{0}_{6}} & \qw & \qw & \qw & \qw & \qw & \qw & \targ{} & \qw & \qw & \gate{\ket{0}} & \qw \\ - & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ - \lstick{\ket{0}_{7}} & \qw & \qw & \qw & \qw & \qw & \qw & \qw & \targ{} & \qw & \gate{\ket{0}} & \qw \\ - & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \cw & \meter{} & \cw & \cw \\ + \lstick{$\ket{0}_{0}$} & \gate{H} & \ctrl{1} & \ctrl{2} & \ctrl{3} & \ctrl{4} & \ctrl{5} & \ctrl{6} & \ctrl{7} & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{1}$} & \qw & \targ{} & \qw & \qw & \qw & \qw & \qw & \qw & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{2}$} & \qw & \qw & \targ{} & \qw & \qw & \qw & \qw & \qw & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{3}$} & \qw & \qw & \qw & \targ{} & \qw & \qw & \qw & \qw & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{4}$} & \qw & \qw & \qw & \qw & \targ{} & \qw & \qw & \qw & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{5}$} & \qw & \qw & \qw & \qw & \qw & \targ{} & \qw & \qw & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{6}$} & \qw & \qw & \qw & \qw & \qw & \qw & \targ{} & \qw & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{7}$} & \qw & \qw & \qw & \qw & \qw & \qw & \qw & \targ{} & \meter{} & \gate{\ket{0}} & \qw \\ + \end{quantikz} + "#]] + .assert_eq(&tex); +} + +#[test] +fn quantikz_user_example() { + let tex = quantikz( + r" + namespace Test { + import Std.Measurement.*; + + @EntryPoint() + operation Run() : (Result, Result) { + use (control, target) = (Qubit(), Qubit()); + PrepareBellState(control, target); + + let resultControl = MResetZ(control); + let resultTarget = MResetZ(target); + return (resultControl, resultTarget); + } + + operation PrepareBellState(q1 : Qubit, q2: Qubit) : Unit { + H(q1); + CNOT(q1, q2); + } + } + " + ).expect("quantikz generation should succeed"); + + expect![[r#" + \begin{quantikz} + \lstick{$\ket{0}_{0}$} & \gate{H} & \ctrl{1} & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{1}$} & \qw & \targ{} & \meter{} & \gate{\ket{0}} & \qw \\ \end{quantikz} "#]] .assert_eq(&tex); From 8433dbf8aaf793506c1d8cede1efd812ba0f73df Mon Sep 17 00:00:00 2001 From: filipw Date: Mon, 24 Nov 2025 17:34:39 +0100 Subject: [PATCH 5/7] added text to the tikz jupyter sample --- examples/python/jupyter/quantikz.ipynb | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/examples/python/jupyter/quantikz.ipynb b/examples/python/jupyter/quantikz.ipynb index 2e7e532..7cd2a34 100644 --- a/examples/python/jupyter/quantikz.ipynb +++ b/examples/python/jupyter/quantikz.ipynb @@ -1,5 +1,14 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, import the library.\n", + "\n", + "You will also need to have LaTeX installed on your system for rendering the diagrams." + ] + }, { "cell_type": "code", "execution_count": null, @@ -10,9 +19,17 @@ "from IPython.core.getipython import get_ipython\n", "from IPython.core.interactiveshell import InteractiveShell\n", "\n", + "# Ensure that TeX binaries are in the PATH for LaTeX rendering\n", "os.environ[\"PATH\"] += os.pathsep + \"/Library/TeX/texbin\"" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, load the Jupyter TikZ extension. " + ] + }, { "cell_type": "code", "execution_count": null, @@ -22,6 +39,13 @@ "%load_ext jupyter_tikz" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now you can write some Q# code. This will be the Q# code for which we will generate a circuit diagram." + ] + }, { "cell_type": "code", "execution_count": null, @@ -48,6 +72,13 @@ "\"\"\"" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the Q# code is written, you can use the `quantikz` function to generate and display the circuit diagram directly in the Jupyter notebook." + ] + }, { "cell_type": "code", "execution_count": null, @@ -55,9 +86,18 @@ "outputs": [], "source": [ "quantikz_diagram = quantikz(code)\n", + "\n", + "# for debugging, display the generated LaTeX code\n", "print(quantikz_diagram)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use the TikZ extension to render a PDF from the LaTeX code generated by Q# Bridge." + ] + }, { "cell_type": "code", "execution_count": null, From b516cc8112e4ea05b9965be02f449f5f0b4583cb Mon Sep 17 00:00:00 2001 From: filipw Date: Mon, 24 Nov 2025 18:12:03 +0100 Subject: [PATCH 6/7] added quantikz for operations and visual comparison to q# circuit in notebook --- examples/python/jupyter/quantikz.ipynb | 20 +++ src/lib.rs | 1 + src/qsharp-bridge.udl | 3 + src/quantikz.rs | 26 ++-- tests/tests_quantikz.rs | 180 +++++++++++++++++++++---- 5 files changed, 199 insertions(+), 31 deletions(-) diff --git a/examples/python/jupyter/quantikz.ipynb b/examples/python/jupyter/quantikz.ipynb index 7cd2a34..0574440 100644 --- a/examples/python/jupyter/quantikz.ipynb +++ b/examples/python/jupyter/quantikz.ipynb @@ -109,6 +109,26 @@ "\n", "shell.run_cell_magic('tikz', '-l quantikz', quantikz_diagram)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For comparison, use the built-in `Circuit` widget from the `qsharp_widgets` library to display the same circuit diagram. This should give us the same visual representation of the quantum circuit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from qsharp_widgets import Circuit\n", + "import qsharp\n", + "\n", + "qsharp.eval(code)\n", + "Circuit(qsharp.circuit(\"MyQuantumApp.Run()\"))" + ] } ], "metadata": { diff --git a/src/lib.rs b/src/lib.rs index 7d9d0de..8cf4380 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ use crate::sim::qir; use crate::sim::run_qs; use crate::sim::run_qs_with_options; use crate::quantikz::quantikz; +use crate::quantikz::quantikz_operation; pub mod noise; pub mod qasm; diff --git a/src/qsharp-bridge.udl b/src/qsharp-bridge.udl index 2f5e93f..8ca3d49 100644 --- a/src/qsharp-bridge.udl +++ b/src/qsharp-bridge.udl @@ -18,6 +18,9 @@ namespace qsharp_bridge { [Throws=QsError] string quantikz([ByRef]string source); + [Throws=QsError] + string quantikz_operation([ByRef]string operation, [ByRef]string source); + [Throws=QsError] string estimate([ByRef]string source, string? job_params); diff --git a/src/quantikz.rs b/src/quantikz.rs index b656b66..00ffa98 100644 --- a/src/quantikz.rs +++ b/src/quantikz.rs @@ -15,8 +15,19 @@ struct Row { is_classical: bool, } -pub fn quantikz(code: &str) -> Result { - let sources = SourceMap::new([("test.qs".into(), code.into())], None); +pub fn quantikz(source: &str) -> Result { + generate_quantikz_circuit(source, CircuitEntryPoint::EntryPoint) +} + +pub fn quantikz_operation(operation: &str, source: &str) -> Result { + generate_quantikz_circuit(source, CircuitEntryPoint::Operation(operation.to_string())) +} + +fn generate_quantikz_circuit( + source: &str, + entry_point: CircuitEntryPoint, +) -> Result { + let sources = SourceMap::new([("test.qs".into(), source.into())], None); let (std_id, store) = qsc::compile::package_store_with_stdlib(Profile::Unrestricted.into()); let mut interpreter = match Interpreter::with_circuit_trace( @@ -34,12 +45,11 @@ pub fn quantikz(code: &str) -> Result { } }; - let circuit = interpreter - .circuit( - CircuitEntryPoint::EntryPoint, - CircuitGenerationMethod::ClassicalEval, - TracerConfig::default(), - )?; + let circuit = interpreter.circuit( + entry_point, + CircuitGenerationMethod::ClassicalEval, + TracerConfig::default(), + )?; Ok(circuit_to_quantikz(&circuit)) } diff --git a/tests/tests_quantikz.rs b/tests/tests_quantikz.rs index 3a2a604..155220d 100644 --- a/tests/tests_quantikz.rs +++ b/tests/tests_quantikz.rs @@ -1,5 +1,5 @@ use expect_test::expect; -use qsharp_bridge::quantikz::quantikz; +use qsharp_bridge::quantikz::{quantikz, quantikz_operation}; #[test] fn quantikz_one_gate() { @@ -24,6 +24,26 @@ fn quantikz_one_gate() { .assert_eq(&tex); } +#[test] +fn quantikz_operation_one_gate() { + let source = r" + namespace Test { + operation Main() : Unit { + use q = Qubit(); + H(q); + M(q); + } + } + "; + let tex = quantikz_operation("Test.Main", source).expect("quantikz generation should succeed"); + expect![[r#" + \begin{quantikz} + \lstick{$\ket{0}_{0}$} & \gate{H} & \meter{} & \cw \\ + \end{quantikz} + "#]] + .assert_eq(&tex); +} + #[test] fn quantikz_toffoli() { let tex = quantikz( @@ -48,6 +68,30 @@ fn quantikz_toffoli() { .assert_eq(&tex); } +#[test] +fn quantikz_operation_toffoli() { + let source = + r" + namespace Test { + operation Main() : Unit { + use q = Qubit[3]; + CCNOT(q[0], q[1], q[2]); + } + } + "; + + let tex = quantikz_operation("Test.Main", source).expect("quantikz generation should succeed"); + + expect![[r#" + \begin{quantikz} + \lstick{$\ket{0}_{0}$} & \ctrl{2} & \qw \\ + \lstick{$\ket{0}_{1}$} & \ctrl{1} & \qw \\ + \lstick{$\ket{0}_{2}$} & \targ{} & \qw \\ + \end{quantikz} + "#]] + .assert_eq(&tex); +} + #[test] fn quantikz_swap_gate() { let tex = quantikz( @@ -71,6 +115,29 @@ fn quantikz_swap_gate() { .assert_eq(&tex); } +#[test] +fn quantikz_operation_swap_gate() { + let source = + r" + namespace Test { + operation Main() : Unit { + use q = Qubit[2]; + SWAP(q[0], q[1]); + } + } + "; + + let tex = quantikz_operation("Test.Main", source).expect("quantikz generation should succeed"); + + expect![[r#" + \begin{quantikz} + \lstick{$\ket{0}_{0}$} & \swap{1} & \qw \\ + \lstick{$\ket{0}_{1}$} & \targX{} & \qw \\ + \end{quantikz} + "#]] + .assert_eq(&tex); +} + #[test] fn quantikz_complex_sample() { let tex = quantikz( @@ -103,6 +170,37 @@ fn quantikz_complex_sample() { "#]].assert_eq(&tex); } +#[test] +fn quantikz_operation_complex_sample() { + let source = r#" + namespace Test { + operation Main() : Unit { + use (q0, q1, q2) = (Qubit(), Qubit(), Qubit()); + H(q2); + Controlled S([q1], q2); + Controlled T([q0], q2); + H(q1); + Controlled S([q0], q1); + H(q0); + SWAP(q0, q2); + let r0 = M(q0); + let r1 = M(q1); + let r2 = M(q2); + } + } + "#; + + let tex = quantikz_operation("Test.Main", source).expect("quantikz generation should succeed"); + + expect![[r#" + \begin{quantikz} + \lstick{$\ket{0}_{0}$} & \gate{R_z(0.3927)} & \qw & \qw & \qw & \qw & \qw & \ctrl{2} & \qw & \ctrl{2} & \gate{T} & \qw & \ctrl{1} & \qw & \ctrl{1} & \gate{H} & \swap{2} & \meter{} & \cw \\ + \lstick{$\ket{0}_{1}$} & \gate{T} & \qw & \ctrl{1} & \qw & \ctrl{1} & \qw & \qw & \qw & \qw & \gate{H} & \gate{T} & \targ{} & \gate{T^\dagger} & \targ{} & \qw & \qw & \meter{} & \cw \\ + \lstick{$\ket{0}_{2}$} & \gate{H} & \gate{T} & \targ{} & \gate{T^\dagger} & \targ{} & \gate{R_z(0.3927)} & \targ{} & \gate{R_z(-0.3927)} & \targ{} & \qw & \qw & \qw & \qw & \qw & \qw & \targX{} & \meter{} & \cw \\ + \end{quantikz} + "#]].assert_eq(&tex); +} + #[test] fn quantikz_rotation_circuit() { let tex = quantikz( @@ -135,6 +233,37 @@ fn quantikz_rotation_circuit() { .assert_eq(&tex); } +#[test] +fn quantikz_operation_rotation_circuit() { + let source = r#" + namespace Test { + open Microsoft.Quantum.Math; + operation Main() : Unit { + use (q0, q1) = (Qubit(), Qubit()); + X(q0); + X(q1); + H(q0); + CNOT(q0, q1); + Rz(2.0 * PI() / 3.0, q1); + H(q0); + H(q1); + M(q0); + M(q1); + } + } + "#; + + let tex = quantikz_operation("Test.Main", source).expect("quantikz generation should succeed"); + + expect![[r#" + \begin{quantikz} + \lstick{$\ket{0}_{0}$} & \gate{X} & \gate{H} & \ctrl{1} & \gate{H} & \meter{} & \cw & \cw \\ + \lstick{$\ket{0}_{1}$} & \gate{X} & \qw & \targ{} & \gate{R_z(2.0944)} & \gate{H} & \meter{} & \cw \\ + \end{quantikz} + "#]] + .assert_eq(&tex); +} + #[test] fn quantikz_cat_state() { let tex = quantikz( @@ -176,34 +305,39 @@ fn quantikz_cat_state() { } #[test] -fn quantikz_user_example() { - let tex = quantikz( - r" - namespace Test { - import Std.Measurement.*; - - @EntryPoint() - operation Run() : (Result, Result) { - use (control, target) = (Qubit(), Qubit()); - PrepareBellState(control, target); - - let resultControl = MResetZ(control); - let resultTarget = MResetZ(target); - return (resultControl, resultTarget); - } +fn quantikz_operation_cat_state() { + let source = r" + namespace Test { + import Std.Measurement.*; + operation Main() : Result[] { + use qubits = Qubit[8]; - operation PrepareBellState(q1 : Qubit, q2: Qubit) : Unit { - H(q1); - CNOT(q1, q2); + // apply Hadamard gate to the first qubit + H(qubits[0]); + + // apply CNOT gates to create entanglement + for qubit in qubits[1..Length(qubits) - 1] { + CNOT(qubits[0], qubit); } + + // return measurement results + MResetEachZ(qubits) } - " - ).expect("quantikz generation should succeed"); + } + "; + + let tex = quantikz_operation("Test.Main", source).expect("quantikz generation should succeed"); expect![[r#" \begin{quantikz} - \lstick{$\ket{0}_{0}$} & \gate{H} & \ctrl{1} & \meter{} & \gate{\ket{0}} & \qw \\ - \lstick{$\ket{0}_{1}$} & \qw & \targ{} & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{0}$} & \gate{H} & \ctrl{1} & \ctrl{2} & \ctrl{3} & \ctrl{4} & \ctrl{5} & \ctrl{6} & \ctrl{7} & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{1}$} & \qw & \targ{} & \qw & \qw & \qw & \qw & \qw & \qw & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{2}$} & \qw & \qw & \targ{} & \qw & \qw & \qw & \qw & \qw & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{3}$} & \qw & \qw & \qw & \targ{} & \qw & \qw & \qw & \qw & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{4}$} & \qw & \qw & \qw & \qw & \targ{} & \qw & \qw & \qw & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{5}$} & \qw & \qw & \qw & \qw & \qw & \targ{} & \qw & \qw & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{6}$} & \qw & \qw & \qw & \qw & \qw & \qw & \targ{} & \qw & \meter{} & \gate{\ket{0}} & \qw \\ + \lstick{$\ket{0}_{7}$} & \qw & \qw & \qw & \qw & \qw & \qw & \qw & \targ{} & \meter{} & \gate{\ket{0}} & \qw \\ \end{quantikz} "#]] .assert_eq(&tex); From bfc4057c8a180168c868dfde605360186344319b Mon Sep 17 00:00:00 2001 From: filipw Date: Wed, 26 Nov 2025 16:00:45 +0100 Subject: [PATCH 7/7] added secoond sample to quantikz circuit notebook --- examples/python/jupyter/quantikz.ipynb | 68 ++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/examples/python/jupyter/quantikz.ipynb b/examples/python/jupyter/quantikz.ipynb index 0574440..80e56ef 100644 --- a/examples/python/jupyter/quantikz.ipynb +++ b/examples/python/jupyter/quantikz.ipynb @@ -6,7 +6,7 @@ "source": [ "First, import the library.\n", "\n", - "You will also need to have LaTeX installed on your system for rendering the diagrams." + "You will also need to have LaTeX installed on your system for rendering the diagrams. We also import official Q# library as we will use it for comparison of the generated diagrams." ] }, { @@ -18,6 +18,8 @@ "from qsharp_bridge import *\n", "from IPython.core.getipython import get_ipython\n", "from IPython.core.interactiveshell import InteractiveShell\n", + "from qsharp_widgets import Circuit\n", + "import qsharp\n", "\n", "# Ensure that TeX binaries are in the PATH for LaTeX rendering\n", "os.environ[\"PATH\"] += os.pathsep + \"/Library/TeX/texbin\"" @@ -52,7 +54,7 @@ "metadata": {}, "outputs": [], "source": [ - "code = \"\"\"\n", + "code_1 = \"\"\"\n", "namespace MyQuantumApp {\n", " @EntryPoint()\n", " operation Run() : (Result, Result) {\n", @@ -69,6 +71,23 @@ " CNOT(q1, q2);\n", " }\n", "}\n", + "\"\"\"\n", + "\n", + "code_2 = \"\"\"\n", + "operation Main() : Result[] {\n", + " use qubits = Qubit[8];\n", + "\n", + " // apply Hadamard gate to the first qubit\n", + " H(qubits[0]);\n", + "\n", + " // apply CNOT gates to create entanglement\n", + " for qubit in qubits[1..Length(qubits) - 1] {\n", + " CNOT(qubits[0], qubit);\n", + " }\n", + "\n", + " // return measurement results\n", + " MResetEachZ(qubits)\n", + "}\n", "\"\"\"" ] }, @@ -85,10 +104,12 @@ "metadata": {}, "outputs": [], "source": [ - "quantikz_diagram = quantikz(code)\n", + "quantikz_diagram_1 = quantikz(code_1)\n", + "quantikz_diagram_2 = quantikz(code_2)\n", "\n", "# for debugging, display the generated LaTeX code\n", - "print(quantikz_diagram)" + "print(quantikz_diagram_1)\n", + "print(quantikz_diagram_2)" ] }, { @@ -107,7 +128,19 @@ "shell = get_ipython()\n", "assert shell is not None\n", "\n", - "shell.run_cell_magic('tikz', '-l quantikz', quantikz_diagram)" + "shell.run_cell_magic('tikz', '-l quantikz', quantikz_diagram_1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "shell = get_ipython()\n", + "assert shell is not None\n", + "\n", + "shell.run_cell_magic('tikz', '-l quantikz', quantikz_diagram_2)" ] }, { @@ -123,11 +156,28 @@ "metadata": {}, "outputs": [], "source": [ - "from qsharp_widgets import Circuit\n", - "import qsharp\n", + "qsharp.init() # this ensures clean state\n", + "qsharp.eval(code_1)\n", + "Circuit(qsharp.circuit(\"MyQuantumApp.Run()\")) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "\n", - "qsharp.eval(code)\n", - "Circuit(qsharp.circuit(\"MyQuantumApp.Run()\"))" + "qsharp.init() # this ensures clean state\n", + "qsharp.eval(code_2)\n", + "Circuit(qsharp.circuit(\"Main()\")) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We should see that the diagrams generated using `quantikz` and the built-in `Circuit` widget are identical. This confirms that our LaTeX generation is working correctly!" ] } ],