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/Cargo.lock b/Cargo.lock index 49db768..0c75178 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", ] @@ -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" @@ -523,7 +539,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 +597,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 +735,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 +743,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 +776,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 +787,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 +799,7 @@ dependencies = [ "qsc_fir", "qsc_frontend", "qsc_hir", + "qsc_lowerer", "regex-lite", "rustc-hash 1.1.0", "serde", @@ -793,7 +810,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 +831,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 +842,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 +855,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 +875,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 +886,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 +896,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 +911,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 +922,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 +938,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 +948,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 +963,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 +981,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 +998,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 +1019,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 +1040,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 +1056,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", @@ -1050,9 +1067,12 @@ dependencies = [ name = "qsharp-bridge" version = "0.2.0" dependencies = [ + "expect-test", "num-bigint", "num-complex", "qsc", + "qsc_circuit", + "qsc_eval", "resource_estimator", "serde", "serde_json", @@ -1170,7 +1190,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..ad02f06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,12 +9,15 @@ 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" uniffi = { version = "0.29.4", features=["build"] } +expect-test = "1.4" [dev-dependencies] serde = { version = "1.0", features = ["derive"] } diff --git a/examples/python/jupyter/quantikz.ipynb b/examples/python/jupyter/quantikz.ipynb new file mode 100644 index 0000000..80e56ef --- /dev/null +++ b/examples/python/jupyter/quantikz.ipynb @@ -0,0 +1,205 @@ +{ + "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. We also import official Q# library as we will use it for comparison of the generated diagrams." + ] + }, + { + "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", + "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\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, load the Jupyter TikZ extension. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%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, + "metadata": {}, + "outputs": [], + "source": [ + "code_1 = \"\"\"\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", + "\"\"\"\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", + "\"\"\"" + ] + }, + { + "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, + "metadata": {}, + "outputs": [], + "source": [ + "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_1)\n", + "print(quantikz_diagram_2)" + ] + }, + { + "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, + "metadata": {}, + "outputs": [], + "source": [ + "shell = get_ipython()\n", + "assert shell is not None\n", + "\n", + "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)" + ] + }, + { + "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": [ + "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.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!" + ] + } + ], + "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/lib.rs b/src/lib.rs index 79844ea..8cf4380 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,10 @@ 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; +use crate::quantikz::quantikz_operation; pub mod noise; pub mod qasm; pub mod sim; +pub mod quantikz; \ No newline at end of file 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) } } } diff --git a/src/qsharp-bridge.udl b/src/qsharp-bridge.udl index deb0504..8ca3d49 100644 --- a/src/qsharp-bridge.udl +++ b/src/qsharp-bridge.udl @@ -15,6 +15,12 @@ namespace qsharp_bridge { [Throws=QsError] string qasm2_expression([ByRef]string expression, QasmGenerationOptions generation_options); + [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 new file mode 100644 index 0000000..00ffa98 --- /dev/null +++ b/src/quantikz.rs @@ -0,0 +1,294 @@ +use qsc::{ + LanguageFeatures, PackageType, SourceMap, + interpret::{CircuitEntryPoint, CircuitGenerationMethod, Interpreter}, target::Profile, +}; +use qsc_circuit::{Circuit, Operation, TracerConfig}; +use std::collections::HashMap; + +use crate::sim::QsError; + +type RegisterMap = HashMap<(usize, Option), usize>; + +#[derive(Clone)] +struct Row { + label: Option, + is_classical: bool, +} + +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( + 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( + entry_point, + 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(); + 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(); + + for q in &c.qubits { + 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, + }); + } + (rows, register_to_row) +} + +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 { + // 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); + } + } +} + +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; + // Switch the rest of the wire to classical + 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; + // 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"); + } + } + } + } +} + +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.qubits + } 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.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 new file mode 100644 index 0000000..155220d --- /dev/null +++ b/tests/tests_quantikz.rs @@ -0,0 +1,344 @@ +use expect_test::expect; +use qsharp_bridge::quantikz::{quantikz, quantikz_operation}; + +#[test] +fn quantikz_one_gate() { + let tex = quantikz( + r" + namespace Test { + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + H(q); + M(q); + } + } + " + ).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_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( + r" + namespace Test { + @EntryPoint() + operation Main() : Unit { + use q = Qubit[3]; + CCNOT(q[0], q[1], q[2]); + } + } + " + ).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_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( + r" + namespace Test { + @EntryPoint() + operation Main() : Unit { + use q = Qubit[2]; + SWAP(q[0], q[1]); + } + } + " + ).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_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( + 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); + } + } + "# + ).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_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( + 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); + } + } + "# + ).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_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( + 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) + } + } + " + ).expect("quantikz generation should succeed"); + + expect![[r#" + \begin{quantikz} + \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_operation_cat_state() { + let source = r" + namespace Test { + import Std.Measurement.*; + 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) + } + } + "; + + let tex = quantikz_operation("Test.Main", source).expect("quantikz generation should succeed"); + + expect![[r#" + \begin{quantikz} + \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); +} \ No newline at end of file