From ff1c2c437199ec898b9d2f675676db8ceee0349a Mon Sep 17 00:00:00 2001 From: Kapakayala Naga sai krishna vinayswami <66941388+vinayswamik@users.noreply.github.com> Date: Mon, 15 Sep 2025 18:57:06 -0500 Subject: [PATCH 1/5] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b60b47e..a261012 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ To load an algorithm as a PyQASM module, use the `load_algorithm` function from ```python from qbraid_algorithms import qft -qft_module = qft.load_algorithm(3) # Load QFT for 3 qubits +qft_module = qft.load_program(3) # Load QFT for 3 qubits ``` Now, you can perform operations with the PyQASM module, such as unrolling, and From 22d02b8c982606f32c0d6db0398e2d40f8837062 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Fri, 19 Sep 2025 00:32:06 -0500 Subject: [PATCH 2/5] Update documentation and improve algorithm descriptions --- README.md | 2 +- docs/_static/css/custom.css | 175 +++++++++++++++--- docs/conf.py | 4 +- docs/index.rst | 19 +- qbraid_algorithms/__init__.py | 57 +++--- .../amplitude_amplification/__init__.py | 63 ++++++- .../amplitude_amplification/amp_ampl.py | 45 ++--- .../bells_inequality/__init__.py | 47 ++++- .../bells_inequality/bells_inequality.py | 5 +- .../bernstein_vazirani/__init__.py | 49 ++++- .../bernstein_vazirani/bernvaz.py | 2 +- qbraid_algorithms/embedding/__init__.py | 74 ++++++-- qbraid_algorithms/evolution/__init__.py | 87 +++++++-- qbraid_algorithms/hhl/__init__.py | 62 ++++++- qbraid_algorithms/hhl/hhl.py | 23 +-- qbraid_algorithms/iqft/__init__.py | 56 +++++- qbraid_algorithms/iqft/iqft.py | 2 +- qbraid_algorithms/qft/__init__.py | 81 ++++++-- qbraid_algorithms/qft/qft.py | 15 +- qbraid_algorithms/qft/qft_lib.py | 44 ++--- qbraid_algorithms/qpe/__init__.py | 76 +++++++- qbraid_algorithms/qpe/phase_est.py | 67 ++++--- qbraid_algorithms/qpe/qpe.py | 15 +- qbraid_algorithms/qtran/__init__.py | 87 +++++++-- qbraid_algorithms/rodeo/__init__.py | 64 ++++++- qbraid_algorithms/rodeo/rodeo.py | 88 +++++---- 26 files changed, 996 insertions(+), 313 deletions(-) diff --git a/README.md b/README.md index a261012..8f00911 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ you can generate .qasm files to use them as subroutines in your own circuits. ### Loading Algorithms as PyQASM Modules -To load an algorithm as a PyQASM module, use the `load_algorithm` function from the `qbraid_algorithms` package, passing algorithm-specific parameters. For example, to load the Quantum Fourier Transform (QFT) algorithm: +To load an algorithm as a PyQASM module, use the `load_program` function from the `qbraid_algorithms` package, passing algorithm-specific parameters. For example, to load the Quantum Fourier Transform (QFT) algorithm: ```python from qbraid_algorithms import qft diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css index 7dc415e..6fafe52 100644 --- a/docs/_static/css/custom.css +++ b/docs/_static/css/custom.css @@ -17,15 +17,11 @@ --font-color4: #ffffff; --brand-color0: #46096f; --brand-color1: #df0982; - --active-gradient0: linear-gradient( - 45deg, - var(--brand-color1), - var(--brand-color0) - ); - --active-gradient1: linear-gradient( - rgb(135, 0, 202) -41.11%, - rgba(216, 164, 250, 0.92) 197.13% - ); + --active-gradient0: linear-gradient(45deg, + var(--brand-color1), + var(--brand-color0)); + --active-gradient1: linear-gradient(rgb(135, 0, 202) -41.11%, + rgba(216, 164, 250, 0.92) 197.13%); } .wy-body-for-nav { @@ -44,14 +40,14 @@ color: var(--font-color0); } -.wy-side-nav-search > a { +.wy-side-nav-search>a { color: var(--font-color0); display: flex; align-items: center; justify-content: center; } -.wy-side-nav-search > a::before { +.wy-side-nav-search>a::before { content: ""; background-image: url(../logo.png); background-size: contain; @@ -64,7 +60,7 @@ color: var(--font-color2) !important; } -.wy-menu-vertical li.current > a, +.wy-menu-vertical li.current>a, .wy-menu-vertical li.on a { background-image: var(--active-gradient0); background-size: 150%; @@ -73,14 +69,14 @@ transition: background-position 150ms ease; } -.wy-menu-vertical li.current > a:hover, +.wy-menu-vertical li.current>a:hover, .wy-menu-vertical li.on a:hover { background-image: var(--active-gradient0); background-size: 150%; background-position: right; } -.wy-menu-vertical li.current > a:hover button.toctree-expand, +.wy-menu-vertical li.current>a:hover button.toctree-expand, .wy-menu-vertical li.on a:hover button.toctree-expand { color: var(--font-color3); } @@ -104,7 +100,7 @@ } /* headers */ -.rst-content .toctree-wrapper > p.caption, +.rst-content .toctree-wrapper>p.caption, h1, h2, h3, @@ -117,12 +113,22 @@ legend { color: var(--brand-color0); } + +/* Style h1 headers only on API and stubs pages */ +html[data-content_root="../"] h1 { + opacity: 0.5 !important; + color: #000000 !important; + font-size: 100% !important; + font-weight: normal !important; + font-style: italic !important; +} + .wy-menu-vertical header, .wy-menu-vertical p.caption { color: var(--brand-color0); } -.wy-menu-vertical li.current > a button.toctree-expand, +.wy-menu-vertical li.current>a button.toctree-expand, .wy-menu-vertical li.on a button.toctree-expand { color: var(--font-color4); } @@ -137,7 +143,7 @@ legend { transition: transform 150ms ease; } -.card > h3 { +.card>h3 { color: var(--font-color0); } @@ -221,17 +227,6 @@ legend { font-family: "Source Sans Pro", sans-serif; } -/* Foot note */ -.rst-content .seealso .admonition-title { - background: var(--active-gradient0); -} - -.rst-content .seealso { - background: var(--layout-color0); - border-radius: 8px; - overflow: hidden; -} - /* common elements */ .rst-content p a { -webkit-text-fill-color: transparent; @@ -241,7 +236,7 @@ legend { background-clip: text; } -.wy-breadcrumbs > li.wy-breadcrumbs-aside > a { +.wy-breadcrumbs>li.wy-breadcrumbs-aside>a { -webkit-text-fill-color: transparent; text-fill-color: transparent; background: var(--active-gradient0); @@ -267,3 +262,125 @@ a .rst-content tt { -webkit-text-fill-color: var(--brand-color0); text-fill-color: var(--brand-color0); } + +/* ============================================================================= + ADMONITION BOXES STYLING + ============================================================================= */ + +/* Shared border radius for enhanced elements */ +.rst-content .note-enhanced, +.rst-content .seealso, +.rst-content .seealso table.autosummary { + border-radius: 10px; +} + +/* Note Enhanced Boxes */ +.rst-content .note-enhanced { + font-size: 1.5em; + background: white; + box-shadow: 0 4px 18px rgba(70, 9, 111, 0.50); +} + +.rst-content .note-enhanced .admonition-title { + border-radius: 10px 10px 0 0; + background: var(--active-gradient0); + color: #ffffff !important; + font-weight: bold; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8); + padding: 12px 16px; +} + +.rst-content .note-enhanced .admonition-title::before { + display: none !important; +} + +/* Seealso Boxes */ +.rst-content .seealso { + font-size: 1.1em; + background: white; + box-shadow: 0 4px 18px rgba(70, 9, 111, 0.50); +} + +/* Reduce line spacing in formulation sections */ +.rst-content .seealso p { + margin-bottom: 0.5em; + line-height: 1.3; +} + +.rst-content .seealso ol, +.rst-content .seealso ul { + margin-bottom: 0.5em; +} + +.rst-content .seealso li { + margin-bottom: 0.3em; + line-height: 1.3; +} + +.rst-content .seealso .math { + margin: 0.2em 0; + line-height: 1.2; +} + +.rst-content .seealso .admonition-title { + border-radius: 10px 10px 0 0; + padding-top: 16px; + background: white; + color: var(--brand-color0) !important; + font-weight: bold; +} + +.rst-content .seealso.note-enhanced-size .admonition-title::before, +.rst-content .left-box .admonition-title::before, +.rst-content .right-box .admonition-title::before { + display: none !important; +} + +.rst-content .seealso.note-enhanced-size>.admonition-title { + font-weight: bold; + text-align: center; +} + +/* Tables */ +.rst-content .seealso table.autosummary { + border-collapse: separate; + border-spacing: 0; + border-width: 2px !important; +} + +/* ============================================================================= + SIDE-BY-SIDE ALGORITHM LAYOUT + ============================================================================= */ + +/* Container Layout */ +.side-by-side { + display: flex; +} + +.left-box, +.right-box { + flex-direction: column; +} + +/* Algorithm Boxes */ +.rst-content .left-box .note, +.rst-content .right-box .note { + background: white !important; + overflow: hidden !important; +} + +/* Algorithm Titles */ +.left-box .admonition-title, +.right-box .admonition-title { + text-align: center; + font-size: 1em !important; +} + +/* ============================================================================= + PAGE-SPECIFIC FIXES + ============================================================================= */ + +/* Hide duplicate title on all qbraid_algorithms submodule pages */ +section[id^="module-qbraid_algorithms."]>p:first-of-type { + display: none; +} \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 6bd7e39..6544276 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ "sphinx.ext.autodoc", "sphinx_autodoc_typehints", "sphinx.ext.autosummary", - "sphinx_copybutton" + "sphinx_copybutton", ] autodoc_mock_imports = ["qbraid", "pyqasm"] @@ -52,4 +52,4 @@ html_favicon = "_static/favicon.ico" html_show_sphinx = False -html_css_files = ["css/s4defs-roles.css"] +html_css_files = ["css/s4defs-roles.css", "css/custom.css"] diff --git a/docs/index.rst b/docs/index.rst index 0da81b5..071acb9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -51,7 +51,7 @@ | algorithms

- Use and build quantum algorithms with qBraid. + Build Quantum Algorithms with qBraid.

@@ -61,10 +61,21 @@ :Release: |release| Overview ---------- +-------- -Python package for utilizing, implementing, and building quantum algorithms in OpenQASM 3. +`qBraid Algorithms `_ is a Python package designed for quantum algorithm development, implementation, and execution. Built on the `OpenQASM3 `_ standard, this library provides researchers, developers, and quantum computing enthusiasts with a robust toolkit for exploring and deploying quantum algorithms across various domains. +**Key Features:** + +* **Comprehensive Algorithm Library**: Implementation of fundamental quantum algorithms including Grover's search, Quantum Fourier Transform (QFT), Quantum Phase Estimation (QPE), and advanced techniques like amplitude amplification and Hamiltonian evolution. + +* **OpenQASM 3 Integration**: Native support for OpenQASM 3, enabling seamless integration with modern quantum hardware and simulators while maintaining compatibility with the evolving quantum computing ecosystem. + +* **Modular Architecture**: Clean, modular design that allows for easy extension, customization, and integration into existing quantum computing workflows. + +* **Research-Ready Implementation**: Optimized for both educational purposes and cutting-edge research, with implementations suitable for near-term quantum devices (NISQ era) and fault-tolerant quantum computers. + +* **Hardware Agnostic**: Designed to work across different quantum computing platforms and simulators, providing flexibility in deployment and testing. Installation ------------- @@ -77,7 +88,7 @@ qbraid-algorithms requires Python 3.11 or greater, and can be installed with pip Install from Source -^^^^^^^^^^^^^^^^^^^^ +-------------------- You can also install from source by cloning this repository and running a pip install command in the root directory of the repository: diff --git a/qbraid_algorithms/__init__.py b/qbraid_algorithms/__init__.py index 6c0bcd4..b69d776 100644 --- a/qbraid_algorithms/__init__.py +++ b/qbraid_algorithms/__init__.py @@ -13,28 +13,30 @@ # limitations under the License. """ -Python package containing quantum and hybrid quantum-classical algorithms that can -be used to carry out research and investigate how to solve problems in different -domains on simulators and near-term real quantum devices using shallow circuits. - -.. currentmodule:: qbraid_algorithms - -Modules -------- - -.. autosummary:: - :toctree: ../stubs/ - - bernstein_vazirani - qft - iqft - qpe - qtran - hhl - evolution - embedding - amplitude_amplification - rodeo +.. admonition:: qBraid Algorithms + :class: note-enhanced + + Python package containing quantum and hybrid quantum-classical algorithms that can + be used to carry out research and investigate how to solve problems in different + domains on simulators and near-term real quantum devices using shallow circuits. + +.. admonition:: Modules + :class: seealso + + .. autosummary:: + :toctree: ../stubs/ + + bells_inequality + bernstein_vazirani + qft + iqft + qpe + qtran + hhl + evolution + embedding + amplitude_amplification + rodeo """ @@ -42,14 +44,15 @@ __all__ = [ "__version__", + "bells_inequality", "qft", "iqft", "bernstein_vazirani", "qpe", "qtran", - 'evolution', - 'embedding', - 'amplitude_amplification', - 'hhl', - 'rodeo' + "evolution", + "embedding", + "amplitude_amplification", + "hhl", + "rodeo", ] diff --git a/qbraid_algorithms/amplitude_amplification/__init__.py b/qbraid_algorithms/amplitude_amplification/__init__.py index 2d28049..bf7e99a 100644 --- a/qbraid_algorithms/amplitude_amplification/__init__.py +++ b/qbraid_algorithms/amplitude_amplification/__init__.py @@ -12,21 +12,64 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -Module providing Amplitude Amplification implementation. +r""" +Amplitude Amplification Algorithm + +.. admonition:: Amplitude Amplification + :class: note-enhanced + + This module provides implementations of amplitude amplification algorithms, including + Grover's algorithm and general amplitude amplification techniques. + **Grover's algorithm** provides quadratic speedup for searching unsorted databases by + repeatedly applying an oracle that marks target states and diffusion operator that + inverts amplitudes about average. **General Amplitude Amplification** works with + arbitrary oracles and state preparation operators. + +.. admonition:: FORMULATION + :class: seealso + + .. container:: side-by-side + + .. container:: left-box + + .. admonition:: Grover's + :class: note + + For a search space of :math:`N` items containing :math:`M` target solutions: + + * **Oracle operator**: :math:`O_f = I - 2|f\rangle\langle f|` ``marks target states`` + * **Diffusion operator**: :math:`D = 2|s\rangle\langle s| - I` where + :math:`|s\rangle = \frac{1}{\sqrt{N}}\sum_{i=0}^{N-1}|i\rangle` + * **Grover operator**: :math:`G = D \cdot O_f` + * **Optimal iterations**: :math:`k \approx \frac{\pi}{4}\sqrt{\frac{N}{M}}` + * **Success probability**: approaches 1 after :math:`k` iterations + + .. container:: right-box + + .. admonition:: General Amplitude Amplification + :class: note + + For initial state :math:`|\psi\rangle` and target subspace spanned by good states: + + * **State preparation**: :math:`A|\psi\rangle = \sqrt{1-a}|\psi_0\rangle + \sqrt{a}|\psi_1\rangle` + * **Reflection operators**: + + - :math:`S_{\psi} = I - 2|\psi\rangle\langle\psi|` ``reflects about initial state`` + - :math:`S_{\chi} = I - 2|\chi\rangle\langle\chi|` ``reflects about good states`` + + * **Amplitude amplification operator**: :math:`Q = -A S_{\psi} A^{\dagger} S_{\chi}` + * **Amplified amplitude**: grows as :math:`\sin((2k+1)\theta)` where :math:`\sin^2(\theta) = a` -Functions ----------- +.. admonition:: Classes + :class: seealso -.. autosummary:: - :toctree: ../stubs/ + .. autosummary:: + :toctree: ../stubs/ - AALibrary + AALibrary """ from .amp_ampl import AALibrary -__all__ = [ - "AALibrary" -] +__all__ = ["AALibrary"] diff --git a/qbraid_algorithms/amplitude_amplification/amp_ampl.py b/qbraid_algorithms/amplitude_amplification/amp_ampl.py index e42316c..0f654da 100644 --- a/qbraid_algorithms/amplitude_amplification/amp_ampl.py +++ b/qbraid_algorithms/amplitude_amplification/amp_ampl.py @@ -11,23 +11,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -''' -Amplitude Amplification Library for Quantum Algorithms -This module implements amplitude amplification techniques for quantum algorithms, -including Grover's algorithm and general amplitude amplification. It provides -the `AALibrary` class, which extends `GateLibrary` to offer reusable quantum -subroutines for amplifying the probability amplitudes of desired quantum states. -Classes: - AALibrary(GateLibrary): - Implements Grover's algorithm and general amplitude amplification. -Usage: - - Use `grover` for unstructured search problems. - - Use `amp_ampl` for general amplitude amplification with arbitrary oracles and state preparation. -Notes: - - The library uses subroutine-based implementations for compact qasm code generation. - - Multi-controlled Z gates are used for phase inversion in the diffusion operator. - - The code is designed to be extensible for other amplitude amplification algorithms. -''' +""" +Amplitude Amplification algorithm implementation + +""" from typing import List from qbraid_algorithms.qtran import GateBuilder, GateLibrary, std_gates @@ -36,13 +23,15 @@ # pylint: disable=invalid-name # mypy: disable_error_code="call-arg" + class AALibrary(GateLibrary): """ - Amplitude Amplification Library implementing Grover's algorithm and general amplitude amplification. + Amplitude Amplification Library that implements Grover's algorithm and general amplitude amplification This library provides quantum algorithms for amplitude amplification, including: - - Grover's algorithm for unstructured search - - General amplitude amplification for arbitrary oracles + + - Grover's algorithm for unstructured search + - General amplitude amplification for arbitrary oracles Both algorithms use the principle of selective phase rotation to amplify desired quantum state amplitudes while suppressing unwanted ones. @@ -66,8 +55,8 @@ def grover(self, H, qubits: List[int], depth: int) -> None: The algorithm structure: 1. Initialize qubits in superposition with Hadamard gates 2. Repeat depth times: - - Apply oracle H (marks target states) - - Apply diffusion operator (inverts amplitudes about average) + - Apply oracle H (marks target states) + - Apply diffusion operator (inverts amplitudes about average) Args: H: Oracle/Hamiltonian that marks target states @@ -75,7 +64,7 @@ def grover(self, H, qubits: List[int], depth: int) -> None: depth: Number of Grover iterations to perform """ # Generate unique subroutine name based on parameters - name = f'Grover{len(qubits)}{H.name}{depth}' + name = f"Grover{len(qubits)}{H.name}{depth}" # Check if subroutine already exists to avoid regeneration if name in self.gate_ref: @@ -131,8 +120,10 @@ def grover(self, H, qubits: List[int], depth: int) -> None: std_library.comment("Z0") std_library.x(register) # Flip all qubits # Multi-controlled Z gate (phase flip when all qubits are |1⟩) - std_library.controlled_op("z",(f"{register}[0]", [f"{register}[{i}]" for i in range(len(qubits) - 1)]), - n=len(qubits) - 1 + std_library.controlled_op( + "z", + (f"{register}[0]", [f"{register}[{i}]" for i in range(len(qubits) - 1)]), + n=len(qubits) - 1, ) std_library.x(register) # Flip back std_library.h(register) @@ -174,7 +165,7 @@ def amp_ampl(self, Z, H, qubits: List[int], depth: int) -> None: There's a bug in the original code where 'z' is used instead of 'Z' in the name generation. This is preserved to maintain exact logic. """ - name = f'AmplAmp{len(qubits)}{Z.name}{depth}' + name = f"AmplAmp{len(qubits)}{Z.name}{depth}" # Check if subroutine already exists if name in self.gate_ref: @@ -248,7 +239,7 @@ def amp_ampl(self, Z, H, qubits: List[int], depth: int) -> None: std_library.controlled_op( "z", (f"{register}[0]", [f"{register}[{i}]" for i in range(len(qubits) - 1)]), - n=len(qubits) - 1 + n=len(qubits) - 1, ) std_library.x(register) diff --git a/qbraid_algorithms/bells_inequality/__init__.py b/qbraid_algorithms/bells_inequality/__init__.py index 4508629..d1f04b0 100644 --- a/qbraid_algorithms/bells_inequality/__init__.py +++ b/qbraid_algorithms/bells_inequality/__init__.py @@ -13,15 +13,50 @@ # limitations under the License. """ -Module providing Bell's Inequality experiment implementation. +Bell's Inequality Experiment -Functions ----------- +.. admonition:: Bell's Inequality Experiment + :class: note-enhanced -.. autosummary:: - :toctree: ../stubs/ + This module provides an implementation of Bell's inequality experiment, a fundamental + test of quantum mechanics that demonstrates **Non-local correlations**. + The experiment creates an entangled Bell state, applies different measurement bases + to each qubit, and measures correlations. + When Bell's inequality is violated, it + proves that quantum correlations cannot be explained by classical local hidden + variable theories. - load_program +.. admonition:: FORMULATION + :class: seealso + + This implementation tests Bell's inequality using three Bell singlet states prepared between qubit pairs. + + 1. **State Preparation**: Each qubit pair is prepared in the Bell singlet state: + + :math:`|\\Psi^-\\rangle = 1/\\sqrt{2}(|01\\rangle - |10\\rangle)` + 2. **Measurement Settings**: Three different measurement configurations: + + - Circuit AB: Alice measures at 0°, Bob measures at 60° (:math:`\\pi/3`) + - Circuit AC: Alice measures at 0°, Charlie measures at 120° (:math:`2\\pi/3`) + - Circuit BC: Bob measures at 60° (:math:`\\pi/3`), Charlie measures at 120° (:math:`2\\pi/3`) + 3. **Bell's Inequality**: The CHSH inequality for Bell singlet states: + + :math:`|E(0°, 60°) - E(0°, 120°) + E(60°, 120°)| \\leq 2` + + where :math:`E(\\theta_A, \\theta_B) = -\\cos(\\theta_A - \\theta_B)` is the correlation function. + 4. **Quantum Prediction**: For Bell singlet states, quantum mechanics predicts: + + :math:`|E(0°, 60°) - E(0°, 120°) + E(60°, 120°)| = |-\\cos(60°) + \\cos(120°) - \\cos(60°)| = 3 > 2` + + This violates Bell's inequality, demonstrating non-local quantum correlations. + +.. admonition:: Functions + :class: seealso + + .. autosummary:: + :toctree: ../stubs/ + + load_program """ diff --git a/qbraid_algorithms/bells_inequality/bells_inequality.py b/qbraid_algorithms/bells_inequality/bells_inequality.py index 6df5370..83340db 100644 --- a/qbraid_algorithms/bells_inequality/bells_inequality.py +++ b/qbraid_algorithms/bells_inequality/bells_inequality.py @@ -13,9 +13,8 @@ # limitations under the License. """ -Bell's Inequality Experiment Implementation +Bell's Inequality Experiment algorithm implementation -Simple functions for loading and running Bell's inequality circuits. """ from pathlib import Path @@ -27,7 +26,7 @@ def load_program() -> QasmModule: """ Load the Bell's inequality circuit as a pyqasm module. - + Returns: pyqasm module containing the Bell's inequality circuit """ diff --git a/qbraid_algorithms/bernstein_vazirani/__init__.py b/qbraid_algorithms/bernstein_vazirani/__init__.py index cd58216..c770418 100644 --- a/qbraid_algorithms/bernstein_vazirani/__init__.py +++ b/qbraid_algorithms/bernstein_vazirani/__init__.py @@ -13,18 +13,51 @@ # limitations under the License. """ -Module providing Berntstein-Vazirani algorithm implementation. +Bernstein-Vazirani Algorithm -Functions ----------- +.. admonition:: Bernstein-Vazirani + :class: note-enhanced -.. autosummary:: - :toctree: ../stubs/ + This module provides a complete implementation of the Bernstein-Vazirani algorithm, + a quantum algorithm that demonstrates quantum parallelism by determining a hidden + bit string with a single query. + The algorithm works by preparing a superposition of all possible inputs, applying + an oracle that encodes the hidden bit string, then using the inverse quantum + Fourier transform to extract the hidden string. This achieves exponential speedup + over classical algorithms that require n queries for an n-bit string. - load_program - generate_subroutine - generate_oracle +.. admonition:: FORMULATION + :class: seealso + For a hidden n-bit string :math:`s` and oracle function :math:`f(x) = s \\cdot x \\pmod{2}`: + + 1. **State Preparation**: Initialize :math:`n` qubits in superposition and ancilla in :math:`|-\\rangle`: + + :math:`H^{\\otimes n}|0\\rangle^{\\otimes n} \\otimes |-\\rangle = + \\frac{1}{\\sqrt{2^n}} \\sum_{x=0}^{2^n-1} |x\\rangle \\otimes |-\\rangle` + + 2. **Oracle Application**: Apply :math:`U_f` implementing phase kickback: + + :math:`U_f|x\\rangle|-\\rangle = (-1)^{f(x)}|x\\rangle|-\\rangle = + (-1)^{s \\cdot x}|x\\rangle|-\\rangle` + + 3. **Hadamard Transform**: Apply :math:`H^{\\otimes n}` to extract the hidden string: + + :math:`H^{\\otimes n} \\left[\\frac{1}{\\sqrt{2^n}} \\sum_{x} (-1)^{s \\cdot x}|x\\rangle\\right] = + |s\\rangle` + + Direct measurement yields the hidden string :math:`s` with probability 1, + demonstrating quantum parallelism through superposition and interference. + +.. admonition:: Functions + :class: seealso + + .. autosummary:: + :toctree: ../stubs/ + + load_program + generate_subroutine + generate_oracle """ from .bernvaz import generate_oracle, generate_subroutine, load_program diff --git a/qbraid_algorithms/bernstein_vazirani/bernvaz.py b/qbraid_algorithms/bernstein_vazirani/bernvaz.py index 312ed63..4509419 100644 --- a/qbraid_algorithms/bernstein_vazirani/bernvaz.py +++ b/qbraid_algorithms/bernstein_vazirani/bernvaz.py @@ -13,7 +13,7 @@ # limitations under the License. """ -Module providing Bernstein-Vazirani algorithm implementation. +Bernstein-Vazirani Algorithm Implementation """ import os diff --git a/qbraid_algorithms/embedding/__init__.py b/qbraid_algorithms/embedding/__init__.py index a66ed60..07fc4df 100644 --- a/qbraid_algorithms/embedding/__init__.py +++ b/qbraid_algorithms/embedding/__init__.py @@ -13,23 +13,73 @@ # limitations under the License. """ -Module providing Several different implementations of block encoding +Quantum Block Encoding and Embedding -Functions ----------- +.. admonition:: Quantum Block Encoding + :class: note-enhanced -.. autosummary:: - :toctree: ../stubs/ + This module provides implementations of quantum **block encoding** techniques for + embedding classical matrices into quantum circuits. + Block encoding is fundamental for quantum linear algebra algorithms, enabling efficient quantum implementations + of matrix operations through unitary transformations. + The module implements preparation-selection (Prep-Select) frameworks and specialized + embeddings for structured matrices like **Toeplitz**, **diagonal**, and **Pauli** forms, providing + quantum speedups for linear algebraic computations. - PrepSelLibrary - Prep - Select - PauliOperator - Toeplitz - Diagonal +.. admonition:: FORMULATION + :class: seealso + + **Block Encoding Definition**: For a matrix :math:`A \\in \\mathbb{C}^{2^n \\times 2^n}` + with :math:`\\|A\\| \\leq \\alpha`, a :math:`(\\alpha, a, \\epsilon)`-block encoding is a + unitary :math:`U` such that: + + :math:`\\langle 0^a | U | 0^a \\rangle = \\frac{A}{\\alpha} + E` + + where :math:`\\|E\\| \\leq \\epsilon` and :math:`a` is the number of ancilla qubits. + + **Preparation-Selection Framework**: + + 1. **Preparation**: Create superposition over matrix elements: + + :math:`\\text{PREP}|0\\rangle = \\sum_{j} \\sqrt{p_j} |j\\rangle` + + 2. **Selection**: Apply controlled operations based on index: + + :math:`\\text{SELECT}|j\\rangle|\\psi\\rangle = |j\\rangle U_j|\\psi\\rangle` + + 3. **Block Encoding**: Combine preparation and selection: + + :math:`U = \\text{PREP}^\\dagger \\cdot \\text{SELECT} \\cdot \\text{PREP}` + + **Specialized Embeddings**: + + - **Toeplitz**: Exploit circulant structure via quantum Fourier transforms + - **Diagonal**: Direct phase encoding for diagonal matrices + - **Pauli Decomposition**: Express operators as weighted Pauli string sums + +.. admonition:: Classes + :class: seealso + + .. autosummary:: + :toctree: ../stubs/ + + PrepSelLibrary + Prep + Select + PauliOperator + Toeplitz + Diagonal """ from .prep_sel import PauliOperator, Prep, PrepSelLibrary, Select from .toeplitz import Diagonal, Toeplitz -__all__ = ['prep_sel','Toeplitz','Prep','Select','Diagonal','PauliOperator','PrepSelLibrary'] +__all__ = [ + "prep_sel", + "Toeplitz", + "Prep", + "Select", + "Diagonal", + "PauliOperator", + "PrepSelLibrary", +] diff --git a/qbraid_algorithms/evolution/__init__.py b/qbraid_algorithms/evolution/__init__.py index 0887734..4d09cc1 100644 --- a/qbraid_algorithms/evolution/__init__.py +++ b/qbraid_algorithms/evolution/__init__.py @@ -13,19 +13,74 @@ # limitations under the License. """ -Module providing Several different implementations of hamiltonian evolution +Quantum Hamiltonian Evolution -Functions ----------- +.. admonition:: Quantum Hamiltonian Evolution + :class: note-enhanced -.. autosummary:: - :toctree: ../stubs/ + This module provides implementations of quantum **Hamiltonian evolution** algorithms + for simulating quantum systems and computing matrix functions. + These techniques are fundamental for quantum simulation, quantum chemistry, + quantum optimization, and quantum machine learning applications. + The module implements **Trotter decomposition** methods for time evolution and + **Generalized Quantum Signal Processing (GQSP)** for polynomial approximations + of Hamiltonian functions, along with test Hamiltonians for benchmarking. - GQSP - Trotter - TransverseFieldIsing - HeisenbergXYZ - FermionicHubbard +.. admonition:: FORMULATION + :class: seealso + + **Time Evolution Problem**: Given a Hamiltonian :math:`H` and time :math:`t`, + compute the unitary time evolution operator: + + :math:`U(t) = e^{-iHt}` + + **Trotter-Suzuki Decomposition**: For :math:`H = H_1 + H_2 + \\ldots + H_k`, + approximate the evolution using product formulas: + + 1. **First-order Trotter**: + + :math:`e^{-iHt} \\approx \\left(\\prod_{j=1}^k e^{-iH_j t/n}\\right)^n` + + 2. **Suzuki Higher-order**: Recursive symmetric decomposition with coefficients + :math:`p_k` and :math:`q_k`: + + :math:`S_{2k}(t) = \\prod_{j=1}^{5^{k-1}} S_2(p_k t) S_2(q_k t) S_2(p_k t)` + + **Generalized Quantum Signal Processing**: For polynomial :math:`P(x)`, + construct a quantum circuit that implements: :math:`\\langle 0| U_{\\text{GQSP}} |0\\rangle = P(H)` + using controlled rotations with optimized phase angles :math:`\\{\\phi_k\\}`: + + :math:`U_{\\text{GQSP}} = \\prod_{k=0}^d R_Y(\\phi_{2k}) R_Z(\\phi_{2k-1}) + (|1\\rangle\\langle 1| \\otimes H + |0\\rangle\\langle 0| \\otimes I)` + + **Test Hamiltonians**: Standard quantum many-body systems for benchmarking: + + - **Transverse Field Ising**: :math:`H = -J\\sum_{i} Z_i Z_{i+1} - h\\sum_i X_i` + - **Heisenberg XYZ**: :math:`H = \\sum_{i} (J_x X_i X_{i+1} + J_y Y_i Y_{i+1} + + J_z Z_i Z_{i+1})` + - **Fermionic Hubbard**: :math:`H = -t\\sum_{\\langle i,j\\rangle,\\sigma} + (c^\\dagger_{i\\sigma} c_{j\\sigma} + \\text{h.c.}) + U\\sum_i n_{i\\uparrow} n_{i\\downarrow}` + +.. admonition:: Classes + :class: seealso + + .. autosummary:: + :toctree: ../stubs/ + + GQSP + Trotter + TransverseFieldIsing + HeisenbergXYZ + FermionicHubbard + RandomizedHamiltonian + +.. admonition:: Functions + :class: seealso + + .. autosummary:: + :toctree: ../stubs/ + + create_test_hamiltonians """ from .gqsp import GQSP @@ -38,6 +93,12 @@ ) from .trotter import Trotter -__all__ = ['Trotter','GQSP','TransverseFieldIsing', - 'HeisenbergXYZ', 'FermionicHubbard', - 'RandomizedHamiltonian','create_test_hamiltonians'] +__all__ = [ + "Trotter", + "GQSP", + "TransverseFieldIsing", + "HeisenbergXYZ", + "FermionicHubbard", + "RandomizedHamiltonian", + "create_test_hamiltonians", +] diff --git a/qbraid_algorithms/hhl/__init__.py b/qbraid_algorithms/hhl/__init__.py index e7148d9..99d9c0e 100644 --- a/qbraid_algorithms/hhl/__init__.py +++ b/qbraid_algorithms/hhl/__init__.py @@ -13,17 +13,65 @@ # limitations under the License. """ -Module providing Qasm file generator for HHL +Harrow-Hassidim-Lloyd (HHL) Algorithm -Functions ----------- +.. admonition:: Harrow-Hassidim-Lloyd (HHL) + :class: note-enhanced -.. autosummary:: - :toctree: ../stubs/ + This module provides an implementation of the **Harrow-Hassidim-Lloyd (HHL)** algorithm, + a quantum algorithm for solving systems of linear equations. + The HHL algorithm offers exponential speedup over classical methods for certain + classes of sparse, well-conditioned matrices, making it fundamental for quantum + machine learning, optimization, and scientific computing applications. + The algorithm combines **quantum phase estimation**, **controlled rotations**, + and **amplitude amplification** to encode the solution of :math:`A\\mathbf{x} = \\mathbf{b}` + in quantum amplitudes, enabling efficient extraction of linear system solutions. - HHLLibrary +.. admonition:: FORMULATION + :class: seealso + + **Linear System Problem**: Given a Hermitian matrix :math:`A \\in \\mathbb{C}^{N \\times N}` + and vector :math:`|b\\rangle`, find the solution :math:`|x\\rangle` to: :math:`A|x\\rangle = |b\\rangle` + + **Algorithm Steps**: + + 1. **State Preparation**: Prepare the input state :math:`|b\\rangle` and ancilla qubits: + + :math:`|\\psi_0\\rangle = |b\\rangle \\otimes |0\\rangle_{\\text{clock}} \\otimes + |0\\rangle_{\\text{ancilla}}` + + 2. **Quantum Phase Estimation**: Apply QPE to estimate eigenvalues :math:`\\lambda_j` of :math:`A`: + + :math:`|\\psi_1\\rangle = \\sum_j \\beta_j |u_j\\rangle \\otimes |\\tilde{\\lambda}_j\\rangle \\otimes + |0\\rangle` + + 3. **Controlled Rotation**: Apply controlled rotation based on eigenvalue estimates: + + :math:`|\\psi_2\\rangle = \\sum_j \\beta_j |u_j\\rangle \\otimes |\\tilde{\\lambda}_j\\rangle \\otimes + \\left(\\sqrt{1-\\frac{C^2}{\\tilde{\\lambda}_j^2}}|0\\rangle + \\frac{C}{\\tilde{\\lambda}_j}|1 + \\rangle\\right)` + + 4. **Uncomputation**: Reverse QPE and measure ancilla in :math:`|1\\rangle` state: + + :math:`|x\\rangle = \\frac{1}{\\sqrt{\\sum_j |\\beta_j|^2/\\lambda_j^2}} \\sum_j + \\frac{\\beta_j}{\\lambda_j} |u_j\\rangle` + + **Complexity**: The algorithm achieves :math:`\\mathcal{O}(\\log(N) s^2 \\kappa^2 / \\epsilon)` + runtime, where :math:`s` is the sparsity, :math:`\\kappa` is the condition number, + and :math:`\\epsilon` is the desired precision. + + **Requirements**: Efficient state preparation for :math:`|b\\rangle`, well-conditioned + matrix :math:`A`, and sparse Hamiltonian simulation for :math:`e^{iAt}`. + +.. admonition:: Classes + :class: seealso + + .. autosummary:: + :toctree: ../stubs/ + + HHLLibrary """ from .hhl import HHLLibrary -__all__ = ['HHLLibrary'] +__all__ = ["HHLLibrary"] diff --git a/qbraid_algorithms/hhl/hhl.py b/qbraid_algorithms/hhl/hhl.py index 9d42260..a25f6bd 100644 --- a/qbraid_algorithms/hhl/hhl.py +++ b/qbraid_algorithms/hhl/hhl.py @@ -13,11 +13,8 @@ # limitations under the License. """ -HHLLibrary class provides an implementation of the HHL (Harrow-Hassidim-Lloyd) quantum algorithm - for solving linear systems using phase estimation techniques. -Methods: - HHL(a: list, b: list, clock: list): - Implements the main steps of the HHL algorithm: +Harrow-Hassidim-Lloyd (HHL) Algorithm Implementation + """ # Importing package modules @@ -26,10 +23,10 @@ class HHLLibrary(PhaseEstimationLibrary): - '''HHL library using base Phase Estimation implementation''' + """HHL library using base Phase Estimation implementation""" def HHL(self, a: list, b: list, clock: list): - ''' + """ Main implementation of the HHL algorithm Args: @@ -39,7 +36,7 @@ def HHL(self, a: list, b: list, clock: list): Returns: None - ''' + """ sys = self.builder # Access to the quantum circuit builder (assumed to be defined in the parent class) @@ -59,17 +56,17 @@ def HHL(self, a: list, b: list, clock: list): anc_c = sys.claim_clbits(1) # Allocate one classical bit for measurement result storage - Phase.phase_estimation(b,clock,a) + Phase.phase_estimation(b, clock, a) # Apply the phase estimation routine with registers (b, clock, a) - for i in range(len(clock)-1): + for i in range(len(clock) - 1): # Apply controlled rotations depending on clock qubits # Controlled rotation around Y-axis by angle pi/(2^{i+1}), where i is the clock qubit index - self.controlled_op("ry", (anc_q[0], clock[i], f'pi/(2^{i+1})')) + self.controlled_op("ry", (anc_q[0], clock[i], f"pi/(2^{i+1})")) # Controlled rotation around Y-axis, scaling by power of 2 (pi / 2^(i+1)) - Phase.inverse_op(b,clock,a) + Phase.inverse_op(b, clock, a) # Apply the inverse of phase estimation to uncompute and restore registers - self.measure(anc_q,anc_c) + self.measure(anc_q, anc_c) # Measure the ancilla qubit and store result in the classical bit diff --git a/qbraid_algorithms/iqft/__init__.py b/qbraid_algorithms/iqft/__init__.py index 8782a3a..10571dc 100644 --- a/qbraid_algorithms/iqft/__init__.py +++ b/qbraid_algorithms/iqft/__init__.py @@ -13,16 +13,58 @@ # limitations under the License. """ -Module providing Inverse Quantum Fourier Transform (QFT) algorithm implementation. +Inverse Quantum Fourier Transform (IQFT) -Functions ----------- +.. admonition:: Inverse Quantum Fourier Transform (IQFT) + :class: note-enhanced -.. autosummary:: - :toctree: ../stubs/ + This module provides an implementation of the **Inverse Quantum Fourier Transform (IQFT)**, + the inverse operation of the Quantum Fourier Transform. + The IQFT is essential for extracting information from quantum algorithms, particularly + in quantum phase estimation, Shor's algorithm, and other quantum algorithms that + require converting from the frequency domain back to the computational basis. + The IQFT transforms quantum states from Fourier basis back to computational basis, + enabling measurement and classical post-processing of quantum algorithm results. - load_program - generate_subroutine +.. admonition:: FORMULATION + :class: seealso + + **Transformation**: The IQFT is the inverse of the QFT, transforming an :math:`n`-qubit + state from the Fourier basis back to the computational basis: + + :math:`\\text{IQFT}|j\\rangle = \\frac{1}{\\sqrt{2^n}} \\sum_{k=0}^{2^n-1} + \\omega_n^{-jk} |k\\rangle` + + where :math:`\\omega_n = e^{2\\pi i/2^n}` is the primitive :math:`2^n`-th root of unity. + + **Circuit Implementation**: The IQFT circuit consists of: + + 1. **Swap Operations**: Reverse the qubit order from QFT + + 2. **Inverse Controlled Rotations**: Apply :math:`R_k^{\\dagger}` gates where: + + :math:`R_k^{\\dagger} = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{-2\\pi i/2^k} \\end{pmatrix}` + + 3. **Inverse Hadamard Gates**: Apply :math:`H^{\\dagger} = H` to each qubit + + **Matrix Form**: For :math:`n` qubits, the IQFT matrix is: + + :math:`\\text{IQFT} = \\frac{1}{\\sqrt{2^n}} \\begin{pmatrix} + 1 & 1 & 1 & \\cdots & 1 \\\\ + 1 & \\omega_n^{-1} & \\omega_n^{-2} & \\cdots & \\omega_n^{-(2^n-1)} \\\\ + \\vdots & \\vdots & \\vdots & \\ddots & \\vdots \\\\ + 1 & \\omega_n^{-(2^n-1)} & \\omega_n^{-2(2^n-1)} & \\cdots & \\omega_n^{-(2^n-1)^2} + \\end{pmatrix}` + + +.. admonition:: Functions + :class: seealso + + .. autosummary:: + :toctree: ../stubs/ + + load_program + generate_subroutine """ diff --git a/qbraid_algorithms/iqft/iqft.py b/qbraid_algorithms/iqft/iqft.py index 076fa45..3f4e55d 100644 --- a/qbraid_algorithms/iqft/iqft.py +++ b/qbraid_algorithms/iqft/iqft.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Module providing Inverse Quantum Fourier Transform (IQFT) algorithm implementation. +Inverse Quantum Fourier Transform (IQFT) Algorithm Implementation """ import os diff --git a/qbraid_algorithms/qft/__init__.py b/qbraid_algorithms/qft/__init__.py index fc5bf16..5488cac 100644 --- a/qbraid_algorithms/qft/__init__.py +++ b/qbraid_algorithms/qft/__init__.py @@ -13,25 +13,80 @@ # limitations under the License. """ -Module providing Quantum Fourier Transform (QFT) algorithm implementation. +Quantum Fourier Transform (QFT) -Functions ----------- +.. admonition:: Quantum Fourier Transform (QFT) + :class: note-enhanced -.. autosummary:: - :toctree: ../stubs/ + This module provides an implementation of the **Quantum Fourier Transform (QFT)**, + a fundamental quantum algorithm that performs the discrete Fourier transform + on quantum states. The QFT is a cornerstone of quantum computing, serving as + a key component in **Shor's algorithm**, **quantum phase estimation**, and + many other quantum algorithms requiring frequency domain analysis. + The QFT enables efficient transformation between computational and Fourier bases, + providing exponential speedup over classical FFT for certain quantum applications. - load_program - generate_subroutine - QFTLibrary +.. admonition:: FORMULATION + :class: seealso + + **Transformation**: The QFT maps an :math:`n`-qubit computational basis state + to a superposition in the Fourier basis: + + :math:`\\text{QFT}|j\\rangle = \\frac{1}{\\sqrt{2^n}} \\sum_{k=0}^{2^n-1} + \\omega_n^{jk} |k\\rangle` + + where :math:`\\omega_n = e^{2\\pi i/2^n}` is the primitive :math:`2^n`-th root of unity. + + + **Circuit Implementation**: The QFT circuit consists of: + + 1. **Hadamard Gates**: Apply :math:`H` to each qubit for uniform superposition + + 2. **Controlled Phase Rotations**: Apply :math:`R_k` gates where: + + :math:`R_k = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{2\\pi i/2^k} \\end{pmatrix}` + + 3. **Swap Operations**: Reverse qubit order for correct output + + **Recursive Structure**: For qubit :math:`j`, the QFT can be written as: + + :math:`\\text{QFT}|j\\rangle = \\frac{1}{\\sqrt{2}} + \\left(|0\\rangle + e^{2\\pi i \\cdot 0.j_n}|1\\rangle\\right) \\otimes + \\frac{1}{\\sqrt{2}} \\left(|0\\rangle + e^{2\\pi i \\cdot 0.j_{n-1}j_n}|1\\rangle\\right) + \\otimes \\ldots` + + **Matrix Form**: The QFT matrix for :math:`n` qubits is: + + :math:`\\text{QFT} = \\frac{1}{\\sqrt{2^n}} \\begin{pmatrix} + 1 & 1 & 1 & \\cdots & 1 \\\\ + 1 & \\omega_n & \\omega_n^2 & \\cdots & \\omega_n^{2^n-1} \\\\ + \\vdots & \\vdots & \\vdots & \\ddots & \\vdots \\\\ + 1 & \\omega_n^{2^n-1} & \\omega_n^{2(2^n-1)} & \\cdots & \\omega_n^{(2^n-1)^2} + \\end{pmatrix}` + + **Complexity**: Requires :math:`O(n^2)` gates compared to :math:`O(n \\log n)` + classical complexity, but enables quantum parallelism for quantum states. + +.. admonition:: Classes + :class: seealso + + .. autosummary:: + :toctree: ../stubs/ + + QFTLibrary + +.. admonition:: Functions + :class: seealso + + .. autosummary:: + :toctree: ../stubs/ + + load_program + generate_subroutine """ from .qft import generate_subroutine, load_program from .qft_lib import QFTLibrary -__all__ = [ - "load_program", - "generate_subroutine", - "QFTLibrary" -] +__all__ = ["load_program", "generate_subroutine", "QFTLibrary"] diff --git a/qbraid_algorithms/qft/qft.py b/qbraid_algorithms/qft/qft.py index e83ede9..d3742fd 100644 --- a/qbraid_algorithms/qft/qft.py +++ b/qbraid_algorithms/qft/qft.py @@ -13,8 +13,21 @@ # limitations under the License. """ -Module providing Quantum Fourier Transform (QFT) algorithm implementation. +Quantum Fourier Transform (QFT) Algorithm Implementation +This module provides a complete implementation of the Quantum Fourier Transform, +a fundamental building block for many quantum algorithms. + +The QFT transforms quantum states from the computational basis to the Fourier basis +by applying Hadamard gates and controlled phase rotations. It's essential for +algorithms like Shor's algorithm and quantum phase estimation, providing the +quantum analogue of the classical discrete Fourier transform. + +Formulation: + The QFT transforms a quantum state |j⟩ to |ψ⟩ = 1/√N Σₖ₌₀^(N-1) e^(2πijk/N)|k⟩ + where N = 2ⁿ for n qubits. The circuit implements this using Hadamard gates H + and controlled phase rotations R_k with phase φ = 2π/2^k. The total circuit + depth is O(n²) with O(n²) gates. """ import os import shutil diff --git a/qbraid_algorithms/qft/qft_lib.py b/qbraid_algorithms/qft/qft_lib.py index d93b856..3fada27 100644 --- a/qbraid_algorithms/qft/qft_lib.py +++ b/qbraid_algorithms/qft/qft_lib.py @@ -12,13 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -This module provides the QFTLibrary class for constructing and managing Quantum -Fourier Transform (QFT) gates using the qBraid algorithms framework. -Classes: - QFTLibrary(GateLibrary): -Dependencies: - - string - - qbraid_algorithms.qtran (GateBuilder, GateLibrary, std_gates) +Quantum Fourier Transform (QFT) Algorithm Implementation + """ # from QasmBuilder import FileBuilder, QasmBuilder, GateBuilder @@ -30,11 +25,13 @@ class QFTLibrary(GateLibrary): - """QFTLibrary provides methods to construct and manage - Quantum Fourier Transform (QFT) gates, extending GateLibrary + """QFTLibrary provides methods to construct and manage + Quantum Fourier Transform (QFT) gates, extending GateLibrary for use in quantum algorithms.""" + name = "QFT" - def __init__(self,*args,**kwargs): + + def __init__(self, *args, **kwargs): """ Initialize the QFTLibrary instance. @@ -42,10 +39,10 @@ def __init__(self,*args,**kwargs): *args: Variable length argument list for parent GateLibrary. **kwargs: Arbitrary keyword arguments for parent GateLibrary. """ - super().__init__(*args,**kwargs) + super().__init__(*args, **kwargs) # self.call_space = "{}" - def QFT(self, qubits:list, swap=True): + def QFT(self, qubits: list, swap=True): """ Constructs a Quantum Fourier Transform (QFT) gate for the specified qubits. @@ -59,22 +56,27 @@ def QFT(self, qubits:list, swap=True): """ name = f'QFT{len(qubits)}{"S" if swap else ""}' if name in self.gate_ref: - self.call_gate(name,qubits[-1],qubits[:-1]) + self.call_gate(name, qubits[-1], qubits[:-1]) return sys = GateBuilder() std = sys.import_library(std_gates) names = string.ascii_letters - qargs = [names[int(i/len(names))]+names[i%len(names)] for i in range(len(qubits))] + qargs = [ + names[int(i / len(names))] + names[i % len(names)] + for i in range(len(qubits)) + ] - std.begin_gate(name,qargs) + std.begin_gate(name, qargs) std.call_space = "{}" for i in range(len(qubits)): std.h(qargs[i]) - for j in range(i+1,len(qubits)): - std.call_gate("cp",qargs[j],controls=qargs[i],phases=f"pi/{2**(j-i)}") + for j in range(i + 1, len(qubits)): + std.call_gate( + "cp", qargs[j], controls=qargs[i], phases=f"pi/{2**(j-i)}" + ) if swap: - for i in range(len(qubits)//2): - std.call_gate("swap", qargs[i], qargs[-i-1]) + for i in range(len(qubits) // 2): + std.call_gate("swap", qargs[i], qargs[-i - 1]) std.end_gate() @@ -96,5 +98,5 @@ def QFT(self, qubits:list, swap=True): # std.end_loop() # std.end_subroutine() - self.merge(*sys.build(),name) - self.call_gate(name,qubits[-1],qubits[:-1]) + self.merge(*sys.build(), name) + self.call_gate(name, qubits[-1], qubits[:-1]) diff --git a/qbraid_algorithms/qpe/__init__.py b/qbraid_algorithms/qpe/__init__.py index 996e846..918cade 100644 --- a/qbraid_algorithms/qpe/__init__.py +++ b/qbraid_algorithms/qpe/__init__.py @@ -13,22 +13,80 @@ # limitations under the License. """ -Module providing Quantum Phase Estimation (QPE) algorithm implementation. +Quantum Phase Estimation (QPE) -Functions ----------- +.. admonition:: Quantum Phase Estimation (QPE) + :class: note-enhanced -.. autosummary:: - :toctree: ../stubs/ + This module provides an implementation of the **Quantum Phase Estimation (QPE)** algorithm, + a fundamental quantum algorithm for estimating eigenvalues of unitary operators. + QPE is central to many quantum algorithms including **Shor's algorithm**, **HHL algorithm**, + and quantum simulation, providing exponential precision improvement over classical methods. + The algorithm uses **controlled unitary operations** and the **quantum Fourier transform** + to extract phase information with high precision, enabling efficient eigenvalue + computation for quantum systems and linear algebra applications. - load_program - generate_subroutine - get_result +.. admonition:: FORMULATION + :class: seealso + **Problem**: Given a unitary operator :math:`U` and eigenstate :math:`|u\\rangle` such that + :math:`U|u\\rangle = e^{2\\pi i \\varphi}|u\\rangle`, estimate the phase :math:`\\varphi`. + + **Algorithm Steps**: + + 1. **Initialization**: Prepare :math:`n` ancilla qubits in superposition and eigenstate: + + :math:`|\\psi_0\\rangle = \\frac{1}{\\sqrt{2^n}} \\sum_{j=0}^{2^n-1} |j\\rangle \\otimes |u\\rangle` + + 2. **Controlled Unitary Evolution**: Apply controlled :math:`U^{2^k}` operations: + + :math:`|\\psi_1\\rangle = \\frac{1}{\\sqrt{2^n}} \\sum_{j=0}^{2^n-1} + e^{2\\pi i \\varphi j} |j\\rangle \\otimes |u\\rangle` + + 3. **Inverse QFT**: Apply IQFT to ancilla register: + + :math:`|\\psi_2\\rangle = |\\tilde{\\varphi}\\rangle \\otimes |u\\rangle` + + 4. **Measurement**: Measure ancilla to obtain :math:`n`-bit approximation :math:`\\tilde{\\varphi}` + + **Precision**: With :math:`n` ancilla qubits, QPE estimates :math:`\\varphi` to + :math:`n`-bit precision with high probability: + + :math:`|\\varphi - \\tilde{\\varphi}| \\leq \\frac{1}{2^n}` + + **Success Probability**: For exact phases representable in :math:`n` bits, + success probability is 1. For general phases, success probability + :math:`\\geq \\frac{4}{\\pi^2} \\approx 0.405`. + + **Circuit Depth**: Requires :math:`O(n^2)` gates and :math:`O(n)` applications + of controlled-:math:`U^{2^k}` operations. + +.. admonition:: Classes + :class: seealso + + .. autosummary:: + :toctree: ../stubs/ + + PhaseEstimationLibrary + +.. admonition:: Functions + :class: seealso + + .. autosummary:: + :toctree: ../stubs/ + + load_program + generate_subroutine + get_result """ from .phase_est import PhaseEstimationLibrary from .qpe import generate_subroutine, get_result, load_program -__all__ = ["load_program", "generate_subroutine", "get_result",'PhaseEstimationLibrary'] +__all__ = [ + "load_program", + "generate_subroutine", + "get_result", + "PhaseEstimationLibrary", +] diff --git a/qbraid_algorithms/qpe/phase_est.py b/qbraid_algorithms/qpe/phase_est.py index 01e38aa..0945a4e 100644 --- a/qbraid_algorithms/qpe/phase_est.py +++ b/qbraid_algorithms/qpe/phase_est.py @@ -13,12 +13,8 @@ # limitations under the License. """ -PhaseEstLibrary +Quantum Phase Estimation Algorithm Implementation -This module defines the PhaseEstimationLibrary class, which provides methods for -constructing quantum phase estimation circuits. Supports both static and time-dependent -Hamiltonians, and is designed for direct implementation of classical phase estimation -algorithms. Iterative phase estimation is planned via the Rodeo package. """ import string @@ -34,15 +30,18 @@ class PhaseEstimationLibrary(GateLibrary): - ''' + """ Library to implement phase estimation circuits directly related to classical phase estimation algorithms. Iterative phase estimation will be supported via the Rodeo package. This library supports both static and time-dependent Hamiltonians. - ''' - def __init__(self,*args,**kwargs): - super().__init__(*args,**kwargs) + """ - def phase_estimation(self, qubits:list,spectra:list,hamiltonian, evolution=None): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def phase_estimation( + self, qubits: list, spectra: list, hamiltonian, evolution=None + ): """ Implements the quantum phase estimation algorithm using the provided qubits, ancilla clock register (spectra), and Hamiltonian. @@ -63,9 +62,9 @@ def phase_estimation(self, qubits:list,spectra:list,hamiltonian, evolution=None) # yet established within this system and will need to be rewritten when that's established. # TODO: Change to work within subroutine scope for improved modularity. - name = f'P_EST_{len(qubits)}_{hamiltonian.name}' + name = f"P_EST_{len(qubits)}_{hamiltonian.name}" if name in self.gate_ref: - self.call_gate(name,spectra[-1],qubits+spectra[:-1]) + self.call_gate(name, spectra[-1], qubits + spectra[:-1]) return name sys = GateBuilder() std = sys.import_library(std_gates) @@ -74,29 +73,34 @@ def phase_estimation(self, qubits:list,spectra:list,hamiltonian, evolution=None) qft = sys.import_library(QFTLibrary) qft.call_space = " {}" names = string.ascii_letters - qargs = [names[int(i/len(names))]+names[i%len(names)] for i in range(len(qubits)+len(spectra))] + qargs = [ + names[int(i / len(names))] + names[i % len(names)] + for i in range(len(qubits) + len(spectra)) + ] # std.begin_gate(name,[f"qubit[{len(qubits)}] a",f"qubit[{len(spectra)}] b"]) - std.begin_gate(name,qargs) + std.begin_gate(name, qargs) for i in range(len(spectra)): if evolution is not None: - ham.controlled(evolution*2**i,qargs[:len(qubits)],qargs[len(qubits)+i]) + ham.controlled( + evolution * 2**i, qargs[: len(qubits)], qargs[len(qubits) + i] + ) else: for _ in range(2**i): - ham.controlled(qargs[:len(qubits)],qargs[len(qubits)+i]) - qft.QFT(qargs[len(qubits):]) + ham.controlled(qargs[: len(qubits)], qargs[len(qubits) + i]) + qft.QFT(qargs[len(qubits) :]) std.end_gate() - self.merge(*sys.build(),name) - self.call_gate(name,spectra[-1],qubits+spectra[:-1]) + self.merge(*sys.build(), name) + self.call_gate(name, spectra[-1], qubits + spectra[:-1]) return name - def inverse_op(self, qubits:list,spectra:list,hamiltonian, evolution=None): + def inverse_op(self, qubits: list, spectra: list, hamiltonian, evolution=None): """ Implements the inverse (reversed) sequence for the application of phase estimation. """ - name = f'Pest_INV_{len(qubits)}_{hamiltonian.name}' + name = f"Pest_INV_{len(qubits)}_{hamiltonian.name}" if name in self.gate_ref: - self.call_gate(name,spectra[-1],qubits+spectra[:-1]) + self.call_gate(name, spectra[-1], qubits + spectra[:-1]) return name sys = GateBuilder() std = sys.import_library(std_gates) @@ -106,19 +110,24 @@ def inverse_op(self, qubits:list,spectra:list,hamiltonian, evolution=None): qft.call_space = " {}" names = string.ascii_letters - qargs = [names[int(i/len(names))]+names[i%len(names)] for i in range(len(qubits)+len(spectra))] + qargs = [ + names[int(i / len(names))] + names[i % len(names)] + for i in range(len(qubits) + len(spectra)) + ] # std.begin_gate(name,[f"qubit[{len(qubits)}] a",f"qubit[{len(spectra)}] b"]) - std.begin_gate(name,qargs) - qft.inverse_op(qft.QFT, (qargs[len(qubits):],)) + std.begin_gate(name, qargs) + qft.inverse_op(qft.QFT, (qargs[len(qubits) :],)) for i in reversed(range(len(spectra))): if evolution is not None: - ham.controlled(-evolution*2**i,qargs[:len(qubits)],qargs[len(qubits)+i]) + ham.controlled( + -evolution * 2**i, qargs[: len(qubits)], qargs[len(qubits) + i] + ) else: # Apply controlled gates in reverse order for proper inversion for _ in range(2**i): - ham.controlled(qargs[:len(qubits)],qargs[len(qubits)+i]) + ham.controlled(qargs[: len(qubits)], qargs[len(qubits) + i]) std.end_gate() - self.merge(*sys.build(),name) - self.call_gate(name,spectra[-1],qubits+spectra[:-1]) + self.merge(*sys.build(), name) + self.call_gate(name, spectra[-1], qubits + spectra[:-1]) return name diff --git a/qbraid_algorithms/qpe/qpe.py b/qbraid_algorithms/qpe/qpe.py index 6842013..1048d1f 100644 --- a/qbraid_algorithms/qpe/qpe.py +++ b/qbraid_algorithms/qpe/qpe.py @@ -13,8 +13,21 @@ # limitations under the License. """ -Module providing Quantum Phase Estimation algorithm implementation. +Quantum Phase Estimation (QPE) Algorithm Implementation +This module provides a complete implementation of the Quantum Phase Estimation algorithm, +a fundamental quantum algorithm that estimates the eigenvalues of unitary operators. + +QPE estimates the phase φ of an eigenvalue e^(2πiφ) by preparing ancilla qubits in +superposition, applying controlled powers of the unitary operator, then using the +inverse quantum Fourier transform to extract the phase. It's a key subroutine in +Shor's algorithm, quantum simulation, and quantum machine learning. + +Mathematical Formulation: + Given a unitary operator U with eigenstate |ψ⟩ such that U|ψ⟩ = e^(2πiφ)|ψ⟩, QPE + prepares the state 1/√(2^t) Σⱼ₌₀^(2^t-1) |j⟩|ψ⟩, applies controlled-U^(2^j) operations, + then applies IQFT to the first register. The final measurement yields φ with + precision t bits and success probability ≥ 4/π² ≈ 0.405. """ import os import shutil diff --git a/qbraid_algorithms/qtran/__init__.py b/qbraid_algorithms/qtran/__init__.py index 7ccc267..fc1c311 100644 --- a/qbraid_algorithms/qtran/__init__.py +++ b/qbraid_algorithms/qtran/__init__.py @@ -13,25 +13,80 @@ # limitations under the License. """ -Module providing Qasm file generator Qasmbuilder, and base class GateLibrary acting as a macro system on top - -Functions ----------- - -.. autosummary:: - :toctree: ../stubs/ - - FileBuilder - GateBuilder - QasmBuilder - IncludeBuilder - GateLibrary - std_gates - +Quantum Algorithm Translation and QASM Generation + +.. admonition:: Quantum Translation Framework (QTRAN) + :class: note-enhanced + + This module provides a comprehensive framework for **quantum algorithm translation** + and **QASM code generation**. QTRAN enables systematic construction of quantum + circuits through high-level abstractions, automated gate library management, + and optimized QASM subroutine generation for quantum algorithms. + The framework supports **modular circuit design**, **gate library composition**, + and **cross-platform quantum code generation**, facilitating rapid development + and deployment of quantum algorithms across different quantum hardware platforms. + +.. admonition:: FORMULATION + :class: seealso + + **Circuit Construction**: QTRAN provides builders for systematic quantum circuit assembly: + + **Gate Library Framework**: Organize quantum operations into reusable libraries: + + :math:`\\mathcal{L} = \\{G_1, G_2, \\ldots, G_n\\}` + + where each gate :math:`G_i` represents a quantum operation with associated parameters. + + **Circuit Builder Pattern**: Construct circuits through compositional operations: + + :math:`C = B(G_1 \\circ G_2 \\circ \\ldots \\circ G_n)` + + where :math:`B` is the builder function and :math:`\\circ` denotes gate composition. + + **QASM Generation**: Transform high-level circuit descriptions to executable QASM: + + :math:`\\text{QASM} = T(C, \\mathcal{P})` + + where :math:`T` is the translation function and :math:`\\mathcal{P}` are platform parameters. + + **Key Components**: + + - **FileBuilder**: Manages QASM file structure and includes + - **GateBuilder**: Constructs individual quantum gates with parameters + - **QasmBuilder**: Assembles complete QASM programs from components + - **IncludeBuilder**: Handles library imports and dependencies + - **GateLibrary**: Provides extensible gate operation collections + - **std_gates**: Standard quantum gate implementations + + **Abstraction Levels**: Supports multiple abstraction levels from low-level + gate operations to high-level algorithm subroutines, enabling both + fine-grained control and rapid prototyping. + +.. admonition:: Classes + :class: seealso + + .. autosummary:: + :toctree: ../stubs/ + + FileBuilder + GateBuilder + QasmBuilder + IncludeBuilder + GateLibrary + std_gates + """ # pylint: disable=invalid-name from .gate_library import GateLibrary, std_gates from .module_loader import qasm_pipe from .qasm_builder import FileBuilder, GateBuilder, IncludeBuilder, QasmBuilder -__all__ = ['FileBuilder', 'QasmBuilder','GateBuilder','IncludeBuilder','GateLibrary','std_gates','qasm_pipe'] +__all__ = [ + "FileBuilder", + "QasmBuilder", + "GateBuilder", + "IncludeBuilder", + "GateLibrary", + "std_gates", + "qasm_pipe", +] diff --git a/qbraid_algorithms/rodeo/__init__.py b/qbraid_algorithms/rodeo/__init__.py index 6db6305..e1effe2 100644 --- a/qbraid_algorithms/rodeo/__init__.py +++ b/qbraid_algorithms/rodeo/__init__.py @@ -13,17 +13,67 @@ # limitations under the License. """ -Module providing QFT algorithmic primitive implementation. +Rodeo Algorithm -Functions ----------- +.. admonition:: Rodeo + :class: note-enhanced -.. autosummary:: - :toctree: ../stubs/ + This module provides an implementation of the **Rodeo algorithm**, a quantum + algorithm for estimating expectation values of observables with high precision. + The Rodeo algorithm is particularly useful for **quantum chemistry**, **quantum simulation**, + and **variational quantum algorithms** where accurate expectation value estimation + is crucial for optimization and ground state preparation. + The algorithm uses **controlled Hamiltonian evolution** with **random phase shifts** + and **ancilla measurements** to extract expectation values with reduced resource + requirements compared to standard quantum expectation value estimation methods. - RodeoLibrary +.. admonition:: FORMULATION + :class: seealso + + **Problem**: Given a quantum state :math:`|\\psi\\rangle` and observable :math:`O`, + estimate the expectation value :math:`\\langle\\psi|O|\\psi\\rangle`. + + **Algorithm Steps**: + + 1. **Ancilla Preparation**: Prepare ancilla qubit in superposition: + + :math:`|+\\rangle = \\frac{1}{\\sqrt{2}}(|0\\rangle + |1\\rangle)` + + 2. **Controlled Evolution**: Apply controlled Hamiltonian evolution with random time :math:`t`: + + :math:`|\\psi_1\\rangle = \\frac{1}{\\sqrt{2}}(|0\\rangle|\\psi\\rangle + + |1\\rangle e^{-iOt}|\\psi\\rangle)` + + 3. **Ancilla Rotation**: Apply Hadamard to ancilla for interference: + + :math:`|\\psi_2\\rangle = \\frac{1}{2}[(1 + e^{-iOt})|0\\rangle|\\psi\\rangle + + (1 - e^{-iOt})|1\\rangle|\\psi\\rangle]` + + 4. **Measurement**: Measure ancilla and extract expectation value from statistics + + **Expectation Value Extraction**: For small evolution times :math:`t`, the expectation + value is estimated as: + + :math:`\\langle\\psi|O|\\psi\\rangle = \\lim_{t \\to 0} \\frac{1}{t} \\arcsin(\\sqrt{P_1})` + + where :math:`P_1` is the probability of measuring :math:`|1\\rangle` in the ancilla. + + **Variance Reduction**: The algorithm achieves improved precision through: + + - **Random Sampling**: Multiple evolution times reduce systematic errors + - **Statistical Averaging**: Ensemble measurements improve accuracy + - **Controlled Evolution**: Direct access to Hamiltonian eigenvalue information + + +.. admonition:: Classes + :class: seealso + + .. autosummary:: + :toctree: ../stubs/ + + RodeoLibrary """ from .rodeo import RodeoLibrary -__all__ = ['RodeoLibrary'] +__all__ = ["RodeoLibrary"] diff --git a/qbraid_algorithms/rodeo/rodeo.py b/qbraid_algorithms/rodeo/rodeo.py index 3309b84..b48d4fd 100644 --- a/qbraid_algorithms/rodeo/rodeo.py +++ b/qbraid_algorithms/rodeo/rodeo.py @@ -11,19 +11,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -''' -Module: rodeo.py -This module implements the Rodeo algorithm for quantum state preparation and -amplitude amplification using the qBraid quantum programming framework. -It provides a specialized quantum gate library, `RodeoLibrary`, which extends the -base `GateLibrary` to support Rodeo-based quantum operations. -Classes: - RodeoLibrary(GateLibrary): -Dependencies: - - random - - string - - qbraid_algorithms.qtran (GateBuilder, GateLibrary, std_gates) -''' +""" +Rodeo Algorithm Implementation + +""" import random import string @@ -40,10 +31,11 @@ class RodeoLibrary(GateLibrary): quantum state preparation. It uses ancilla qubits and controlled operations to selectively amplify desired quantum states. """ - def __init__(self,*args,**kwargs): - super().__init__(*args,**kwargs) - def rodeo(self, qubits:list,t,depth: int,hamiltonian, evolution=None): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def rodeo(self, qubits: list, t, depth: int, hamiltonian, evolution=None): """ Implement the Rodeo algorithm with multiple ancilla qubits. @@ -61,13 +53,13 @@ def rodeo(self, qubits:list,t,depth: int,hamiltonian, evolution=None): Returns: str: Name of the created gate for potential reuse """ - name = f'Rodeo{depth}_{len(qubits)}_{hamiltonian.name}' + name = f"Rodeo{depth}_{len(qubits)}_{hamiltonian.name}" anc_q = self.builder.claim_qubits(depth) anc_c = self.builder.claim_clbits(depth) - self.comment(f'rodeo call {name} ancillas q:{anc_q} c:{anc_c}') + self.comment(f"rodeo call {name} ancillas q:{anc_q} c:{anc_c}") if name in self.gate_ref: - self.call_gate(name,qubits[-1],anc_q+qubits[:-1],t) - self.measure(anc_q,anc_c) + self.call_gate(name, qubits[-1], anc_q + qubits[:-1], t) + self.measure(anc_q, anc_c) return name sys = GateBuilder() std = sys.import_library(std_gates) @@ -75,28 +67,31 @@ def rodeo(self, qubits:list,t,depth: int,hamiltonian, evolution=None): ham = sys.import_library(hamiltonian) ham.call_space = " {}" names = string.ascii_letters - qargs = [names[int(i/len(names))]+names[i%len(names)] for i in range(len(qubits)+depth)] + qargs = [ + names[int(i / len(names))] + names[i % len(names)] + for i in range(len(qubits) + depth) + ] - s = [2*random.random()-2 for d in range(depth)] - std.begin_gate(name,qargs,params='t') + s = [2 * random.random() - 2 for d in range(depth)] + std.begin_gate(name, qargs, params="t") for i in range(depth): std.h(qargs[i]) if evolution is not None: - ham.controlled(s[i],qargs[depth:],qargs[i]) - std.phase(f'{s[i]}*{t}',qargs[i]) + ham.controlled(s[i], qargs[depth:], qargs[i]) + std.phase(f"{s[i]}*{t}", qargs[i]) else: - ham.controlled(qargs[depth:],qargs[i]) - std.phase(f'{t}',qargs[i]) + ham.controlled(qargs[depth:], qargs[i]) + std.phase(f"{t}", qargs[i]) std.h(qargs[i]) std.end_gate() - self.merge(*sys.build(),name) + self.merge(*sys.build(), name) - self.call_gate(name,qubits[-1],anc_q+qubits[:-1],t) - self.measure(anc_q,anc_c) + self.call_gate(name, qubits[-1], anc_q + qubits[:-1], t) + self.measure(anc_q, anc_c) return name - def rodeo_mcm(self, qubits:list,t,depth: int,hamiltonian, evolution=None): + def rodeo_mcm(self, qubits: list, t, depth: int, hamiltonian, evolution=None): """ Implement the Rodeo algorithm with mid-circuit measurements (MCM). @@ -114,11 +109,11 @@ def rodeo_mcm(self, qubits:list,t,depth: int,hamiltonian, evolution=None): Returns: str: Name of the created gate for potential reuse """ - name = f'Rodeo_{len(qubits)}_{hamiltonian.name}' + name = f"Rodeo_{len(qubits)}_{hamiltonian.name}" anc_q = self.builder.claim_qubits(1) anc_c = self.builder.claim_clbits(1) - self.comment(f'rodeo call {name} ancillas q:{anc_q} c:{anc_c}') - s = [str(2*random.random()-1) for d in range(depth)] + self.comment(f"rodeo call {name} ancillas q:{anc_q} c:{anc_c}") + s = [str(2 * random.random() - 1) for d in range(depth)] # TODO: re-add var once array initializations work so the full cnf of rodeo is actually # applied (otherwise its just novel kitaev phase est) # ts= self.add_var( @@ -129,8 +124,8 @@ def rodeo_mcm(self, qubits:list,t,depth: int,hamiltonian, evolution=None): if name in self.gate_ref: # self.begin_loop(("float",ts)) self.begin_loop(depth) - self.call_gate(name,qubits[-1],anc_q+qubits[:-1],t) - self.measure(anc_q,anc_c) + self.call_gate(name, qubits[-1], anc_q + qubits[:-1], t) + self.measure(anc_q, anc_c) self.begin_if(f"cb{anc_c} == true") self.program("break;") self.end_if() @@ -143,24 +138,27 @@ def rodeo_mcm(self, qubits:list,t,depth: int,hamiltonian, evolution=None): ham = sys.import_library(hamiltonian) ham.call_space = " {}" names = string.ascii_letters - qargs = [names[int(i/len(names))]+names[i%len(names)] for i in range(len(qubits)+1)] - std.begin_gate(name,qargs,params='t') + qargs = [ + names[int(i / len(names))] + names[i % len(names)] + for i in range(len(qubits) + 1) + ] + std.begin_gate(name, qargs, params="t") std.h(qargs[0]) if evolution is not None: - ham.controlled(s[0],qargs[1:],qargs[0]) - std.phase(f'{s[0]}*{t}',qargs[0]) + ham.controlled(s[0], qargs[1:], qargs[0]) + std.phase(f"{s[0]}*{t}", qargs[0]) else: - ham.controlled(qargs[1:],qargs[0]) - std.phase(f'{t}',qargs[0]) + ham.controlled(qargs[1:], qargs[0]) + std.phase(f"{t}", qargs[0]) std.h(qargs[0]) std.end_gate() - self.merge(*sys.build(),name) + self.merge(*sys.build(), name) # self.begin_loop(("float",ts)) self.begin_loop(depth) - self.call_gate(name,qubits[-1],anc_q+qubits[:-1],t) - self.measure(anc_q,anc_c) + self.call_gate(name, qubits[-1], anc_q + qubits[:-1], t) + self.measure(anc_q, anc_c) self.begin_if(f"cb{anc_c} == true") self.program("break;") self.end_if() From 54ab13a76274db9257df91321a353798a99e3b20 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Tue, 23 Sep 2025 08:37:54 -0500 Subject: [PATCH 3/5] Refactor algorithm loading functions in README and codebase --- .../algorithm-implementation.md | 4 +- README.md | 12 +- examples/QPE/qpe.ipynb | 1192 ++++++++--------- examples/bells_inequality.ipynb | 6 +- examples/bernstein_vazirani.ipynb | 1010 +++++++------- examples/qft.ipynb | 944 ++++++------- .../bells_inequality/__init__.py | 6 +- .../bells_inequality/bells_inequality.py | 4 +- .../bernstein_vazirani/__init__.py | 8 +- .../bernstein_vazirani/bernvaz.py | 16 +- qbraid_algorithms/cli/generate.py | 8 +- qbraid_algorithms/embedding/prep_sel.py | 113 +- qbraid_algorithms/embedding/toeplitz.py | 27 +- qbraid_algorithms/evolution/gqsp.py | 45 +- qbraid_algorithms/evolution/h_test_suite.py | 94 +- qbraid_algorithms/evolution/trotter.py | 45 +- qbraid_algorithms/iqft/__init__.py | 8 +- qbraid_algorithms/iqft/iqft.py | 12 +- .../bells_inequality.qasm | 0 .../bernvaz.qasm | 0 .../bernvaz_subroutine.qasm | 0 .../{iqft => qasm_resources}/iqft.qasm | 0 .../iqft_subroutine.qasm | 0 .../oracle.qasm | 0 .../{qft => qasm_resources}/qft.qasm | 0 .../qft_subroutine.qasm | 0 .../{qpe => qasm_resources}/qpe.qasm | 0 .../qpe_subroutine.qasm | 0 qbraid_algorithms/qft/__init__.py | 8 +- qbraid_algorithms/qft/qft.py | 10 +- qbraid_algorithms/qpe/__init__.py | 10 +- qbraid_algorithms/qpe/qpe.py | 14 +- qbraid_algorithms/qtran/gate_library.py | 146 +- qbraid_algorithms/qtran/module_loader.py | 11 +- qbraid_algorithms/qtran/qasm_builder.py | 50 +- tests/test_bells_inequality.py | 6 +- tests/test_bernvaz.py | 28 +- tests/test_builder_algorithms.py | 176 ++- tests/test_builder_statics.py | 22 +- tests/test_cli.py | 8 +- tests/test_iqft.py | 20 +- tests/test_qasmbuilder.py | 112 +- tests/test_qft.py | 34 +- tests/test_qpe.py | 30 +- 44 files changed, 2209 insertions(+), 2030 deletions(-) rename qbraid_algorithms/{bells_inequality => qasm_resources}/bells_inequality.qasm (100%) rename qbraid_algorithms/{bernstein_vazirani => qasm_resources}/bernvaz.qasm (100%) rename qbraid_algorithms/{bernstein_vazirani => qasm_resources}/bernvaz_subroutine.qasm (100%) rename qbraid_algorithms/{iqft => qasm_resources}/iqft.qasm (100%) rename qbraid_algorithms/{iqft => qasm_resources}/iqft_subroutine.qasm (100%) rename qbraid_algorithms/{bernstein_vazirani => qasm_resources}/oracle.qasm (100%) rename qbraid_algorithms/{qft => qasm_resources}/qft.qasm (100%) rename qbraid_algorithms/{qft => qasm_resources}/qft_subroutine.qasm (100%) rename qbraid_algorithms/{qpe => qasm_resources}/qpe.qasm (100%) rename qbraid_algorithms/{qpe => qasm_resources}/qpe_subroutine.qasm (100%) diff --git a/.github/ISSUE_TEMPLATE/algorithm-implementation.md b/.github/ISSUE_TEMPLATE/algorithm-implementation.md index 0787978..ebe5009 100644 --- a/.github/ISSUE_TEMPLATE/algorithm-implementation.md +++ b/.github/ISSUE_TEMPLATE/algorithm-implementation.md @@ -8,8 +8,8 @@ assignees: '' --- **Required functions to implement:** -- [ ] `load_program()` - Load complete executable circuit -- [ ] `generate_subroutine()` - Generate reusable subroutine +- [ ] `generate_program()` - Load complete executable circuit +- [ ] `save_to_qasm()` - Generate reusable subroutine - [ ] Additional functions: _______________ **CLI integration needed:** diff --git a/README.md b/README.md index 8f00911..258d5e8 100644 --- a/README.md +++ b/README.md @@ -65,12 +65,12 @@ you can generate .qasm files to use them as subroutines in your own circuits. ### Loading Algorithms as PyQASM Modules -To load an algorithm as a PyQASM module, use the `load_program` function from the `qbraid_algorithms` package, passing algorithm-specific parameters. For example, to load the Quantum Fourier Transform (QFT) algorithm: +To load an algorithm as a PyQASM module, use the `generate_program` function from the `qbraid_algorithms` package, passing algorithm-specific parameters. For example, to load the Quantum Fourier Transform (QFT) algorithm: ```python from qbraid_algorithms import qft -qft_module = qft.load_program(3) # Load QFT for 3 qubits +qft_module = qft.generate_program(3) # Load QFT for 3 qubits ``` Now, you can perform operations with the PyQASM module, such as unrolling, and @@ -84,15 +84,15 @@ qasm_str = pyqasm.dumps(qft_module) ### Loading Algorithms as `.qasm` Files In order to utilize algorithms as subroutines in your own circuits, use the -`generate_subroutine` function for your desired algorithm. By passing algorithm-specific parameters, and optionally a desired output path, you can +`save_to_qasm` function for your desired algorithm. By passing algorithm-specific parameters, and optionally a desired output path, you can generate a .qasm file containing a subroutine for the paramterized circuit. For example, to generate a QFT subroutine for 4 qubits: ```python from qbraid_algorithms import qft, iqft path = "path/to/output" # Specify your desired output path -qft.generate_subroutine(4) # Generate 4-qubit QFT in the current directory -iqft.generate_subroutine(4, path=path) # Generate 4-qubit IQFT in specified path +qft.save_to_qasm(4) # Generate 4-qubit QFT in the current directory +iqft.save_to_qasm(4, path=path) # Generate 4-qubit IQFT in specified path ``` @@ -101,7 +101,7 @@ To utilize the generated subroutine in your own circuit, include the generated when generating the subroutine. For example, after running ```python -qft.generate_subroutine(4) +qft.save_to_qasm(4) ``` you can append `include "qft.qasm";` to your OpenQASM file, and call the diff --git a/examples/QPE/qpe.ipynb b/examples/QPE/qpe.ipynb index d7c16a9..5083079 100644 --- a/examples/QPE/qpe.ipynb +++ b/examples/QPE/qpe.ipynb @@ -1,597 +1,597 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": 69, - "id": "a42257b1", - "metadata": {}, - "outputs": [], - "source": [ - "import sys\n", - "import os\n", - "\n", - "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '../..')))" - ] - }, - { - "cell_type": "markdown", - "id": "405f6563", - "metadata": {}, - "source": [ - "## Quantum Phase Estimation (QPE)\n", - "#### In this tutorial, we will demonstrate the capabilities of qBraid Algorithms Quantum Phase Estimation Module\n", - "Begin by importing the module from qBraid Algorithms library" - ] - }, - { - "cell_type": "code", - "execution_count": 70, - "id": "0a281dc2", - "metadata": {}, - "outputs": [], - "source": [ - "import pyqasm\n", - "from qbraid_algorithms import qpe" - ] - }, - { - "cell_type": "markdown", - "id": "727d132b", - "metadata": {}, - "source": [ - "To load a full QPE algorithm circuit as a PyQASM module, pass a path to the unitary U to the `load_program()` method - this shoulds be defined as a valid custom QASM gate. Additionally, pass a custom gate that prepares your eigenstate. For example, to set the eigenstate to |1$\\rangle$, simply define and pass the following .qasm file:\n", - "```\n", - "OPENQASM 3.0;\n", - "include \"stdgates.inc\";\n", - "\n", - "gate prep q {\n", - " x q;\n", - "}\n", - "```\n", - "Note, the gate names in both cases can be anything." - ] - }, - { - "cell_type": "code", - "execution_count": 71, - "id": "82d18afa", - "metadata": {}, - "outputs": [], - "source": [ - "module = qpe.load_program(\"unitary.qasm\", \"prepare_state.qasm\")" - ] - }, - { - "cell_type": "markdown", - "id": "57bc257e", - "metadata": {}, - "source": [ - "We can perform all standard [PyQASM](https://docs.qbraid.com/pyqasm/user-guide/overview) operations on the module, such as unrolling." - ] - }, - { - "cell_type": "code", - "execution_count": 72, - "id": "c294fd7f", - "metadata": {}, - "outputs": [], - "source": [ - "module.unroll()" - ] - }, - { - "cell_type": "markdown", - "id": "01a23645", - "metadata": {}, - "source": [ - "Below, we display the unrolled circuits." - ] - }, - { - "cell_type": "code", - "execution_count": 73, - "id": "046b193b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\n", - "include \"stdgates.inc\";\n", - "qubit[4] q;\n", - "qubit[1] psi;\n", - "bit[4] b;\n", - "x psi[0];\n", - "h q[0];\n", - "h q[1];\n", - "h q[2];\n", - "h q[3];\n", - "cz q[0], psi[0];\n", - "cz q[1], psi[0];\n", - "cz q[1], psi[0];\n", - "cz q[2], psi[0];\n", - "cz q[2], psi[0];\n", - "cz q[2], psi[0];\n", - "cz q[2], psi[0];\n", - "cz q[3], psi[0];\n", - "cz q[3], psi[0];\n", - "cz q[3], psi[0];\n", - "cz q[3], psi[0];\n", - "cz q[3], psi[0];\n", - "cz q[3], psi[0];\n", - "cz q[3], psi[0];\n", - "cz q[3], psi[0];\n", - "h q[3];\n", - "rz(-0.7853981633974483) q[3];\n", - "rx(1.5707963267948966) q[3];\n", - "rz(3.141592653589793) q[3];\n", - "rx(1.5707963267948966) q[3];\n", - "rz(3.141592653589793) q[3];\n", - "cx q[3], q[2];\n", - "rz(0.7853981633974483) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "cx q[3], q[2];\n", - "rz(-0.7853981633974483) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "h q[2];\n", - "rz(-0.39269908169872414) q[3];\n", - "rx(1.5707963267948966) q[3];\n", - "rz(3.141592653589793) q[3];\n", - "rx(1.5707963267948966) q[3];\n", - "rz(3.141592653589793) q[3];\n", - "cx q[3], q[1];\n", - "rz(0.39269908169872414) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "cx q[3], q[1];\n", - "rz(-0.39269908169872414) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rz(-0.7853981633974483) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "cx q[2], q[1];\n", - "rz(0.7853981633974483) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "cx q[2], q[1];\n", - "rz(-0.7853981633974483) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "h q[1];\n", - "rz(-0.19634954084936207) q[3];\n", - "rx(1.5707963267948966) q[3];\n", - "rz(3.141592653589793) q[3];\n", - "rx(1.5707963267948966) q[3];\n", - "rz(3.141592653589793) q[3];\n", - "cx q[3], q[0];\n", - "rz(0.19634954084936207) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "cx q[3], q[0];\n", - "rz(-0.19634954084936207) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rz(-0.39269908169872414) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "cx q[2], q[0];\n", - "rz(0.39269908169872414) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "cx q[2], q[0];\n", - "rz(-0.39269908169872414) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rz(-0.7853981633974483) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "cx q[1], q[0];\n", - "rz(0.7853981633974483) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "cx q[1], q[0];\n", - "rz(-0.7853981633974483) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "h q[0];\n", - "swap q[0], q[3];\n", - "swap q[1], q[2];\n", - "b[0] = measure q[0];\n", - "b[1] = measure q[1];\n", - "b[2] = measure q[2];\n", - "b[3] = measure q[3];\n", - "\n" - ] - } - ], - "source": [ - "module_str = pyqasm.dumps(module)\n", - "print(module_str)" - ] - }, - { - "cell_type": "markdown", - "id": "858d7bf8", - "metadata": {}, - "source": [ - "## Using QPE in your own OpenQASM3 program\n", - "#### qBraid algorithms makes it easy to incorporate QPE into your own OpenQASM3 circuit.\n", - "To use the QPE algorithm within your own circuit, simply pass the path to your pre-defined unitary operation to the `generate_subroutine` function. The function will generate a subroutine containing QPE for your unitary within a QASM file located in your current working directory, or any path of your choice." - ] - }, - { - "cell_type": "code", - "execution_count": 74, - "id": "1efed244", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Subroutine 'qpe' has been added to /Users/lukeandreesen/qbraid_algos/examples/QPE/qpe.qasm\n" - ] - } - ], - "source": [ - "qpe.generate_subroutine(\"unitary.qasm\")" - ] - }, - { - "cell_type": "markdown", - "id": "8738adb4", - "metadata": {}, - "source": [ - "To use the subroutine in your own circuit, add `include \"qpe.qasm\";` to your OpenQASM file, and call the `qpe` method by passing an appropriately sized register of qubits, as well as an ancilla qubit." - ] - }, - { - "cell_type": "code", - "execution_count": 75, - "id": "72a9dbe9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\r\n", - "include \"stdgates.inc\";\r\n", - "include \"iqft.qasm\";\r\n", - "\r\n", - "gate custom_t q {\r\n", - " z q;\r\n", - "}\r\n", - "\r\n", - "gate CU a, b {\r\n", - " ctrl @ custom_t a, b;\r\n", - "}\r\n", - " \r\n", - "\r\n", - "def qpe(qubit[4] q, qubit[1] psi) {\r\n", - " int n = 4;\r\n", - " for int i in [0:n-1] {\r\n", - " h q[i];\r\n", - " }\r\n", - " for int j in [0:n-1] {\r\n", - " int[16] k = 1 << j;\r\n", - " for int m in [0:k-1] {\r\n", - " CU q[j], psi[0];\r\n", - " }\r\n", - " }\r\n", - " iqft(q);\r\n", - "\r\n", - "}" - ] - } - ], - "source": [ - "%cat qpe.qasm" - ] - }, - { - "cell_type": "markdown", - "id": "7d939af7", - "metadata": {}, - "source": [ - "## Running Algorithms on qBraid\n", - "Running algorithms on qBraid is simple. First, import QbraidProvider from qBraid Runtime. Visit [here](https://docs.qbraid.com/sdk/user-guide/providers/native#qbraidprovider) for more information." - ] - }, - { - "cell_type": "markdown", - "id": "6401942b", - "metadata": {}, - "source": [ - "### Sample Problem: QPE with |1⟩ and the Z Gate\n", - "\n", - "The **Pauli-Z** gate is defined as:\n", - "\n", - "$$\n", - "Z =\n", - "\\begin{bmatrix}\n", - "1 & 0 \\\\\n", - "0 & -1\n", - "\\end{bmatrix}\n", - "$$\n", - "\n", - "#### 1. Eigenvalues and Eigenstates\n", - "- $|0\\rangle$ → eigenvalue $+1 = e^{2\\pi i \\cdot 0}$ → phase $\\phi = 0$ \n", - "- $|1\\rangle$ → eigenvalue $-1 = e^{i\\pi} = e^{2\\pi i \\cdot \\frac12}$ → phase $\\phi = 0.5$\n", - "\n", - "Since our input state is $|1\\rangle$, we are in an **eigenstate** of $Z$ with eigenvalue $-1$.\n", - "\n", - "#### 2. Phase Interpretation\n", - "In QPE, the unitary’s eigenvalue is written as:\n", - "\n", - "$$\n", - "\\lambda = e^{2\\pi i \\phi}\n", - "$$\n", - "\n", - "For $\\lambda = -1$:\n", - "\n", - "$$\n", - "-1 = e^{i\\pi} = e^{2\\pi i \\cdot \\frac12}\n", - "$$\n", - "$$\n", - "\\Rightarrow \\phi = \\frac12\n", - "$$\n", - "\n", - "#### 3. QPE Output with 3 Counting Qubits\n", - "- $n = 3$ → precision is $2^3 = 8$ possible values \n", - "- $\\phi = 0.5$ in binary with 3 bits is:\n", - "$$\n", - "0.5 = 0.100_2\n", - "$$\n", - "- QPE measurement register will give **`100`** with probability $1$.\n", - "\n", - "---\n", - "\n", - "**Final result:** \n", - "For $|1\\rangle$ and $Z$, QPE perfectly returns `100` for 3 counting qubits, corresponding to a phase of **0.5**.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 76, - "id": "74c3cd37", - "metadata": {}, - "outputs": [], - "source": [ - "from qbraid.runtime import QbraidProvider" - ] - }, - { - "cell_type": "markdown", - "id": "14200703", - "metadata": {}, - "source": [ - "If you have not yet configured QbraidProvider, provide your API key." - ] - }, - { - "cell_type": "code", - "execution_count": 77, - "id": "9d35aeaa", - "metadata": {}, - "outputs": [], - "source": [ - "# provider = QbraidProvider(api_key='API_KEY')\n", - "provider = QbraidProvider()" - ] - }, - { - "cell_type": "markdown", - "id": "011126c3", - "metadata": {}, - "source": [ - "We'll run our program on qBraid's QIR simulator." - ] - }, - { - "cell_type": "code", - "execution_count": 78, - "id": "9023abed", - "metadata": {}, - "outputs": [], - "source": [ - "device = provider.get_device('qbraid_qir_simulator')" - ] - }, - { - "cell_type": "code", - "execution_count": 79, - "id": "3fd289f7", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[, , , , , , , , ]\n" - ] - } - ], - "source": [ - "print(provider.get_devices())" - ] - }, - { - "cell_type": "markdown", - "id": "76f7ad3a", - "metadata": {}, - "source": [ - "To run the job, simply pass a QASM string to the device. If you have a PyQASM module, use `dumps` to generate a QASM string" - ] - }, - { - "cell_type": "code", - "execution_count": 80, - "id": "4532cf30", - "metadata": {}, - "outputs": [], - "source": [ - "module = qpe.load_program(\"unitary.qasm\", \"prepare_state.qasm\", num_qubits=3)\n", - "module.unroll()\n", - "qasm_str = pyqasm.dumps(module)" - ] - }, - { - "cell_type": "code", - "execution_count": 81, - "id": "434bf090", - "metadata": {}, - "outputs": [], - "source": [ - "job = device.run(qasm_str, shots=500)" - ] - }, - { - "cell_type": "markdown", - "id": "713d7e0d", - "metadata": {}, - "source": [ - "Endianess is flipped for this machine - so we'll reverse the Qubit order; we also receive measurement results for the Ancilla qubit, which we'll drop here." - ] - }, - { - "cell_type": "code", - "execution_count": 82, - "id": "4dffb435", - "metadata": {}, - "outputs": [], - "source": [ - "results = job.result()\n", - "counts = results.data.get_counts()\n", - "# Drop the ancilla qubit\n", - "counts = {bitstr[:-1]: count for bitstr, count in counts.items()}\n", - "# Reverse the qubit order\n", - "counts = {bitstr[::-1]: count for bitstr, count in counts.items()}\n", - "\n", - "result = qpe.get_eigenvalue(counts)" - ] - }, - { - "cell_type": "markdown", - "id": "8ee6aa91", - "metadata": {}, - "source": [ - "Below, we show our succesfuly result of 0.5" - ] - }, - { - "cell_type": "code", - "execution_count": 83, - "id": "25c8c8fb", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.5\n" - ] - } - ], - "source": [ - "print(result)" - ] - }, - { - "cell_type": "markdown", - "id": "75dfbb27", - "metadata": {}, - "source": [ - "Finally, we can plot the results using qBraid Visualization." - ] - }, - { - "cell_type": "code", - "execution_count": 84, - "id": "6ea15fac", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from qbraid.visualization import plot_histogram\n", - "\n", - "plot_histogram(counts)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "14285905", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.11.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} + "cells": [ + { + "cell_type": "code", + "execution_count": 69, + "id": "a42257b1", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "\n", + "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '../..')))" + ] + }, + { + "cell_type": "markdown", + "id": "405f6563", + "metadata": {}, + "source": [ + "## Quantum Phase Estimation (QPE)\n", + "#### In this tutorial, we will demonstrate the capabilities of qBraid Algorithms Quantum Phase Estimation Module\n", + "Begin by importing the module from qBraid Algorithms library" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "0a281dc2", + "metadata": {}, + "outputs": [], + "source": [ + "import pyqasm\n", + "from qbraid_algorithms import qpe" + ] + }, + { + "cell_type": "markdown", + "id": "727d132b", + "metadata": {}, + "source": [ + "To load a full QPE algorithm circuit as a PyQASM module, pass a path to the unitary U to the `generate_program()` method - this shoulds be defined as a valid custom QASM gate. Additionally, pass a custom gate that prepares your eigenstate. For example, to set the eigenstate to |1$\\rangle$, simply define and pass the following .qasm file:\n", + "```\n", + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "\n", + "gate prep q {\n", + " x q;\n", + "}\n", + "```\n", + "Note, the gate names in both cases can be anything." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "82d18afa", + "metadata": {}, + "outputs": [], + "source": [ + "module = qpe.generate_program(\"unitary.qasm\", \"prepare_state.qasm\")" + ] + }, + { + "cell_type": "markdown", + "id": "57bc257e", + "metadata": {}, + "source": [ + "We can perform all standard [PyQASM](https://docs.qbraid.com/pyqasm/user-guide/overview) operations on the module, such as unrolling." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "c294fd7f", + "metadata": {}, + "outputs": [], + "source": [ + "module.unroll()" + ] + }, + { + "cell_type": "markdown", + "id": "01a23645", + "metadata": {}, + "source": [ + "Below, we display the unrolled circuits." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "046b193b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "qubit[4] q;\n", + "qubit[1] psi;\n", + "bit[4] b;\n", + "x psi[0];\n", + "h q[0];\n", + "h q[1];\n", + "h q[2];\n", + "h q[3];\n", + "cz q[0], psi[0];\n", + "cz q[1], psi[0];\n", + "cz q[1], psi[0];\n", + "cz q[2], psi[0];\n", + "cz q[2], psi[0];\n", + "cz q[2], psi[0];\n", + "cz q[2], psi[0];\n", + "cz q[3], psi[0];\n", + "cz q[3], psi[0];\n", + "cz q[3], psi[0];\n", + "cz q[3], psi[0];\n", + "cz q[3], psi[0];\n", + "cz q[3], psi[0];\n", + "cz q[3], psi[0];\n", + "cz q[3], psi[0];\n", + "h q[3];\n", + "rz(-0.7853981633974483) q[3];\n", + "rx(1.5707963267948966) q[3];\n", + "rz(3.141592653589793) q[3];\n", + "rx(1.5707963267948966) q[3];\n", + "rz(3.141592653589793) q[3];\n", + "cx q[3], q[2];\n", + "rz(0.7853981633974483) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "cx q[3], q[2];\n", + "rz(-0.7853981633974483) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "h q[2];\n", + "rz(-0.39269908169872414) q[3];\n", + "rx(1.5707963267948966) q[3];\n", + "rz(3.141592653589793) q[3];\n", + "rx(1.5707963267948966) q[3];\n", + "rz(3.141592653589793) q[3];\n", + "cx q[3], q[1];\n", + "rz(0.39269908169872414) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "cx q[3], q[1];\n", + "rz(-0.39269908169872414) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "rz(-0.7853981633974483) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "cx q[2], q[1];\n", + "rz(0.7853981633974483) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "cx q[2], q[1];\n", + "rz(-0.7853981633974483) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "h q[1];\n", + "rz(-0.19634954084936207) q[3];\n", + "rx(1.5707963267948966) q[3];\n", + "rz(3.141592653589793) q[3];\n", + "rx(1.5707963267948966) q[3];\n", + "rz(3.141592653589793) q[3];\n", + "cx q[3], q[0];\n", + "rz(0.19634954084936207) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "cx q[3], q[0];\n", + "rz(-0.19634954084936207) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rz(-0.39269908169872414) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "cx q[2], q[0];\n", + "rz(0.39269908169872414) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "cx q[2], q[0];\n", + "rz(-0.39269908169872414) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rz(-0.7853981633974483) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "cx q[1], q[0];\n", + "rz(0.7853981633974483) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "cx q[1], q[0];\n", + "rz(-0.7853981633974483) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "h q[0];\n", + "swap q[0], q[3];\n", + "swap q[1], q[2];\n", + "b[0] = measure q[0];\n", + "b[1] = measure q[1];\n", + "b[2] = measure q[2];\n", + "b[3] = measure q[3];\n", + "\n" + ] + } + ], + "source": [ + "module_str = pyqasm.dumps(module)\n", + "print(module_str)" + ] + }, + { + "cell_type": "markdown", + "id": "858d7bf8", + "metadata": {}, + "source": [ + "## Using QPE in your own OpenQASM3 program\n", + "#### qBraid algorithms makes it easy to incorporate QPE into your own OpenQASM3 circuit.\n", + "To use the QPE algorithm within your own circuit, simply pass the path to your pre-defined unitary operation to the `save_to_qasm` function. The function will generate a subroutine containing QPE for your unitary within a QASM file located in your current working directory, or any path of your choice." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "1efed244", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Subroutine 'qpe' has been added to /Users/lukeandreesen/qbraid_algos/examples/QPE/qpe.qasm\n" + ] + } + ], + "source": [ + "qpe.save_to_qasm(\"unitary.qasm\")" + ] + }, + { + "cell_type": "markdown", + "id": "8738adb4", + "metadata": {}, + "source": [ + "To use the subroutine in your own circuit, add `include \"qpe.qasm\";` to your OpenQASM file, and call the `qpe` method by passing an appropriately sized register of qubits, as well as an ancilla qubit." + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "72a9dbe9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\r\n", + "include \"stdgates.inc\";\r\n", + "include \"iqft.qasm\";\r\n", + "\r\n", + "gate custom_t q {\r\n", + " z q;\r\n", + "}\r\n", + "\r\n", + "gate CU a, b {\r\n", + " ctrl @ custom_t a, b;\r\n", + "}\r\n", + " \r\n", + "\r\n", + "def qpe(qubit[4] q, qubit[1] psi) {\r\n", + " int n = 4;\r\n", + " for int i in [0:n-1] {\r\n", + " h q[i];\r\n", + " }\r\n", + " for int j in [0:n-1] {\r\n", + " int[16] k = 1 << j;\r\n", + " for int m in [0:k-1] {\r\n", + " CU q[j], psi[0];\r\n", + " }\r\n", + " }\r\n", + " iqft(q);\r\n", + "\r\n", + "}" + ] + } + ], + "source": [ + "%cat qpe.qasm" + ] + }, + { + "cell_type": "markdown", + "id": "7d939af7", + "metadata": {}, + "source": [ + "## Running Algorithms on qBraid\n", + "Running algorithms on qBraid is simple. First, import QbraidProvider from qBraid Runtime. Visit [here](https://docs.qbraid.com/sdk/user-guide/providers/native#qbraidprovider) for more information." + ] + }, + { + "cell_type": "markdown", + "id": "6401942b", + "metadata": {}, + "source": [ + "### Sample Problem: QPE with |1⟩ and the Z Gate\n", + "\n", + "The **Pauli-Z** gate is defined as:\n", + "\n", + "$$\n", + "Z =\n", + "\\begin{bmatrix}\n", + "1 & 0 \\\\\n", + "0 & -1\n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "#### 1. Eigenvalues and Eigenstates\n", + "- $|0\\rangle$ → eigenvalue $+1 = e^{2\\pi i \\cdot 0}$ → phase $\\phi = 0$ \n", + "- $|1\\rangle$ → eigenvalue $-1 = e^{i\\pi} = e^{2\\pi i \\cdot \\frac12}$ → phase $\\phi = 0.5$\n", + "\n", + "Since our input state is $|1\\rangle$, we are in an **eigenstate** of $Z$ with eigenvalue $-1$.\n", + "\n", + "#### 2. Phase Interpretation\n", + "In QPE, the unitary’s eigenvalue is written as:\n", + "\n", + "$$\n", + "\\lambda = e^{2\\pi i \\phi}\n", + "$$\n", + "\n", + "For $\\lambda = -1$:\n", + "\n", + "$$\n", + "-1 = e^{i\\pi} = e^{2\\pi i \\cdot \\frac12}\n", + "$$\n", + "$$\n", + "\\Rightarrow \\phi = \\frac12\n", + "$$\n", + "\n", + "#### 3. QPE Output with 3 Counting Qubits\n", + "- $n = 3$ → precision is $2^3 = 8$ possible values \n", + "- $\\phi = 0.5$ in binary with 3 bits is:\n", + "$$\n", + "0.5 = 0.100_2\n", + "$$\n", + "- QPE measurement register will give **`100`** with probability $1$.\n", + "\n", + "---\n", + "\n", + "**Final result:** \n", + "For $|1\\rangle$ and $Z$, QPE perfectly returns `100` for 3 counting qubits, corresponding to a phase of **0.5**.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "74c3cd37", + "metadata": {}, + "outputs": [], + "source": [ + "from qbraid.runtime import QbraidProvider" + ] + }, + { + "cell_type": "markdown", + "id": "14200703", + "metadata": {}, + "source": [ + "If you have not yet configured QbraidProvider, provide your API key." + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "9d35aeaa", + "metadata": {}, + "outputs": [], + "source": [ + "# provider = QbraidProvider(api_key='API_KEY')\n", + "provider = QbraidProvider()" + ] + }, + { + "cell_type": "markdown", + "id": "011126c3", + "metadata": {}, + "source": [ + "We'll run our program on qBraid's QIR simulator." + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "9023abed", + "metadata": {}, + "outputs": [], + "source": [ + "device = provider.get_device('qbraid_qir_simulator')" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "3fd289f7", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[, , , , , , , , ]\n" + ] + } + ], + "source": [ + "print(provider.get_devices())" + ] + }, + { + "cell_type": "markdown", + "id": "76f7ad3a", + "metadata": {}, + "source": [ + "To run the job, simply pass a QASM string to the device. If you have a PyQASM module, use `dumps` to generate a QASM string" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "4532cf30", + "metadata": {}, + "outputs": [], + "source": [ + "module = qpe.generate_program(\"unitary.qasm\", \"prepare_state.qasm\", num_qubits=3)\n", + "module.unroll()\n", + "qasm_str = pyqasm.dumps(module)" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "434bf090", + "metadata": {}, + "outputs": [], + "source": [ + "job = device.run(qasm_str, shots=500)" + ] + }, + { + "cell_type": "markdown", + "id": "713d7e0d", + "metadata": {}, + "source": [ + "Endianess is flipped for this machine - so we'll reverse the Qubit order; we also receive measurement results for the Ancilla qubit, which we'll drop here." + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "4dffb435", + "metadata": {}, + "outputs": [], + "source": [ + "results = job.result()\n", + "counts = results.data.get_counts()\n", + "# Drop the ancilla qubit\n", + "counts = {bitstr[:-1]: count for bitstr, count in counts.items()}\n", + "# Reverse the qubit order\n", + "counts = {bitstr[::-1]: count for bitstr, count in counts.items()}\n", + "\n", + "result = qpe.get_eigenvalue(counts)" + ] + }, + { + "cell_type": "markdown", + "id": "8ee6aa91", + "metadata": {}, + "source": [ + "Below, we show our succesfuly result of 0.5" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "25c8c8fb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.5\n" + ] + } + ], + "source": [ + "print(result)" + ] + }, + { + "cell_type": "markdown", + "id": "75dfbb27", + "metadata": {}, + "source": [ + "Finally, we can plot the results using qBraid Visualization." + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "6ea15fac", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from qbraid.visualization import plot_histogram\n", + "\n", + "plot_histogram(counts)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14285905", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/examples/bells_inequality.ipynb b/examples/bells_inequality.ipynb index 273e3de..1f7ad1b 100644 --- a/examples/bells_inequality.ipynb +++ b/examples/bells_inequality.ipynb @@ -5,7 +5,7 @@ "execution_count": 1, "id": "70d162e3", "metadata": { - "hide_input": true + "hide_input": true }, "outputs": [], "source": [ @@ -28,12 +28,12 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "b514527a", "metadata": {}, "outputs": [], "source": [ - "program = bells_inequality.load_program()" + "program = bells_inequality.generate_program()" ] }, { diff --git a/examples/bernstein_vazirani.ipynb b/examples/bernstein_vazirani.ipynb index c4f3b2a..c0fc967 100644 --- a/examples/bernstein_vazirani.ipynb +++ b/examples/bernstein_vazirani.ipynb @@ -1,506 +1,506 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "405809fb", - "metadata": { - "tags": [ - "hide_cell" - ] - }, - "outputs": [], - "source": [ - "import sys\n", - "import os\n", - "\n", - "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '..')))" - ] - }, - { - "cell_type": "markdown", - "id": "d3b8876c", - "metadata": {}, - "source": [ - "## Bernstein Vazirani Algorithm\n", - "#### In this tutorial, we will demonstrate the capabilities of qBraid Algorithms Bernstein Vazirani Module\n", - "Begin by importing the module from qBraid Algorithms library" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e939fb23", - "metadata": {}, - "outputs": [], - "source": [ - "import pyqasm\n", - "from qbraid_algorithms import bernstein_vazirani as bv" - ] - }, - { - "cell_type": "markdown", - "id": "3545fac9", - "metadata": {}, - "source": [ - "To load a full Bernstein Vazirani algorithm circuit as a PyQASM module, simply pass the secret string to be encoded in the oracle to the `load_program()` method." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "97649823", - "metadata": {}, - "outputs": [], - "source": [ - "module = bv.load_program('1011')" - ] - }, - { - "cell_type": "markdown", - "id": "7e2ac473", - "metadata": {}, - "source": [ - "We can perform all standard [PyQASM](https://docs.qbraid.com/pyqasm/user-guide/overview) operations on the module, such as unrolling." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "0fb825a7", - "metadata": {}, - "outputs": [], - "source": [ - "module.unroll()" - ] - }, - { - "cell_type": "markdown", - "id": "287b4aa8", - "metadata": {}, - "source": [ - "Below, we display the unrolled circuit, which includes preparing the input and ancilla qubits, applying the '1011' oracle, applying Hadamard gates after the oracle, then measuring the results." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "31842ddc", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\n", - "include \"stdgates.inc\";\n", - "qubit[1] ancilla;\n", - "qubit[4] q;\n", - "bit[4] b;\n", - "h q[0];\n", - "h q[1];\n", - "h q[2];\n", - "h q[3];\n", - "x ancilla[0];\n", - "h ancilla[0];\n", - "cx q[0], ancilla[0];\n", - "cx q[2], ancilla[0];\n", - "cx q[3], ancilla[0];\n", - "h q[0];\n", - "h q[1];\n", - "h q[2];\n", - "h q[3];\n", - "b[0] = measure q[0];\n", - "b[1] = measure q[1];\n", - "b[2] = measure q[2];\n", - "b[3] = measure q[3];\n", - "\n" - ] - } - ], - "source": [ - "module_str = pyqasm.dumps(module)\n", - "print(module_str)" - ] - }, - { - "cell_type": "markdown", - "id": "21906ab0", - "metadata": {}, - "source": [ - "## Using B-V in your own OpenQASM3 program\n", - "#### qBraid algorithms makes it easy to incorporate either the full Bernstein Vazirani algorithm - or just the encoded oracle - into your own OpenQASM3 circuit.\n", - "To use a secret string-encoded oracle in your circuit, first generate the oracle submodule using the `generate_oracle` method, which takes a secret string. The method will create a QASM3 file containing your oracle as a subroutine within your current working directory." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "6f3e9784", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Oracle 'oracle' has been added to /Users/lukeandreesen/qbraid_algos/examples/oracle.qasm\n" - ] - } - ], - "source": [ - "bv.generate_oracle('111')" - ] - }, - { - "cell_type": "markdown", - "id": "61817da7", - "metadata": {}, - "source": [ - "Below, we can see the custom oracle subroutine that you now have access to. To use this in your own circuit, simply add `include \"oracle.qasm\";` to your OpenQASM file, and call the `oracle` subroutine by passing an appropriately sized register of qubits and an ancilla qubit." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "8833f4b9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\r\n", - "include \"stdgates.inc\";\r\n", - "\r\n", - "def oracle(qubit[3] q, qubit[1] ancilla) {\r\n", - " int[32] s = 7;\r\n", - " int[16] n = 3;\r\n", - " for int i in [0:n - 1] {\r\n", - " if ((s >> i) & 1) {\r\n", - " cx q[i], ancilla[0];\r\n", - " }\r\n", - " }\r\n", - "}\r\n" - ] - } - ], - "source": [ - "%cat oracle.qasm" - ] - }, - { - "cell_type": "markdown", - "id": "3672d119", - "metadata": {}, - "source": [ - "Similarly, you can generate an entire Bernstein Vazirani circuit as a submodule using the `generate_subroutine` method, again passing your desired secret string." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "5e37f60d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Subroutine 'bernvaz' has been added to /Users/lukeandreesen/qbraid_algos/examples/bernvaz.qasm\n" - ] - } - ], - "source": [ - "subroutine = bv.generate_subroutine('011')" - ] - }, - { - "cell_type": "markdown", - "id": "05cfcdf4", - "metadata": {}, - "source": [ - "To use the subroutine in your own circuit, add `include \"bernvaz.qasm\";` to your OpenQASM file, and call the `bernvaz` method by passing an appropriately sized register of qubits, as well as an ancilla qubit." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "38b7beb3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\r\n", - "include \"stdgates.inc\";\r\n", - "\r\n", - "def bernvaz(qubit[3] q, qubit[1] ancilla) {\r\n", - " int[32] s = 6;\r\n", - " int[16] n = 3;\r\n", - " for int i in [0:n - 1] {\r\n", - " h q[i];\r\n", - " }\r\n", - " x ancilla[0];\r\n", - " h ancilla[0];\r\n", - " // Note: Nested subroutine calls not yet supported by QASM, so manually insert\r\n", - " for int i in [0:n - 1] {\r\n", - " if ((s >> i) & 1) {\r\n", - " cx q[i], ancilla[0];\r\n", - " }\r\n", - " }\r\n", - " for int i in [0:n - 1] {\r\n", - " h q[i];\r\n", - " }\r\n", - "\r\n", - "}\r\n" - ] - } - ], - "source": [ - "%cat bernvaz.qasm" - ] - }, - { - "cell_type": "markdown", - "id": "e7d3dd7e", - "metadata": {}, - "source": [ - "## Running Algorithms on qBraid\n", - "Running algorithms on qBraid is simple. First, import QbraidProvider from qBraid Runtime. Visit [here](https://docs.qbraid.com/sdk/user-guide/providers/native#qbraidprovider) for more information." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "96870043", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/opt/homebrew/lib/python3.11/site-packages/pennylane/capture/capture_operators.py:33: RuntimeWarning: PennyLane is not yet compatible with JAX versions > 0.4.28. You have version 0.4.30 installed. Please downgrade JAX to <=0.4.28 to avoid runtime errors.\n", - " warnings.warn(\n" - ] - } - ], - "source": [ - "from qbraid.runtime import QbraidProvider" - ] - }, - { - "cell_type": "markdown", - "id": "f3ffffdf", - "metadata": {}, - "source": [ - "If you have not yet configured QbraidProvider, provide your API key." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "b82efa4b", - "metadata": {}, - "outputs": [], - "source": [ - "# provider = QbraidProvider(api_key='API_KEY')\n", - "provider = QbraidProvider()" - ] - }, - { - "cell_type": "markdown", - "id": "539d7239", - "metadata": {}, - "source": [ - "We'll run our program on qBraid's QIR simulator." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "a273c97e", - "metadata": {}, - "outputs": [], - "source": [ - "device = provider.get_device('qbraid_qir_simulator')" - ] - }, - { - "cell_type": "markdown", - "id": "8495dbad", - "metadata": {}, - "source": [ - "To run the job, simply pass a QASM string to the device. If you have a PyQASM module, use `dumps` to generate a QASM string" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "51206d57", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\n", - "include \"stdgates.inc\";\n", - "qubit[1] ancilla;\n", - "qubit[3] q;\n", - "bit[3] b;\n", - "h q[0];\n", - "h q[1];\n", - "h q[2];\n", - "x ancilla[0];\n", - "h ancilla[0];\n", - "cx q[0], ancilla[0];\n", - "cx q[2], ancilla[0];\n", - "h q[0];\n", - "h q[1];\n", - "h q[2];\n", - "b[0] = measure q[0];\n", - "b[1] = measure q[1];\n", - "b[2] = measure q[2];\n", - "\n" - ] - } - ], - "source": [ - "secret_string = '101'\n", - "module = bv.load_program(secret_string)\n", - "module.unroll()\n", - "qasm_str = pyqasm.dumps(module)\n", - "print(qasm_str)" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "358fcbc7", - "metadata": {}, - "outputs": [], - "source": [ - "job = device.run(qasm_str, shots=500)" - ] - }, - { - "cell_type": "markdown", - "id": "53858d48", - "metadata": {}, - "source": [ - "We can now get the counts from the job results." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "9a8f91c5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'1010': 500}\n" - ] - } - ], - "source": [ - "results = job.result()\n", - "counts = results.data.get_counts()" - ] - }, - { - "cell_type": "markdown", - "id": "b00c560b", - "metadata": {}, - "source": [ - "We can now check the counts to see if the most frequent value is our secret string. This particular backend includes the ancilla qubit as the least significant bit, so we'll remove that." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "b14a37ce", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Secret String: 101. B-V result string: 101\n" - ] - } - ], - "source": [ - "# Remove ancilla qubit from bitstring\n", - "processed_counts = {bitstr[:-1]: count for bitstr, count in counts.items()}\n", - "# Find most frequent count\n", - "max_str = max(processed_counts, key=counts.get)\n", - "\n", - "print(f\"Secret String: {secret_string}. B-V result string: {result_string}\")" - ] - }, - { - "cell_type": "markdown", - "id": "29cbdab1", - "metadata": {}, - "source": [ - "We see that the algorithm successfully identified the secret string. Finally, we can plot the results using qBraid Visualization." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "e84ea61e", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAG3CAYAAABSTJRlAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA8nElEQVR4nO3de3QU9f3/8dfMJoSQkIQk5Aa5AUGIBoNcI9YLpEREqwULKAWkVnr4Av6Ear3US7E9otiqtdXytbWgViripSqICKFi1cglGLmohHsCuQEh2RDMJtnZ3x8Ju5uvaDUEEsbn45yc475ndj6feUtmX5mZ3TU8Ho9HAAAANmW29wQAAADOJMIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwtXYPO4cOHdJPf/pTRUVFKTg4WBkZGdq8ebN3ucfj0f3336/4+HgFBwcrOztbu3btarGNyspKTZ48WWFhYYqIiNDNN9+s48ePn+1dAQAAHVC7hp1jx45pxIgRCgwM1KpVq/TZZ5/pD3/4g7p16+ZdZ+HChXryySe1aNEibdiwQSEhIcrJyVFdXZ13ncmTJ2vHjh1as2aNVqxYoffff18zZsxoj10CAAAdjNGeXwR611136cMPP9R//vOfUy73eDxKSEjQL3/5S91+++2SpOrqasXGxmrJkiWaNGmSPv/8c6Wnp2vTpk0aPHiwJOmdd97RVVddpYMHDyohIeGs7Q8AAOh4Atpz8DfffFM5OTn6yU9+ovXr16tHjx76n//5H91yyy2SpH379qmsrEzZ2dne54SHh2vYsGHKy8vTpEmTlJeXp4iICG/QkaTs7GyZpqkNGzboxz/+8VfGdblccrlc3seWZamyslJRUVEyDOMM7jEAAGgrHo9HNTU1SkhIkGl+/cWqdg07e/fu1V/+8hfNmzdP99xzjzZt2qRbb71VnTp10rRp01RWViZJio2NbfG82NhY77KysjLFxMS0WB4QEKDIyEjvOv/XggULNH/+/DOwRwAA4GwrLi5Wz549v3Z5u4Ydy7I0ePBgPfTQQ5KkgQMHavv27Vq0aJGmTZt2xsa9++67NW/ePO/j6upqJSUlqbi4WGFhYWdsXAAA0HacTqcSExPVtWvXb1yvXcNOfHy80tPTW9T69++vV199VZIUFxcnSSovL1d8fLx3nfLycmVmZnrXqaioaLGNxsZGVVZWep//fwUFBSkoKOgr9bCwMMIOAADnmP92C0q7vhtrxIgR2rlzZ4taYWGhkpOTJUmpqamKi4tTbm6ud7nT6dSGDRuUlZUlScrKylJVVZXy8/O966xbt06WZWnYsGFnYS8AAEBH1q5ndubOnauLL75YDz30kCZMmKCNGzfqmWee0TPPPCOpKanddttt+t3vfqe0tDSlpqbqvvvuU0JCgq677jpJTWeCrrzySt1yyy1atGiRGhoaNHv2bE2aNIl3YgEAgPZ967kkrVixQnfffbd27dql1NRUzZs3z/tuLKnpTusHHnhAzzzzjKqqqnTJJZfo6aefVt++fb3rVFZWavbs2XrrrbdkmqbGjx+vJ598UqGhod9qDk6nU+Hh4aquruYyFgAA54hv+/rd7mGnIyDsAABw7vm2r9/t/nURAAAAZxJhBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphB8A5LSUlReedd54yMzOVmZmpZcuWSZJ27dqliy++WH379tWQIUO0Y8cO73O+aRkA+yHsADjnLVu2TAUFBSooKNDEiRMlSb/4xS80Y8YMFRYW6s4779RNN93kXf+blgGwH8IOANupqKjQ5s2b9dOf/lSSNH78eBUXF2v37t3fuAyAPRF2AJzzpk6dqoyMDN188806fPiwiouLFR8fr4CAAEmSYRhKSkpSUVHRNy4DYE+EHQDntPfff19bt27Vli1bFB0drWnTprX3lAB0MAHtPQEAOB1JSUmSpMDAQN12223q27evEhMTVVpaqsbGRgUEBMjj8aioqEhJSUkKCwv72mUA7IkzOwDOWbW1taqqqvI+/uc//6mBAwcqJiZGF110kf7xj39Ikl599VX17NlTffr0+cZlAOzJ8Hg8nvaeRHtzOp0K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NX36dLlcrhbPGzp0qK644go98sgjmjFjhg4cOKDVq1d7l584cUIhISF6++23NWbMmFOO7XK5WmzX6XQqMTFR1dXVCgsLa9P9TLlrZZtuDwCAc83+h8eeke06nU6Fh4f/19fvdruMlZ+fr4qKCl100UUKCAhQQECA1q9fryeffFIBAQGKjY1VfX29qqqqWjyvvLxccXFxkqS4uLivvDvr5OOT65xKUFCQwsLCWvwAAAB7arewM2rUKG3btk0FBQXen8GDB2vy5Mne/w4MDFRubq73OTt37lRRUZGysrIkSVlZWdq2bZsqKiq866xZs0ZhYWFKT08/6/sEAAA6nna7Z6dr16664IILWtRCQkIUFRXlrd98882aN2+eIiMjFRYWpjlz5igrK0vDhw+XJI0ePVrp6emaMmWKFi5cqLKyMt17772aNWuWgoKCzvo+AQCAjqddb1D+bx5//HGZpqnx48fL5XIpJydHTz/9tHe5w+HQihUrNHPmTGVlZSkkJETTpk3Tgw8+2I6zBgAAHUm73aDckXzbG5xagxuUAQDfd9/bG5QBAADOBsIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwtVaFnS1btmjbtm3ex2+88Yauu+463XPPPaqvr2+zyQEAAJyuVoWdX/ziFyosLJQk7d27V5MmTVKXLl20fPly/epXv2rTCQIAAJyOVoWdwsJCZWZmSpKWL1+uSy+9VEuXLtWSJUv06quvtuX8AAAATkurwo7H45FlWZKktWvX6qqrrpIkJSYm6siRI203OwAAgNPUqrAzePBg/e53v9MLL7yg9evXa+zYsZKkffv2KTY2tk0nCAAAcDpaFXYef/xxbdmyRbNnz9avf/1r9enTR5L0yiuv6OKLL27TCQIAAJyOgNY86cILL2zxbqyTHn30UQUEtGqTAAAAZ0Srzuz06tVLR48e/Uq9rq5Offv2Pe1JAQAAtJVWhZ39+/fL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Vr5T2Lnqqqt03333qa6u7ivLvvzySz3wwAO6+uqr22xyAAAAp+s7vfX83nvv1Wuvvaa+fftq9uzZOu+88yRJX3zxhZ566im53W79+te/PiMTBQAAaI3vFHZiY2P10UcfaebMmbr77rvl8XgkSYZhKCcnR0899ZRiY2PPyEQBAABa4zt/qGBycrLefvttHTt2TLt375bH41FaWpq6det2JuYHAABwWlr1CcqS1K1bNw0ZMqQt5wIAANDmWvXdWAAAAOcKwg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALC1dg07CxYs0JAhQ9S1a1fFxMTouuuu086dO1usU1dXp1mzZikqKkqhoaEaP368ysvLW6xTVFSksWPHqkuXLoqJidEdd9yhxsbGs7krAACgg2rXsLN+/XrNmjVLH3/8sdasWaOGhgaNHj1atbW13nXmzp2rt956S8uXL9f69etVUlKicePGeZe73W6NHTtW9fX1+uijj/Tcc89pyZIluv/++9tjlwAAQAdjeDweT3tP4qTDhw8rJiZG69ev16WXXqrq6mp1795dS5cu1fXXXy9J+uKLL9S/f3/l5eVp+PDhWrVqla6++mqVlJQoNjZWkrRo0SLdeeedOnz4sDp16vRfx3U6nQoPD1d1dbXCwsLadJ9S7lrZptsDAOBcs//hsWdku9/29btD3bNTXV0tSYqMjJQk5efnq6GhQdnZ2d51+vXrp6SkJOXl5UmS8vLylJGR4Q06kpSTkyOn06kdO3acchyXyyWn09niBwAA2FNAe0/gJMuydNttt2nEiBG64IILJEllZWXq1KmTIiIiWqwbGxursrIy7zr+Qefk8pPLTmXBggWaP3/+V+qbN29WaGioJCkzM1M1NTXas2ePd3m/fv3kcDhahKiUlBRFRUUpPz+/xfjJyckqKCjQz/q6JUkHaw29e8jUlT3dSujStF5Ng7R8n0NZMZb6R/hOsC0uNNU/wqPhMb7aa/tNhQRIOT0tby23xNTROmlCL18t/4ihTytN3ZTmlmk01XY5Df2nzNSPk93qFtRUq6iTVhQ5NDLBUkpo0zguS3pxt0ODoi1dGOkbe+keUz1CpMvifOOsLDZleaRrkny1D8oN7asxNKWPr7btmKFNh03d0NutYEdTrei4obUlpq5KdCsuuKlW3SC9us+hEbGWzgv3jf33Qocu6GZpaHdf7dX9proGSqN7+MZZe8hUVb10faqvtumwoW3HTE3v61ZzK1RYbeiDclPjUtyKaD7pV/6ltLLYoVEJlpKbe1HnlpbucWhwtKUBfr14cbepxFCPLo3z1VYUNf3NcLVfL94vM1R83NBkv15srTS0+YipG3u71bm5FweOG8otMTU20a3Y5l5U1Uuv7XfoklhLfZt74ZG0uNChjG6Whvj14pV9piI6Sdl+vXj3kKmaBml8iq+28bCh7cdM779HSdpZbejDclPjU90KD2yqlX0pvV3sUHaCpaTmXnzplv65x6Eh3S1ldPON/cJuU6ldPbok1ld7q8iUaUhjE31jry8zdahWurG3r/ZppaH8I6Ym93ErqPlPrv3HDa0rMXV1klsxnZtqx1zS6wcc+kGcpbSwpnEsj7Rkl0MXRloaFO0b++W9pqI6S6MSfOOsPmiqtlEa59eLjysMfV5laHpfX+3zKkN5FaZ+kupW1+ZelJyQ3jno0OgelnqGNI1T2ygt2+vQsO6WzvfrxfO7TPUO82iEXy/eOGCqkymN8evFe6Wmyr6UJvn9zn5y1NAnR01N6eNWYHMv9tYYeq/U1I+S3Ipu7sVRl/TGAYcui7PUu7kXjR7p+V0OZUZausivF8v2mureWRrp14t3Dpqqc0vXJftqeRWGdlYbuinNV/usytDHFaYmpLoV2tyLQycMrT5oKqenpR5dmsY53iC9vM+h4TGW0v2OX0t2mTov3KMsv+PXvw6Y6uyQrvQ7fq0rMXW4Tpro14stRwwVVJqamuZWQPMv7R6nofVlpq5Ndiuq+fh1pE56s8ihy+Mt9eraNE6DJb2w26GBUZYGRvnGfmmvqbhg6fJ43zirik3VW9K1fr34sNzQHqehqX692HHM0IbDpib2ciuk+dWSY3nrjuWSVFpaquLiYu/jjIwMuVwuFRYWemtpaWkKDg7W1q1bvbXExETFx8dr48aN3lp0dLR69er1tSc1/q8Ocxlr5syZWrVqlT744AP17NlTkrR06VJNnz5dLperxbpDhw7VFVdcoUceeUQzZszQgQMHtHr1au/yEydOKCQkRG+//bbGjBnzlbFcLleLbTqdTiUmJnIZCwCAM6C9L2N1iDM7s2fP1ooVK/T+++97g44kxcXFqb6+XlVVVS3O7pSXlysuLs67jn/aO7n85LJTCQoKUlBQUBvvBQAA6Ija9Z4dj8ej2bNn6/XXX9e6deuUmpraYvmgQYMUGBio3Nxcb23nzp0qKipSVlaWJCkrK0vbtm1TRUWFd501a9YoLCxM6enpZ2dHAABAh9WuZ3ZmzZqlpUuX6o033lDXrl2999iEh4crODhY4eHhuvnmmzVv3jxFRkYqLCxMc+bMUVZWloYPHy5JGj16tNLT0zVlyhQtXLhQZWVluvfeezVr1izO3gAAgPYNO3/5y18kSZdffnmL+uLFi3XTTTdJkh5//HGZpqnx48fL5XIpJydHTz/9tHddh8OhFStWaObMmcrKylJISIimTZumBx988GztBgAA6MA6zA3K7YnP2QEA4Mxp7xuUO9Tn7AAAALQ1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALC1dg0777//vq655holJCTIMAz961//arHc4/Ho/vvvV3x8vIKDg5Wdna1du3a1WKeyslKTJ09WWFiYIiIidPPNN+v48eNncS8AAEBH1q5hp7a2VhdeeKGeeuqpUy5fuHChnnzySS1atEgbNmxQSEiIcnJyVFdX511n8uTJ2rFjh9asWaMVK1bo/fff14wZM87WLgAAgA4uoD0HHzNmjMaMGXPKZR6PR0888YTuvfdeXXvttZKk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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from qbraid.visualization import plot_histogram\n", - "\n", - "plot_histogram(counts)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6d95cc0b", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.11.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "405809fb", + "metadata": { + "tags": [ + "hide_cell" + ] + }, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "\n", + "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '..')))" + ] + }, + { + "cell_type": "markdown", + "id": "d3b8876c", + "metadata": {}, + "source": [ + "## Bernstein Vazirani Algorithm\n", + "#### In this tutorial, we will demonstrate the capabilities of qBraid Algorithms Bernstein Vazirani Module\n", + "Begin by importing the module from qBraid Algorithms library" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e939fb23", + "metadata": {}, + "outputs": [], + "source": [ + "import pyqasm\n", + "from qbraid_algorithms import bernstein_vazirani as bv" + ] + }, + { + "cell_type": "markdown", + "id": "3545fac9", + "metadata": {}, + "source": [ + "To load a full Bernstein Vazirani algorithm circuit as a PyQASM module, simply pass the secret string to be encoded in the oracle to the `load_program()` method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97649823", + "metadata": {}, + "outputs": [], + "source": [ + "module = bv.generate_program('1011')" + ] + }, + { + "cell_type": "markdown", + "id": "7e2ac473", + "metadata": {}, + "source": [ + "We can perform all standard [PyQASM](https://docs.qbraid.com/pyqasm/user-guide/overview) operations on the module, such as unrolling." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0fb825a7", + "metadata": {}, + "outputs": [], + "source": [ + "module.unroll()" + ] + }, + { + "cell_type": "markdown", + "id": "287b4aa8", + "metadata": {}, + "source": [ + "Below, we display the unrolled circuit, which includes preparing the input and ancilla qubits, applying the '1011' oracle, applying Hadamard gates after the oracle, then measuring the results." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "31842ddc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "qubit[1] ancilla;\n", + "qubit[4] q;\n", + "bit[4] b;\n", + "h q[0];\n", + "h q[1];\n", + "h q[2];\n", + "h q[3];\n", + "x ancilla[0];\n", + "h ancilla[0];\n", + "cx q[0], ancilla[0];\n", + "cx q[2], ancilla[0];\n", + "cx q[3], ancilla[0];\n", + "h q[0];\n", + "h q[1];\n", + "h q[2];\n", + "h q[3];\n", + "b[0] = measure q[0];\n", + "b[1] = measure q[1];\n", + "b[2] = measure q[2];\n", + "b[3] = measure q[3];\n", + "\n" + ] + } + ], + "source": [ + "module_str = pyqasm.dumps(module)\n", + "print(module_str)" + ] + }, + { + "cell_type": "markdown", + "id": "21906ab0", + "metadata": {}, + "source": [ + "## Using B-V in your own OpenQASM3 program\n", + "#### qBraid algorithms makes it easy to incorporate either the full Bernstein Vazirani algorithm - or just the encoded oracle - into your own OpenQASM3 circuit.\n", + "To use a secret string-encoded oracle in your circuit, first generate the oracle submodule using the `generate_oracle` method, which takes a secret string. The method will create a QASM3 file containing your oracle as a subroutine within your current working directory." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "6f3e9784", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Oracle 'oracle' has been added to /Users/lukeandreesen/qbraid_algos/examples/oracle.qasm\n" + ] + } + ], + "source": [ + "bv.generate_oracle('111')" + ] + }, + { + "cell_type": "markdown", + "id": "61817da7", + "metadata": {}, + "source": [ + "Below, we can see the custom oracle subroutine that you now have access to. To use this in your own circuit, simply add `include \"oracle.qasm\";` to your OpenQASM file, and call the `oracle` subroutine by passing an appropriately sized register of qubits and an ancilla qubit." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "8833f4b9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\r\n", + "include \"stdgates.inc\";\r\n", + "\r\n", + "def oracle(qubit[3] q, qubit[1] ancilla) {\r\n", + " int[32] s = 7;\r\n", + " int[16] n = 3;\r\n", + " for int i in [0:n - 1] {\r\n", + " if ((s >> i) & 1) {\r\n", + " cx q[i], ancilla[0];\r\n", + " }\r\n", + " }\r\n", + "}\r\n" + ] + } + ], + "source": [ + "%cat oracle.qasm" + ] + }, + { + "cell_type": "markdown", + "id": "3672d119", + "metadata": {}, + "source": [ + "Similarly, you can generate an entire Bernstein Vazirani circuit as a submodule using the `save_to_qasm` method, again passing your desired secret string." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "5e37f60d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Subroutine 'bernvaz' has been added to /Users/lukeandreesen/qbraid_algos/examples/bernvaz.qasm\n" + ] + } + ], + "source": [ + "subroutine = bv.save_to_qasm('011')" + ] + }, + { + "cell_type": "markdown", + "id": "05cfcdf4", + "metadata": {}, + "source": [ + "To use the subroutine in your own circuit, add `include \"bernvaz.qasm\";` to your OpenQASM file, and call the `bernvaz` method by passing an appropriately sized register of qubits, as well as an ancilla qubit." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "38b7beb3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\r\n", + "include \"stdgates.inc\";\r\n", + "\r\n", + "def bernvaz(qubit[3] q, qubit[1] ancilla) {\r\n", + " int[32] s = 6;\r\n", + " int[16] n = 3;\r\n", + " for int i in [0:n - 1] {\r\n", + " h q[i];\r\n", + " }\r\n", + " x ancilla[0];\r\n", + " h ancilla[0];\r\n", + " // Note: Nested subroutine calls not yet supported by QASM, so manually insert\r\n", + " for int i in [0:n - 1] {\r\n", + " if ((s >> i) & 1) {\r\n", + " cx q[i], ancilla[0];\r\n", + " }\r\n", + " }\r\n", + " for int i in [0:n - 1] {\r\n", + " h q[i];\r\n", + " }\r\n", + "\r\n", + "}\r\n" + ] + } + ], + "source": [ + "%cat bernvaz.qasm" + ] + }, + { + "cell_type": "markdown", + "id": "e7d3dd7e", + "metadata": {}, + "source": [ + "## Running Algorithms on qBraid\n", + "Running algorithms on qBraid is simple. First, import QbraidProvider from qBraid Runtime. Visit [here](https://docs.qbraid.com/sdk/user-guide/providers/native#qbraidprovider) for more information." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "96870043", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/homebrew/lib/python3.11/site-packages/pennylane/capture/capture_operators.py:33: RuntimeWarning: PennyLane is not yet compatible with JAX versions > 0.4.28. You have version 0.4.30 installed. Please downgrade JAX to <=0.4.28 to avoid runtime errors.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "from qbraid.runtime import QbraidProvider" + ] + }, + { + "cell_type": "markdown", + "id": "f3ffffdf", + "metadata": {}, + "source": [ + "If you have not yet configured QbraidProvider, provide your API key." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "b82efa4b", + "metadata": {}, + "outputs": [], + "source": [ + "# provider = QbraidProvider(api_key='API_KEY')\n", + "provider = QbraidProvider()" + ] + }, + { + "cell_type": "markdown", + "id": "539d7239", + "metadata": {}, + "source": [ + "We'll run our program on qBraid's QIR simulator." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "a273c97e", + "metadata": {}, + "outputs": [], + "source": [ + "device = provider.get_device('qbraid_qir_simulator')" + ] + }, + { + "cell_type": "markdown", + "id": "8495dbad", + "metadata": {}, + "source": [ + "To run the job, simply pass a QASM string to the device. If you have a PyQASM module, use `dumps` to generate a QASM string" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51206d57", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "qubit[1] ancilla;\n", + "qubit[3] q;\n", + "bit[3] b;\n", + "h q[0];\n", + "h q[1];\n", + "h q[2];\n", + "x ancilla[0];\n", + "h ancilla[0];\n", + "cx q[0], ancilla[0];\n", + "cx q[2], ancilla[0];\n", + "h q[0];\n", + "h q[1];\n", + "h q[2];\n", + "b[0] = measure q[0];\n", + "b[1] = measure q[1];\n", + "b[2] = measure q[2];\n", + "\n" + ] + } + ], + "source": [ + "secret_string = '101'\n", + "module = bv.generate_program(secret_string)\n", + "module.unroll()\n", + "qasm_str = pyqasm.dumps(module)\n", + "print(qasm_str)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "358fcbc7", + "metadata": {}, + "outputs": [], + "source": [ + "job = device.run(qasm_str, shots=500)" + ] + }, + { + "cell_type": "markdown", + "id": "53858d48", + "metadata": {}, + "source": [ + "We can now get the counts from the job results." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "9a8f91c5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'1010': 500}\n" + ] + } + ], + "source": [ + "results = job.result()\n", + "counts = results.data.get_counts()" + ] + }, + { + "cell_type": "markdown", + "id": "b00c560b", + "metadata": {}, + "source": [ + "We can now check the counts to see if the most frequent value is our secret string. This particular backend includes the ancilla qubit as the least significant bit, so we'll remove that." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "b14a37ce", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Secret String: 101. B-V result string: 101\n" + ] + } + ], + "source": [ + "# Remove ancilla qubit from bitstring\n", + "processed_counts = {bitstr[:-1]: count for bitstr, count in counts.items()}\n", + "# Find most frequent count\n", + "max_str = max(processed_counts, key=counts.get)\n", + "\n", + "print(f\"Secret String: {secret_string}. B-V result string: {result_string}\")" + ] + }, + { + "cell_type": "markdown", + "id": "29cbdab1", + "metadata": {}, + "source": [ + "We see that the algorithm successfully identified the secret string. Finally, we can plot the results using qBraid Visualization." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "e84ea61e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAG3CAYAAABSTJRlAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA8nElEQVR4nO3de3QU9f3/8dfMJoSQkIQk5Aa5AUGIBoNcI9YLpEREqwULKAWkVnr4Av6Ear3US7E9otiqtdXytbWgViripSqICKFi1cglGLmohHsCuQEh2RDMJtnZ3x8Ju5uvaDUEEsbn45yc475ndj6feUtmX5mZ3TU8Ho9HAAAANmW29wQAAADOJMIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwtXYPO4cOHdJPf/pTRUVFKTg4WBkZGdq8ebN3ucfj0f3336/4+HgFBwcrOztbu3btarGNyspKTZ48WWFhYYqIiNDNN9+s48ePn+1dAQAAHVC7hp1jx45pxIgRCgwM1KpVq/TZZ5/pD3/4g7p16+ZdZ+HChXryySe1aNEibdiwQSEhIcrJyVFdXZ13ncmTJ2vHjh1as2aNVqxYoffff18zZsxoj10CAAAdjNGeXwR611136cMPP9R//vOfUy73eDxKSEjQL3/5S91+++2SpOrqasXGxmrJkiWaNGmSPv/8c6Wnp2vTpk0aPHiwJOmdd97RVVddpYMHDyohIeGs7Q8AAOh4Atpz8DfffFM5OTn6yU9+ovXr16tHjx76n//5H91yyy2SpH379qmsrEzZ2dne54SHh2vYsGHKy8vTpEmTlJeXp4iICG/QkaTs7GyZpqkNGzboxz/+8VfGdblccrlc3seWZamyslJRUVEyDOMM7jEAAGgrHo9HNTU1SkhIkGl+/cWqdg07e/fu1V/+8hfNmzdP99xzjzZt2qRbb71VnTp10rRp01RWViZJio2NbfG82NhY77KysjLFxMS0WB4QEKDIyEjvOv/XggULNH/+/DOwRwAA4GwrLi5Wz549v3Z5u4Ydy7I0ePBgPfTQQ5KkgQMHavv27Vq0aJGmTZt2xsa9++67NW/ePO/j6upqJSUlqbi4WGFhYWdsXAAA0HacTqcSExPVtWvXb1yvXcNOfHy80tPTW9T69++vV199VZIUFxcnSSovL1d8fLx3nfLycmVmZnrXqaioaLGNxsZGVVZWep//fwUFBSkoKOgr9bCwMMIOAADnmP92C0q7vhtrxIgR2rlzZ4taYWGhkpOTJUmpqamKi4tTbm6ud7nT6dSGDRuUlZUlScrKylJVVZXy8/O966xbt06WZWnYsGFnYS8AAEBH1q5ndubOnauLL75YDz30kCZMmKCNGzfqmWee0TPPPCOpKanddttt+t3vfqe0tDSlpqbqvvvuU0JCgq677jpJTWeCrrzySt1yyy1atGiRGhoaNHv2bE2aNIl3YgEAgPZ967kkrVixQnfffbd27dql1NRUzZs3z/tuLKnpTusHHnhAzzzzjKqqqnTJJZfo6aefVt++fb3rVFZWavbs2XrrrbdkmqbGjx+vJ598UqGhod9qDk6nU+Hh4aquruYyFgAA54hv+/rd7mGnIyDsAABw7vm2r9/t/nURAAAAZxJhBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphB8A5LSUlReedd54yMzOVmZmpZcuWSZJ27dqliy++WH379tWQIUO0Y8cO73O+aRkA+yHsADjnLVu2TAUFBSooKNDEiRMlSb/4xS80Y8YMFRYW6s4779RNN93kXf+blgGwH8IOANupqKjQ5s2b9dOf/lSSNH78eBUXF2v37t3fuAyAPRF2AJzzpk6dqoyMDN188806fPiwiouLFR8fr4CAAEmSYRhKSkpSUVHRNy4DYE+EHQDntPfff19bt27Vli1bFB0drWnTprX3lAB0MAHtPQEAOB1JSUmSpMDAQN12223q27evEhMTVVpaqsbGRgUEBMjj8aioqEhJSUkKCwv72mUA7IkzOwDOWbW1taqqqvI+/uc//6mBAwcqJiZGF110kf7xj39Ikl599VX17NlTffr0+cZlAOzJ8Hg8nvaeRHtzOp0K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NX36dLlcrhbPGzp0qK644go98sgjmjFjhg4cOKDVq1d7l584cUIhISF6++23NWbMmFOO7XK5WmzX6XQqMTFR1dXVCgsLa9P9TLlrZZtuDwCAc83+h8eeke06nU6Fh4f/19fvdruMlZ+fr4qKCl100UUKCAhQQECA1q9fryeffFIBAQGKjY1VfX29qqqqWjyvvLxccXFxkqS4uLivvDvr5OOT65xKUFCQwsLCWvwAAAB7arewM2rUKG3btk0FBQXen8GDB2vy5Mne/w4MDFRubq73OTt37lRRUZGysrIkSVlZWdq2bZsqKiq866xZs0ZhYWFKT08/6/sEAAA6nna7Z6dr16664IILWtRCQkIUFRXlrd98882aN2+eIiMjFRYWpjlz5igrK0vDhw+XJI0ePVrp6emaMmWKFi5cqLKyMt17772aNWuWgoKCzvo+AQCAjqddb1D+bx5//HGZpqnx48fL5XIpJydHTz/9tHe5w+HQihUrNHPmTGVlZSkkJETTpk3Tgw8+2I6zBgAAHUm73aDckXzbG5xagxuUAQDfd9/bG5QBAADOBsIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwtVaFnS1btmjbtm3ex2+88Yauu+463XPPPaqvr2+zyQEAAJyuVoWdX/ziFyosLJQk7d27V5MmTVKXLl20fPly/epXv2rTCQIAAJyOVoWdwsJCZWZmSpKWL1+uSy+9VEuXLtWSJUv06quvtuX8AAAATkurwo7H45FlWZKktWvX6qqrrpIkJSYm6siRI203OwAAgNPUqrAzePBg/e53v9MLL7yg9evXa+zYsZKkffv2KTY2tk0nCAAAcDpaFXYef/xxbdmyRbNnz9avf/1r9enTR5L0yiuv6OKLL27TCQIAAJyOgNY86cILL2zxbqyTHn30UQUEtGqTAAAAZ0Srzuz06tVLR48e/Uq9rq5Offv2Pe1JAQAAtJVWhZ39+/fL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Vr5T2Lnqqqt03333qa6u7ivLvvzySz3wwAO6+uqr22xyAAAAp+s7vfX83nvv1Wuvvaa+fftq9uzZOu+88yRJX3zxhZ566im53W79+te/PiMTBQAAaI3vFHZiY2P10UcfaebMmbr77rvl8XgkSYZhKCcnR0899ZRiY2PPyEQBAABa4zt/qGBycrLefvttHTt2TLt375bH41FaWpq6det2JuYHAABwWlr1CcqS1K1bNw0ZMqQt5wIAANDmWvXdWAAAAOcKwg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALC1dg07CxYs0JAhQ9S1a1fFxMTouuuu086dO1usU1dXp1mzZikqKkqhoaEaP368ysvLW6xTVFSksWPHqkuXLoqJidEdd9yhxsbGs7krAACgg2rXsLN+/XrNmjVLH3/8sdasWaOGhgaNHj1atbW13nXmzp2rt956S8uXL9f69etVUlKicePGeZe73W6NHTtW9fX1+uijj/Tcc89pyZIluv/++9tjlwAAQAdjeDweT3tP4qTDhw8rJiZG69ev16WXXqrq6mp1795dS5cu1fXXXy9J+uKLL9S/f3/l5eVp+PDhWrVqla6++mqVlJQoNjZWkrRo0SLdeeedOnz4sDp16vRfx3U6nQoPD1d1dbXCwsLadJ9S7lrZptsDAOBcs//hsWdku9/29btD3bNTXV0tSYqMjJQk5efnq6GhQdnZ2d51+vXrp6SkJOXl5UmS8vLylJGR4Q06kpSTkyOn06kdO3acchyXyyWn09niBwAA2FNAe0/gJMuydNttt2nEiBG64IILJEllZWXq1KmTIiIiWqwbGxursrIy7zr+Qefk8pPLTmXBggWaP3/+V+qbN29WaGioJCkzM1M1NTXas2ePd3m/fv3kcDhahKiUlBRFRUUpPz+/xfjJyckqKCjQz/q6JUkHaw29e8jUlT3dSujStF5Ng7R8n0NZMZb6R/hOsC0uNNU/wqPhMb7aa/tNhQRIOT0tby23xNTROmlCL18t/4ihTytN3ZTmlmk01XY5Df2nzNSPk93qFtRUq6iTVhQ5NDLBUkpo0zguS3pxt0ODoi1dGOkbe+keUz1CpMvifOOsLDZleaRrkny1D8oN7asxNKWPr7btmKFNh03d0NutYEdTrei4obUlpq5KdCsuuKlW3SC9us+hEbGWzgv3jf33Qocu6GZpaHdf7dX9proGSqN7+MZZe8hUVb10faqvtumwoW3HTE3v61ZzK1RYbeiDclPjUtyKaD7pV/6ltLLYoVEJlpKbe1HnlpbucWhwtKUBfr14cbepxFCPLo3z1VYUNf3NcLVfL94vM1R83NBkv15srTS0+YipG3u71bm5FweOG8otMTU20a3Y5l5U1Uuv7XfoklhLfZt74ZG0uNChjG6Whvj14pV9piI6Sdl+vXj3kKmaBml8iq+28bCh7cdM779HSdpZbejDclPjU90KD2yqlX0pvV3sUHaCpaTmXnzplv65x6Eh3S1ldPON/cJuU6ldPbok1ld7q8iUaUhjE31jry8zdahWurG3r/ZppaH8I6Ym93ErqPlPrv3HDa0rMXV1klsxnZtqx1zS6wcc+kGcpbSwpnEsj7Rkl0MXRloaFO0b++W9pqI6S6MSfOOsPmiqtlEa59eLjysMfV5laHpfX+3zKkN5FaZ+kupW1+ZelJyQ3jno0OgelnqGNI1T2ygt2+vQsO6WzvfrxfO7TPUO82iEXy/eOGCqkymN8evFe6Wmyr6UJvn9zn5y1NAnR01N6eNWYHMv9tYYeq/U1I+S3Ipu7sVRl/TGAYcui7PUu7kXjR7p+V0OZUZausivF8v2mureWRrp14t3Dpqqc0vXJftqeRWGdlYbuinNV/usytDHFaYmpLoV2tyLQycMrT5oKqenpR5dmsY53iC9vM+h4TGW0v2OX0t2mTov3KMsv+PXvw6Y6uyQrvQ7fq0rMXW4Tpro14stRwwVVJqamuZWQPMv7R6nofVlpq5Ndiuq+fh1pE56s8ihy+Mt9eraNE6DJb2w26GBUZYGRvnGfmmvqbhg6fJ43zirik3VW9K1fr34sNzQHqehqX692HHM0IbDpib2ciuk+dWSY3nrjuWSVFpaquLiYu/jjIwMuVwuFRYWemtpaWkKDg7W1q1bvbXExETFx8dr48aN3lp0dLR69er1tSc1/q8Ocxlr5syZWrVqlT744AP17NlTkrR06VJNnz5dLperxbpDhw7VFVdcoUceeUQzZszQgQMHtHr1au/yEydOKCQkRG+//bbGjBnzlbFcLleLbTqdTiUmJnIZCwCAM6C9L2N1iDM7s2fP1ooVK/T+++97g44kxcXFqb6+XlVVVS3O7pSXlysuLs67jn/aO7n85LJTCQoKUlBQUBvvBQAA6Ija9Z4dj8ej2bNn6/XXX9e6deuUmpraYvmgQYMUGBio3Nxcb23nzp0qKipSVlaWJCkrK0vbtm1TRUWFd501a9YoLCxM6enpZ2dHAABAh9WuZ3ZmzZqlpUuX6o033lDXrl2999iEh4crODhY4eHhuvnmmzVv3jxFRkYqLCxMc+bMUVZWloYPHy5JGj16tNLT0zVlyhQtXLhQZWVluvfeezVr1izO3gAAgPYNO3/5y18kSZdffnmL+uLFi3XTTTdJkh5//HGZpqnx48fL5XIpJydHTz/9tHddh8OhFStWaObMmcrKylJISIimTZumBx988GztBgAA6MA6zA3K7YnP2QEA4Mxp7xuUO9Tn7AAAALQ1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALC1dg0777//vq655holJCTIMAz961//arHc4/Ho/vvvV3x8vIKDg5Wdna1du3a1WKeyslKTJ09WWFiYIiIidPPNN+v48eNncS8AAEBH1q5hp7a2VhdeeKGeeuqpUy5fuHChnnzySS1atEgbNmxQSEiIcnJyVFdX511n8uTJ2rFjh9asWaMVK1bo/fff14wZM87WLgAAgA4uoD0HHzNmjMaMGXPKZR6PR0888YTuvfdeXXvttZKk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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from qbraid.visualization import plot_histogram\n", + "\n", + "plot_histogram(counts)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6d95cc0b", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/examples/qft.ipynb b/examples/qft.ipynb index 6a062ae..cdf1361 100644 --- a/examples/qft.ipynb +++ b/examples/qft.ipynb @@ -1,473 +1,473 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "23827387", - "metadata": {}, - "outputs": [], - "source": [ - "import sys\n", - "import os\n", - "\n", - "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '..')))" - ] - }, - { - "cell_type": "markdown", - "id": "338ad3ca", - "metadata": {}, - "source": [ - "## Quantum Fourier Transform\n", - "#### In this tutorial, we will demonstrate the capabilities of qBraid Algorithms QFT and Inverse QFT (IQFT) Modules\n", - "Begin by importing the modules from qBraid Algorithms library" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "ccfc911a", - "metadata": {}, - "outputs": [], - "source": [ - "import pyqasm\n", - "from qbraid_algorithms import qft\n", - "from qbraid_algorithms import iqft" - ] - }, - { - "cell_type": "markdown", - "id": "87d3980f", - "metadata": {}, - "source": [ - "We can load both QFT and IQFT as PyQASM modules by calling the `load_program` method, passing the number of qubits to apply the operations to." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "ff7dc849", - "metadata": {}, - "outputs": [], - "source": [ - "num_qubits = 3\n", - "qft_module = qft.load_program(num_qubits)\n", - "iqft_module = iqft.load_program(num_qubits)" - ] - }, - { - "cell_type": "markdown", - "id": "b68eeb17", - "metadata": {}, - "source": [ - "We can perform all standard [PyQASM](https://docs.qbraid.com/pyqasm/user-guide/overview) operations on the module, such as unrolling." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "e8c1a74d", - "metadata": {}, - "outputs": [], - "source": [ - "qft_module.unroll()\n", - "iqft_module.unroll()" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "4ff9ef62", - "metadata": {}, - "outputs": [], - "source": [ - "qft_str = pyqasm.dumps(qft_module)\n", - "iqft_str = pyqasm.dumps(iqft_module)" - ] - }, - { - "cell_type": "markdown", - "id": "d626c8d5", - "metadata": {}, - "source": [ - "Below, we display a preview of the the unrolled QFT circuit, followed by the IQFT circuit." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "b2795398", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\n", - "include \"stdgates.inc\";\n", - "qubit[3] q;\n", - "bit[3] b;\n", - "h q[0];\n", - "rz(0.7853981633974483) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "...\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "h q[2];\n", - "swap q[0], q[2];\n", - "b[0] = measure q[0];\n", - "b[1] = measure q[1];\n", - "b[2] = measure q[2];\n", - "\n" - ] - } - ], - "source": [ - "print(\"\\n\".join(qft_str.split(\"\\n\")[:10]) + \"\\n...\\n\" + \"\\n\".join(qft_str.split(\"\\n\")[-10:]))" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "385c7c14", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\n", - "include \"stdgates.inc\";\n", - "qubit[3] q;\n", - "bit[3] b;\n", - "h q[2];\n", - "rz(-0.7853981633974483) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "...\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "h q[0];\n", - "swap q[0], q[2];\n", - "b[0] = measure q[0];\n", - "b[1] = measure q[1];\n", - "b[2] = measure q[2];\n", - "\n" - ] - } - ], - "source": [ - "print(\"\\n\".join(iqft_str.split(\"\\n\")[:10]) + \"\\n...\\n\" + \"\\n\".join(iqft_str.split(\"\\n\")[-10:]))" - ] - }, - { - "cell_type": "markdown", - "id": "35ada7c7", - "metadata": {}, - "source": [ - "## Using Quantum Fourier Transform in your own OpenQASM3 program\n", - "#### qBraid algorithms makes it easy to incorporate QFT and IQFT into your own OpenQASM3 circuit.\n", - "To use a QFT/IQFT in your circuit, first generate the subroutine using the `generate_subroutine` method, which takes the number of qubits to use. The method will create a QASM3 file containing the algorithm as a subroutine within your current working directory." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "3da3ed9d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Subroutine 'qft' has been added to /Users/lukeandreesen/qbraid_algos/examples/qft.qasm\n", - "Subroutine 'iqft' has been added to /Users/lukeandreesen/qbraid_algos/examples/iqft.qasm\n" - ] - } - ], - "source": [ - "qft.generate_subroutine(3)\n", - "iqft.generate_subroutine(3)" - ] - }, - { - "cell_type": "markdown", - "id": "9fb21cfd", - "metadata": {}, - "source": [ - "To use the QFT subroutine in your own circuit, add `include \"qft.qasm\";` to your OpenQASM file, and call the `qft` method by passing an appropriately sized register of qubits." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "59fb5dec", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\r\n", - "include \"stdgates.inc\";\r\n", - "\r\n", - "\r\n", - "def qft(qubit[3] q) {\r\n", - " int n = 3;\r\n", - " for int[16] i in [0:n - 1] {\r\n", - " h q[i];\r\n", - " for int[16] j in [i + 1:n - 1] {\r\n", - " int[16] k = j - i;\r\n", - " cp(2 * pi / (1 << (k + 1))) q[j], q[i];\r\n", - " }\r\n", - " }\r\n", - "\r\n", - " for int[16] i in [0:(n >> 1) - 1] {\r\n", - " swap q[i], q[n - i - 1];\r\n", - " }\r\n", - "}" - ] - } - ], - "source": [ - "%cat qft.qasm" - ] - }, - { - "cell_type": "markdown", - "id": "ab150803", - "metadata": {}, - "source": [ - "To use the IQFT subroutine in your own circuit, add `include \"qft.qasm\";` to your OpenQASM file, and call the `qft` method by passing an appropriately sized register of qubits." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "66248d06", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\r\n", - "include \"stdgates.inc\";\r\n", - "\r\n", - "def iqft(qubit[3] q) {\r\n", - " int n = 3;\r\n", - " \r\n", - " for int[16] i in [0:n-1] {\r\n", - " int[16] target = n - i - 1;\r\n", - " for int[16] j in [0:(n - target - 2)] {\r\n", - " int[16] control = n - j - 1;\r\n", - " int[16] k = control - target;\r\n", - " cp(-2 * pi / (1 << (k + 1))) q[control], q[target];\r\n", - " }\r\n", - " h q[target];\r\n", - " }\r\n", - "\r\n", - " for int[16] i in [0:(n >> 1) - 1] {\r\n", - " swap q[i], q[n - i - 1];\r\n", - " }\r\n", - "}" - ] - } - ], - "source": [ - "%cat iqft.qasm" - ] - }, - { - "cell_type": "markdown", - "id": "d94efa88", - "metadata": {}, - "source": [ - "## Running Algorithms on qBraid\n", - "Running algorithms on qBraid is simple. First, import QbraidProvider from qBraid Runtime. Visit [here](https://docs.qbraid.com/sdk/user-guide/providers/native#qbraidprovider) for more information." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "f2b39f1a", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/opt/homebrew/lib/python3.11/site-packages/pennylane/capture/capture_operators.py:33: RuntimeWarning: PennyLane is not yet compatible with JAX versions > 0.4.28. You have version 0.4.30 installed. Please downgrade JAX to <=0.4.28 to avoid runtime errors.\n", - " warnings.warn(\n" - ] - } - ], - "source": [ - "from qbraid.runtime import QbraidProvider" - ] - }, - { - "cell_type": "markdown", - "id": "e12616de", - "metadata": {}, - "source": [ - "If you have not yet configured QbraidProvider, provide your API key." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "4bb1e7e5", - "metadata": {}, - "outputs": [], - "source": [ - "# provider = QbraidProvider(api_key='API_KEY')\n", - "provider = QbraidProvider()" - ] - }, - { - "cell_type": "markdown", - "id": "629d7b9e", - "metadata": {}, - "source": [ - "We'll run our program on qBraid's QIR simulator." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "80ee5d99", - "metadata": {}, - "outputs": [], - "source": [ - "device = provider.get_device('qbraid_qir_simulator')" - ] - }, - { - "cell_type": "markdown", - "id": "4dce4910", - "metadata": {}, - "source": [ - "To run the job, simply pass a QASM string to the device. If you have a PyQASM module, use `dumps` to generate a QASM string" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "293bfb68", - "metadata": {}, - "outputs": [], - "source": [ - "module = qft.load_program(4)\n", - "qasm_str = pyqasm.dumps(module)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "8a26081c", - "metadata": {}, - "outputs": [], - "source": [ - "job = device.run(qasm_str, shots=500)" - ] - }, - { - "cell_type": "markdown", - "id": "d3c7c3a9", - "metadata": {}, - "source": [ - "We can now get the counts from the job results." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "7e1a7a8a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'0000': 21, '0001': 39, '0010': 27, '0011': 35, '0100': 4, '0101': 4, '0110': 34, '0111': 16, '1000': 25, '1001': 18, '1010': 14, '1011': 8, '1100': 54, '1101': 58, '1110': 69, '1111': 74}\n" - ] - } - ], - "source": [ - "results = job.result()\n", - "counts = results.data.get_counts()\n", - "print(counts)" - ] - }, - { - "cell_type": "markdown", - "id": "65f39cab", - "metadata": {}, - "source": [ - "Finally, we can plot the results using qBraid Visualization." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "b201c84b", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjIAAAG3CAYAAACuWb+vAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAABpTElEQVR4nO3deVxU5f4H8M8ZhAGRHWRfZRXZQUHNcklL7ebVFvtpuWZ51a7avaVdq2tZWrfbYtlimWU300rzZmXdK+bSTZNFlEXZZN9EkEWEAWae3x/EkVHc2IaBz/v14vVinpk53+c5M5zz4ZxnzkhCCAEiIiIiPaTQdQeIiIiIOopBhoiIiPQWgwwRERHpLQYZIiIi0lsMMkRERKS3GGSIiIhIbzHIEBERkd5ikCEiIiK9xSBDREREeotBhoiIiPSWToOMWq3Gs88+C09PT5iYmGDIkCF48cUX0fZbE4QQeO655+Do6AgTExNMmDABmZmZOuw1ERER9RY6DTKvvPIK3nvvPbzzzjs4ffo0XnnlFbz66qt4++235ce8+uqr2LhxI95//3389ttvMDU1xaRJk9DQ0KDDnhMREVFvIOnySyOnTp0Ke3t7bNmyRW6bMWMGTExM8K9//QtCCDg5OeHJJ5/EX/7yFwBAdXU17O3t8cknn2DmzJm66joRERH1AgN0WXzkyJHYvHkzMjIy4Ovri5MnT+KXX37B66+/DgDIyclBaWkpJkyYID/HwsICI0aMwNGjR9sNMiqVCiqVSr6t0WhQWVkJGxsbSJLU/YMiIiKiThNCoLa2Fk5OTlAorn0CSadBZtWqVaipqYG/vz8MDAygVqvx0ksvYdasWQCA0tJSAIC9vb3W8+zt7eX7rrR+/XqsXbu2eztOREREPaKgoAAuLi7XvF+nQebLL7/E559/ju3btyMwMBBJSUlYvnw5nJycMGfOnA4tc/Xq1Vi5cqV8u7q6Gm5ubigoKIC5uXlXdZ2IiIi6UU1NDVxdXWFmZnbdx+k0yPz1r3/FqlWr5FNEQUFByMvLw/r16zFnzhw4ODgAAMrKyuDo6Cg/r6ysDKGhoe0uU6lUQqlUXtVubm7OIENERKRnbjQtRKefWrp06dJV570MDAyg0WgAAJ6ennBwcEBsbKx8f01NDX777TfExMT0aF+JiIio99HpEZl77rkHL730Etzc3BAYGIgTJ07g9ddfx/z58wG0pLDly5dj3bp18PHxgaenJ5599lk4OTlh2rRpuuw6ERER9QI6DTJvv/02nn32WfzpT3/CuXPn4OTkhMceewzPPfec/JinnnoKdXV1WLRoEaqqqjB69Gj8+OOPMDY21mHPiYiIqDfQ6XVkekJNTQ0sLCxQXV3NOTJERER64mb33/yuJSIiItJbDDJERESktxhkiIiISG8xyBAREZHeYpAhIiLqpyoqKhAaGir/+Pr6YsCAAaisrJQfc+DAARgYGODNN9/UXUevQ6cfvyYiIiLdsbGxQVJSknz7tddew6FDh2BtbQ2g5Wt+Vq1ahcmTJ+uohzfGIzJEREQEANiyZQsWLFgg3166dCnWrFkDGxsbHfbq+hhkiIiICL/++isuXLiAqVOnAgC+/vprKBQK/OEPf9Bxz66Pp5aIiIgIW7ZswSOPPIIBAwagtLQU69atw8GDB3XdrRtikCEiIurnLl68iC+//BJxcXEAgISEBJSUlCA0NBQAcP78eXz77bcoLy/HSy+9pMOeXo1BhoiIqJ/buXMnQkJC4O/vDwCYMmUKysrK5Pvnzp2L0NBQLF++XEc9vDbOkSEiIurnrpzkq0/4pZFERETU6/BLI4mIiKjPY5AhIiIivcUgQ0RERHqLQYaIiIj0FoMMERER6S0GGSIiItJbDDJERESkt3hlXyIion7AY9X33bLc3A1TumW5N4tHZIiIiEhvMcgQERGR3mKQISIiIr3FIENERER6i0GGiIiI9BaDDBEREektBhkiIqJeQqVSYenSpfDx8UFQUBBmz54NAPjxxx8RGRmJ4OBgREdH4+TJkzruae/B68gQERH1EqtWrYIkScjIyIAkSSgtLcWFCxcwa9YsHD58GIGBgThy5AhmzZqFlJQUXXe3V2CQISIi6gXq6uqwZcsWFBYWQpIkAICDgwPi4+NhY2ODwMBAAMBtt92G/Px8JCYmIjw8XJdd7hV4aomIiKgXyM7OhrW1NV5++WVERkbitttuQ2xsLHx8fFBRUYFff/0VAPDtt9+itrYWubm5uu1wL8EjMkRERL1Ac3Mz8vLyMHToUGzYsAEnTpzAnXfeidTUVHz99ddYvXo1Ll68iJiYGAwdOhQDBnAXDjDIEBER9Qpubm5QKBSYNWsWACAsLAyenp5ITk7GhAkTMHbsWAAtE4IdHBwwdOhQXXa31+CpJSIiol7A1tYW48ePx08//QQAyMnJQU5ODgICAlBSUiI/7sUXX8S4cePg7e2tq672KjwiQ0RE1Eu8//77WLBgAZ5++mkoFAp88MEHcHZ2xqOPPoojR46gubkZMTEx2LJli6672mvoNMh4eHggLy/vqvY//elP2LRpExoaGvDkk09ix44dUKlUmDRpEt59913Y29vroLdERETdy8vLCz///PNV7R9++KEOeqMfdHpqKS4uDiUlJfLPf//7XwDA/fffDwBYsWIF9u7di6+++gqHDh1CcXExpk+frssuExERUS+i0yMydnZ2Wrc3bNiAIUOG4Pbbb0d1dTW2bNmC7du3Y9y4cQCArVu3IiAgAMeOHUN0dLQuukxERES9SK+Z7NvY2Ih//etfmD9/PiRJQkJCApqamjBhwgT5Mf7+/nBzc8PRo0evuRyVSoWamhqtHyIiIuqbes1k3z179qCqqgpz584FAJSWlsLIyAiWlpZaj7O3t0dpaek1l7N+/XqsXbv2qvb4+HgMGjQIABAaGora2lpkZ2fL9/v7+8PAwACpqalym4eHB2xsbJCQkKBV393dHUlJSWhsbAQAWFhYwM/PD2fOnJGDk1KpREhICHJzc3Hu3Dn5+VFRUSgrK0N+fr7cFhQUhMbGRqSnp8tt3t7eMDU11fo+DRcXFzg5OSEuLg5CCAAts9y9vLyQnJyM+vp6AMCgQYMwdOhQZGZm4sKFCwAAAwMDREREoLCwEMXFxfIyw8LCUF1djbNnz8ptAQEBkCQJaWlpWuvC2toaiYmJcpuDgwPc3Nxw4sQJNDU1AQAsLS3h6+uL06dPo7a2FgBgbGyM4OBg5OTkoLy8XH7+8OHDUVJSgoKCAq11oVKpkJGRIbf5+PjAxMQEp06dkttcXV3h6OiI48ePy203uy4GDBiA8PBwFBQUaH0SICwsDFVVVcjJydFaFwBw+vRpuc3T0xOWlpY4ceKE3Obo6AhXV1ckJiaiubkZAGBlZQUfHx+kpaXh4sWLAAATExMEBQXh7NmzOH/+/HXXRXBwMOrr65GZmSm3+fr6QqlUIjk5+brrws7ODp6enjh16hQaGhoAAGZmZggICEBGRgaqqqoAAIaGhggLC0N+fr7W31V4eDgqKyu1Lrg1dOhQCCG01oWXlxcsLCy01oWTkxNcXFyQkJAAtVp9S+tCkiRERUWhuLgYhYWF8jJDQkJQV1eHrKwsuc3Pzw9GRkZa68LNzQ329vaIi4uT2wYPHgwPDw+cPHkSKpUKAGBubg5/f3+kp6ejuroaAGBkZITQ0FDk5eWhrKxMfn5ERAQqKiq01kVgYCDUajXOnDkjtw0ZMgRmZmZISkqS25ydneHs7Ky1LqytreHt7Y3U1FTU1dUBAAYOHIhhw4YhOzsbFRUVAACFQoHIyEgUFRWhqKjohuvC0NBQ65L17u7usLOzQ3x8vNzWuv1quy5at19t10Xr9uvKdREZGYny8nKtuY3Dhg1DU1PTDbdfresiPj4eGo0GAGBjY4MhQ4YgJSUFly5dAgCYmpoiMDAQWVlZqKysBHB5+3XluuC2/Na25fN91filTEJOrYSHvTXy45IvSIgrV+ChIWqYGLS05V+UsL9YgcmuajiYtLRVNwG7cgwwyl4DPwuBtrpjW972NbweSbSuRR2bNGkSjIyMsHfvXgDA9u3bMW/ePPmPrdXw4cMxduxYvPLKK+0uR6VSaT2npqYGrq6uqK6uhrm5efcNgIiIqBfzWPV9tyw3d8OUblluTU0NLCwsbrj/7hVHZPLy8rB//37s3r1bbnNwcEBjYyOqqqq0jsqUlZXBwcHhmstSKpVQKpXd2V0iIiLqJXrFHJmtW7di8ODBmDLlcqqLiIiAoaEhYmNj5bb09HTk5+cjJiZGF90kIiKiXkbnR2Q0Gg22bt2KOXPmaH1vhIWFBRYsWICVK1fC2toa5ubmWLZsGWJiYviJJSIi0mvddZoH6L5TPb2VzoPM/v37kZ+fj/nz51913xtvvAGFQoEZM2ZoXRCPiIiICOgFQWbixIm41nxjY2NjbNq0CZs2berhXhEREZE+6BVzZIiIiIg6gkGGiIiI9BaDDBEREektBhkiIiLSWwwyREREpLcYZIiIiEhvMcgQERGR3mKQISIiIr3FIENERER6i0GGiIiI9BaDDBEREektBhkiIiLSWwwyREREpLcYZIiIiEhvMcgQERGR3mKQISIiIr3FIENERER6i0GGiIiI9BaDDBEREektBhkiIiLSWwwyREREpLcYZIiIiEhvMcgQERGR3mKQISIiIr3FIENERER6i0GGiIiI9BaDDBEREektBhkiIiLSWwwyREREpLcYZIiIiEhvMcgQERGR3mKQISIiIr3FIENERHQNHh4e8PPzQ2hoKEJDQ7Fz504AwA8//IDw8HCEhoZi2LBh+PTTT3Xc0/5rgK47QERE1Jvt3LkToaGh8m0hBGbPno2DBw8iODgYubm58Pf3x/Tp02FmZqa7jvZTPCJDRER0iyRJQlVVFQCgpqYGNjY2UCqVuu1UP6XzIFNUVITZs2fDxsYGJiYmCAoKQnx8vHy/EALPPfccHB0dYWJiggkTJiAzM1OHPSYiov7kkUceQVBQEBYsWIDy8nJIkoSdO3di+vTpcHd3x+jRo/Hpp5/CyMhI113tl3QaZC5cuIBRo0bB0NAQ+/btQ1paGv75z3/CyspKfsyrr76KjRs34v3338dvv/0GU1NTTJo0CQ0NDTrsORER9QeHDx/GqVOnkJiYCFtbW8yZMwfNzc1Yt24ddu/ejby8PMTGxuLhhx/G+fPndd3dfkmnc2ReeeUVuLq6YuvWrXKbp6en/LsQAm+++SbWrFmDe++9FwCwbds22NvbY8+ePZg5c2aP95mIiPoPNzc3AIChoSGWL18OX19fJCUlobi4GGPGjAEAREVFwcXFBSdOnMCdd96py+72Szo9IvPtt98iMjIS999/PwYPHoywsDB8+OGH8v05OTkoLS3FhAkT5DYLCwuMGDECR48ebXeZKpUKNTU1Wj9ERES3qq6uTp4HAwBffPEFwsLC4OrqipKSEpw+fRoAkJWVhezsbPj5+emop/2bTo/InD17Fu+99x5WrlyJZ555BnFxcXjiiSdgZGSEOXPmoLS0FABgb2+v9Tx7e3v5viutX78ea9euvao9Pj4egwYNAgCEhoaitrYW2dnZ8v3+/v4wMDBAamqq3Obh4QEbGxskJCRo1XZ3d0dSUhIaGxsBtIQrPz8/nDlzRg5OSqUSISEhyM3Nxblz5+TnR0VFoaysDPn5+XJbUFAQGhsbkZ6eLrd5e3vD1NQUJ0+elNtcXFzg5OSEuLg4CCEAALa2tvDy8kJycjLq6+sBAIMGDcLQoUORmZmJCxcuAAAMDAwQERGBwsJCFBcXy8sMCwtDdXU1zp49K7cFBARAkiSkpaVprQtra2skJibKbQ4ODnBzc8OJEyfQ1NQEALC0tISvry9Onz6N2tpaAICxsTGCg4ORk5OD8vJy+fnDhw9HSUkJCgoKtNaFSqVCRkaG3Obj4wMTExOcOnVKbnN1dYWjoyOOHz8ut93suhgwYADCw8NRUFCAkpISrXVRVVWFnJwcrXUBQN5gAS1HDS0tLXHixAm5zdHREa6urkhMTERzczMAwMrKCj4+PkhLS8PFixcBQJ4HdvbsWa3D0O2ti+DgYNTX12vNCfP19YVSqURycvJ114WdnR08PT1x6tQp+TSsmZkZAgICkJGRIW+cDQ0NERYWhvz8fK2/qfDwcFRWViI3N1duGzp0KIQQWuvCy8sLFhYWWuvCyckJLi4uSEhIgFqtvqV1IUkSoqKiUFxcjMLCQnmZISEhqKurQ1ZWltzm5+cHIyMjrXXh5uYGe3t7xMXFyW2DBw+Gh4cHTp48CZVKBQAwNzeHv78/0tPTUV1dDQAwMjJCaGgo8vLyUFZWJj8/IiICFRUVWusiMDAQarUaZ86ckduGDBkCMzMzJCUlyW3Ozs5wdnbWWhfW1tbw9vZGamoq6urqAAADBw7EsGHDkJ2djYqKCgCAQqFAZGQkioqKUFRUdMN1YWhoiJSUFLnN3d0ddnZ2WnMOW7dfbddF6/ar7bpo3X5duS4iIyNRXl6OvLw8uW3YsGFoamq64fardV3Ex8dDo9EAAGxsbDBkyBCkpKTg0qVLAABTU1MEBgYiKysLlZWVAC5vv65cF929LS8qKsLq1athZGSExsZGDB48GCtXrkReXh4++OADTJ8+Hc3NzRBCYMWKFbCzs0N1dfVNbcsBYK6PGgqppS2zRsKRUgX+6K6G1e9zhs81AN/lG2CckwYeg1q2+SoN8HmWASJsNQixFvIyt2cr4GwK3O6gkbcFV27L5/uq8UuZhJxaCQ97a+TnJl+QEFeuwEND1DAxaGnLvyhhf7ECk13VcDBpaatuAnblGGCUvQZ+FpdrA+iWbXnb1/B6JNG6R9QBIyMjREZG4tdff5XbnnjiCcTFxeHo0aP49ddfMWrUKBQXF8PR0VF+zAMPPCBPtrqSSqWS/0CBltnkrq6uqK6uhrm5efcOiIiI6CZ4rPq+25adu2FKj9a8Vr3OqqmpgYWFxQ333zo9teTo6IihQ4dqtQUEBMhHKxwcHABA6z+C1tut911JqVTC3Nxc64eIiIj6Jp0GmVGjRmkdggOAjIwMuLu7A2g5hO/g4IDY2Fj5/pqaGvz222+IiYnp0b4SERFR76PTOTIrVqzAyJEj8fLLL+OBBx7A8ePHsXnzZmzevBlAy/ny5cuXY926dfDx8YGnpyeeffZZODk5Ydq0abrsOhEREfUCOg0yUVFR+Oabb7B69Wq88MIL8PT0xJtvvolZs2bJj3nqqadQV1eHRYsWoaqqCqNHj8aPP/4IY2NjHfaciIiIegOdf9fS1KlTMXXq1GveL0kSXnjhBbzwwgs92CsiIiLSBzr/igIiIiKijmKQISIiIr2l81NLREREuqSLa7pQ1+ERGSIiItJbDDJERKQXPDw84Ofnh9DQUISGhl51dfetW7dCkiTs2bNHNx0kneCpJSIi0hs7d+5EaGjoVe25ubn48MMPER0d3fOdIp3iERkiItJrGo0GCxcuxNtvvw2lUqnr7lAPY5AhIiK98cgjjyAoKAgLFixAeXk5AOD111/HqFGjEBERoePekS4wyBARkV44fPgwTp06hcTERNja2mLOnDlISUnBrl27sGbNGl13j3SEc2SIiEgvuLm5AQAMDQ2xfPly+Pr64siRI8jNzYWPjw8AoLS0FIsWLUJJSQkWL16sy+5SD+ERGSIi6vXq6upQVVUl3/7iiy8QFhaGxYsXo6SkBLm5ucjNzUV0dDQ2b97MENOP8IgMERH1emVlZZgxYwbUajWEEPDy8sK2bdt03S3qBRhkiIio1/Py8sKJEydu+LiDBw92f2eoV+GpJSIiItJbDDJERESktxhkiIiISG8xyBAREZHeYpAhIiIivcUgQ0RERHqLQYaIiIj0FoMMERER6S1eEI+IiHoVj1Xfd9uyczdM6bZlk27wiAwRERHpLQYZIiIi0lsMMkRERKS3GGSIiIhIbzHIEBERkd5ikCEiIiK9xSBDREREeotBhoiIiPQWgwwRERHpLQYZIiIi0lsMMkRERKS3GGSIiIhIbzHIEBERkd5ikCEiIiK9pdMg8/e//x2SJGn9+Pv7y/c3NDRgyZIlsLGxwaBBgzBjxgyUlZXpsMdERETUm+j8iExgYCBKSkrkn19++UW+b8WKFdi7dy+++uorHDp0CMXFxZg+fboOe0tERES9yQCdd2DAADg4OFzVXl1djS1btmD79u0YN24cAGDr1q0ICAjAsWPHEB0d3e7yVCoVVCqVfLumpqZ7Ok5EREQ6p/Mgk5mZCScnJxgbGyMmJgbr16+Hm5sbEhIS0NTUhAkTJsiP9ff3h5ubG44ePXrNILN+/XqsXbv2qvb4+HgMGjQIABAaGora2lpkZ2drLdvAwACpqalym4eHB2xsbJCQkCC32dvbw93dHUlJSWhsbAQAWFhYwM/PD2fOnJGDk1KpREhICHJzc3Hu3Dn5+VFRUSgrK0N+fr7cFhQUhMbGRqSnp8tt3t7eMDU1xcmTJ+U2FxcXODk5IS4uDkIIAICtrS28vLyQnJyM+vp6AMCgQYMwdOhQZGZm4sKFCwAAAwMDREREoLCwEMXFxfIyw8LCUF1djbNnz8ptAQEBkCQJaWlpWuvC2toaiYmJcpuDgwPc3Nxw4sQJNDU1AQAsLS3h6+uL06dPo7a2FgBgbGyM4OBg5OTkoLy8XH7+8OHDUVJSgoKCAq11oVKpkJGRIbf5+PjAxMQEp06dkttcXV3h6OiI48ePy203uy4GDBiA8PBwFBQUoKSkRGtdVFVVIScnR2tdAMDp06flNk9PT1haWuLEiRNym6OjI1xdXZGYmIjm5mYAgJWVFXx8fJCWloaLFy8CAExMTBAUFISzZ8/i/Pnz110XwcHBqK+vR2Zmptzm6+sLpVKJ5OTk664LOzs7eHp64tSpU2hoaAAAmJmZISAgABkZGaiqqgIAGBoaIiwsDPn5+SgtLZWfHx4ejsrKSuTm5sptQ4cOhRBCa114eXnBwsJCa104OTnBxcUFCQkJUKvVt7QuJElCVFQUiouLUVhYKC8zJCQEdXV1yMrKktv8/PxgZGSktS7c3Nxgb2+PuLg4uW3w4MHw8PDAyZMn5X9yzM3N4e/vj/T0dFRXVwMAjIyMEBoairy8PK1T2BEREaioqNBaF4GBgVCr1Thz5ozcNmTIEJiZmSEpKUluc3Z2hrOzs9a6sLa2hre3N1JTU1FXVwcAGDhwIIYNG4bs7GxUVFQAABQKBSIjI1FUVISioqIbrgtDQ0OkpKTIbe7u7rCzs0N8fLzc1rr9arsuWrdfbddF6/brynURGRmJ8vJy5OXlyW3Dhg1DU1PTDbdfresiPj4eGo0GAGBjY4MhQ4YgJSUFly5dAgCYmpoCAO5w1MDLrGU716QBPssyQJiNBmE2Ql7mjrMKOJi0PLbVvgIFGjXAve6X2/5XJiG7RsIjPhr576Tttny+b8trU1gn4T9FCtzloobTwJbn1jYBX+UYIGawBgGWl2tvzVAgwFIgevDltt25CpgOACa5XK5dWVnZ7rYcAOb6qKGQWtoyayQcKVXgj+5qWClb2s41AN/lG2CckwYeg1rqqDTA51kGiLDVIMT6cu3t2Qo4mwK3O1we45Xb8vm+avxSJiGnVsLD3pf7mHxBQly5Ag8NUcPEoKUt/6KE/cUKTHZVw8Gkpa26CdiVY4BR9hr4WVyuDaBbtuVt98fXI4nWPaIO7Nu3DxcvXoSfnx9KSkqwdu1aFBUVISUlBXv37sW8efO0jq4ALRv8sWPH4pVXXml3me0dkXF1dUV1dTXMzc27dTxERNR5Hqu+77Zl526YovN6fa3mtep1Vk1NDSwsLG64/9bpEZm7775b/j04OBgjRoyAu7s7vvzyS5iYmHRomUqlEkqlsqu6SERERL2Yzif7ttV6WiIrKwsODg5obGyUD4G3Kisra3dODREREfU/vSrIXLx4EdnZ2XB0dERERAQMDQ0RGxsr35+eno78/HzExMTosJdERETUW+j01NJf/vIX3HPPPXB3d0dxcTGef/55GBgY4KGHHoKFhQUWLFiAlStXwtraGubm5li2bBliYmKuOdGXiIiI+hedBpnCwkI89NBDqKiogJ2dHUaPHo1jx47Bzs4OAPDGG29AoVBgxowZUKlUmDRpEt59911ddpmIiIh6EZ0GmR07dlz3fmNjY2zatAmbNm3qoR4RERGRPulVc2SIiIiIbgWDDBEREektBhkiIiLSWwwyREREpLcYZIiIiEhvMcgQERGR3mKQISIiIr3FIENERER6i0GGiIiI9BaDDBEREektBhkiIiLSWwwyREREpLcYZIiIiEhvMcgQERGR3mKQISIiIr3FIENERER6i0GGiIiI9FaHgkxiYiKSk5Pl2//+978xbdo0PPPMM2hsbOyyzhERERFdT4eCzGOPPYaMjAwAwNmzZzFz5kwMHDgQX331FZ566qku7SARERHRtXQoyGRkZCA0NBQA8NVXX2HMmDHYvn07PvnkE+zatasr+0dERER0TR0KMkIIaDQaAMD+/fsxefJkAICrqyvOnz/fdb0jIiIiuo4OBZnIyEisW7cOn332GQ4dOoQpU6YAAHJycmBvb9+lHSQiIiK6lg4FmTfeeAOJiYlYunQp/va3v8Hb2xsA8PXXX2PkyJFd2kEiIiKiaxnQkSeFhIRofWqp1T/+8Q8MGNChRRIRERHdsg4dkfHy8kJFRcVV7Q0NDfD19e10p4iIiIhuRoeCTG5uLtRq9VXtKpUKhYWFne4UERER0c24pfNA3377rfz7Tz/9BAsLC/m2Wq1GbGwsPD09u653RERERNdxS0Fm2rRpAABJkjBnzhyt+wwNDeHh4YF//vOfXdY5unkTJ05EaWkpFAoFzMzMsHHjRoSFheHHH3/EmjVr0NjYiIEDB+KDDz5ASEiIrrtLRETUJW4pyLReO8bT0xNxcXGwtbXtlk7Rrfvyyy9haWkJAPjmm28wd+5cHDx4ELNmzcLhw4cRGBiII0eOYNasWUhJSdFtZ4mIiLpIh+bI5OTkMMT0Mq0hBgCqq6shSRKys7NhY2ODwMBAAMBtt92G/Px8JCYm6qiXREREXavDn5WOjY1FbGwszp07Jx+pafXxxx93umN06x555BH8/PPPAIAffvgBbm5uqKiowK+//oqRI0fi22+/RW1tLXJzcxEeHq7j3hIREXVeh4LM2rVr8cILLyAyMhKOjo6QJKmr+0UdsG3bNgDAp59+iqeffho//PADvv76a6xevRoXL15ETEwMhg4dymv9EBFRn9GhPdr777+PTz75BA8//HBX94e6wJw5c/D444+joqICY8eOxdixYwG0fDzewcEBQ4cO1XEPiYiIukaH5sg0Njbyqwh6kaqqKhQXF8u39+zZAxsbG1hbW6OkpERuf/HFFzFu3Dj5KyWIiIj0XYeOyCxcuBDbt2/Hs88+29X9oQ6orq7G/fffj/r6eigUCtjZ2eG7776DJEl47rnncOTIETQ3NyMmJgZbtmzRdXeJiIi6TIeCTENDAzZv3oz9+/cjODgYhoaGWve//vrrt7zMDRs2YPXq1fjzn/+MN998U67z5JNPYseOHVCpVJg0aRLeffddfsP2Fdzd3XH8+PF27/vwww97uDdEREQ9p0NB5tSpUwgNDQWAq65J0pGJv3Fxcfjggw8QHBys1b5ixQp8//33+Oqrr2BhYYGlS5di+vTp+N///teRbhMREVEf06Eg0/oR365w8eJFzJo1Cx9++CHWrVsnt1dXV2PLli3Yvn07xo0bBwDYunUrAgICcOzYMURHR7e7PJVKBZVKJd+uqanpsr4SERFR76Lzz+EuWbIEU6ZMwYQJE7SCTEJCApqamjBhwgS5zd/fH25ubjh69Og1g8z69euxdu3aq9rj4+MxaNAgAEBoaChqa2uRnZ2ttWwDAwOkpqbKbR4eHrCxsUFCQoLcZm9vD3d3dyQlJaGxsREAYGFhAT8/P5w5c0YOTkqlEiEhIcjNzcW5c+fk50dFRaGsrAz5+flyW1BQEBobG5Geni63eXt7w9TUFCdPnpTbXFxc4OTkhLi4OAghAAC2trbw8vJCcnIy6uvrAQCDBg3C0KFDkZmZiQsXLgAADAwMEBERgcLCQq2JwWFhYaiursbZs2fltoCAAEiShLS0NK11YW1trXUxPQcHB7i5ueHEiRNoamoC0HJhPl9fX5w+fRq1tbUAAGNjYwQHByMnJwfl5eXy84cPH46SkhIUFBRorQuVSoWMjAy5zcfHByYmJjh16pTc5urqCkdHR61Taje7LgYMGIDw8HAUFBRoTYYOCwtDVVUVcnJytNYFAJw+fVpu8/T0hKWlJU6cOCG3OTo6wtXVFYmJiWhubgYAWFlZwcfHB2lpabh48SIAwMTEBEFBQTh79izOnz9/3XURHByM+vp6ZGZmym2+vr5QKpVITk6+7rqws7ODp6cnTp06hYaGBgCAmZkZAgICkJGRgaqqKgAtXy0SFhaG/Px8lJaWys8PDw9HZWUlcnNz5bahQ4dCCKG1Lry8vGBhYaG1LpycnODi4oKEhAT5y2Vvdl1IkoSoqCgUFxdrfQFtSEgI6urqkJWVJbf5+fnByMhIa124ubnB3t4ecXFxctvgwYPh4eGBkydPyv/kmJubw9/fH+np6aiurgYAGBkZITQ0FHl5eSgrK5OfHxERgYqKCq11ERgYCLVajTNnzshtQ4YMgZmZGZKSkuQ2Z2dnODs7a60La2treHt7IzU1FXV1dQCAgQMHYtiwYcjOzkZFRQUAQKFQIDIyEkVFRSgqKrrhujA0NNQ6Qu7u7g47OzvEx8fLba3br7bronX71XZdtG6/rlwXkZGRKC8vR15entw2bNgwNDU13XD71bou4uPj5WuP2djYYMiQIUhJScGlS5cAAKampgCAOxw18DJr2c41aYDPsgwQZqNBmI2Ql7njrAIOJi2PbbWvQIFGDXCv++W2/5VJyK6R8IiPRv47abstn+/b8toU1kn4T5ECd7mo4TSw5bm1TcBXOQaIGaxBgOXl2lszFAiwFIgefLltd64CpgOASS6Xa1dWVra7LQeAuT5qKH4/iZFZI+FIqQJ/dFfDStnSdq4B+C7fAOOcNPAY1FJHpQE+zzJAhK0GIdaXa2/PVsDZFLjd4fIYr9yWz/dV45cyCTm1Eh72vtzH5AsS4soVeGiIGiYGLW35FyXsL1ZgsqsaDiYtbdVNwK4cA4yy18DP4nJtAN2yLW+7P74eSbTuEW/B2LFjr3sK6cCBAze1nB07duCll15CXFwcjI2NcccddyA0NBRvvvkmtm/fjnnz5mkdXQFaNvhjx47FK6+80u4y2zsi4+rqiurqapibm99Uv4iISHc8Vn3fbcvO3TBF5/X6Ws1r1eusmpoaWFhY3HD/3aEjMq3zY1o1NTUhKSkJKSkpV32Z5LUUFBTgz3/+M/773//C2Ni4I91ol1KphFKp7LLlERERUe/VoSDzxhtvtNv+97//XT5sfCMJCQk4d+6c1qXy1Wo1Dh8+jHfeeQc//fQTGhsbUVVVpfU9QmVlZXBwcOhIt4mIiKiP6dAF8a5l9uzZN/09S+PHj0dycjKSkpLkn8jISMyaNUv+3dDQELGxsfJz0tPTkZ+fj5iYmK7sNhEREempLp3se/To0Zs+TWRmZoZhw4ZptZmamsLGxkZuX7BgAVauXAlra2uYm5tj2bJliImJueZE3/5AF+dViYiIeqsOBZnp06dr3RZCoKSkBPHx8V16td833ngDCoUCM2bM0LogHhERERHQwSBjYWGhdVuhUMDPzw8vvPACJk6c2OHOHDx4UOu2sbExNm3ahE2bNnV4mdR3TJw4EaWlpVAoFDAzM8PGjRsRFhYGDw8PKJVKmJi0fEZw9erVePDBB3XcWyIi6gkdCjJbt27t6n4Q3dCXX34pT/z+5ptvMHfuXPnaDDt37rzq03RERNT3dWqOTEJCgnxxrMDAQISFhXVJp4ja0/bTa9XV1R36OgwiIupbOhRkzp07h5kzZ+LgwYPyzqWqqgpjx47Fjh07YGdn15V9JJI98sgj8ldk/PDDD1rtQggMHz4cGzZs4HuQiKif6NDHr5ctW4ba2lqkpqaisrISlZWVSElJQU1NDZ544omu7iORbNu2bSgoKMC6devw9NNPAwAOHz6MU6dOITExEba2tjd9UUYiItJ/HQoyP/74I9599135e2iAlu9h2bRpE/bt29dlnSO6ljlz5uDnn39GRUUF3NzcALR8b9Dy5ctx5MgRHfdOv0ycOBHBwcEIDQ3FbbfdpvW9SUDLnDhJkrBnzx7ddJCI6Do6dGpJo9HA0NDwqnZDQ0P5y8CIulJVVRUuXboEJycnAMCePXtgY2MDY2Njras/f/HFF5yrdYuuN4k6NzcXH374Yb++dhMR9W4dCjLjxo3Dn//8Z3zxxRfyjqWoqAgrVqzA+PHju7SDREDL5N77778f9fX1UCgUsLOzw3fffYeysjLMmDEDarUaQgh4eXlh27Ztuu6uXrnWJGqNRoOFCxfi7bffxpNPPqmj3hERXV+Hgsw777yDP/zhD/Dw8ICrqyuAli+BHDZsGP71r391aQeJAMDd3V3ra97buvJUCN269iZRv/766xg1ahQiIiJ02TUiouvqUJBxdXVFYmIi9u/fjzNnzgAAAgICMGHChC7tHBH1jNajWJ9++imefvppvPrqq9i1axcOHz6s454REV3fLU32PXDgAIYOHYqamhpIkoQ777wTy5Ytw7JlyxAVFYXAwEBOtCTSY62TqP/9738jNzcXPj4+8PDwwLFjx7Bo0SK89957uu4iEZGWWwoyb775Jh599FGYm5tfdZ+FhQUee+wxvP76613WOSLqXlVVVSguLpZvt06ifuaZZ1BSUoLc3Fzk5uYiOjoamzdvxuLFi3XYWyKiq93SqaWTJ0/ilVdeueb9EydOxGuvvdbpThFRz7jWJGpeNZmI9MUtBZmysrJ2P3YtL2zAAJSXl3e6U0TUM643ibqtK7/QlYiot7ilU0vOzs5ISUm55v2nTp2Co6NjpztFREREdDNuKchMnjwZzz77LBoaGq66r76+Hs8//zymTp3aZZ0jIiIiup5bOrW0Zs0a7N69G76+vli6dCn8/PwAAGfOnMGmTZugVqvxt7/9rVs6SkRERHSlWwoy9vb2+PXXX7F48WKsXr0aQggAgCRJmDRpEjZt2gR7e/tu6Sj1Hx6rvu+2ZedumNJtyyYiop53yxfEc3d3xw8//IALFy4gKysLQgj4+PjAysqqO/pHREREdE0durIvAFhZWSEqKqor+0JERER0S25psi8RERFRb9LhIzJEpF8494iI+iIekSEiIiK9xSBDREREeotBhoiIiPQWgwwRERHpLQYZIiIi0lsMMkRERKS3GGSIiIhIbzHIEBERkd5ikCEiIiK9xSBDREREeotBhoiIiPQWgwwRERHpLQYZIiIi0lsMMkRERKS3dBpk3nvvPQQHB8Pc3Bzm5uaIiYnBvn375PsbGhqwZMkS2NjYYNCgQZgxYwbKysp02GMiIiLqTXQaZFxcXLBhwwYkJCQgPj4e48aNw7333ovU1FQAwIoVK7B371589dVXOHToEIqLizF9+nRddpl+19DQgGnTpsHX1xchISG48847kZWVBQAYMWIEQkNDERoaimHDhkGSJJw6dUrHPSYior5ogC6L33PPPVq3X3rpJbz33ns4duwYXFxcsGXLFmzfvh3jxo0DAGzduhUBAQE4duwYoqOjddFlamPRokW4++67IUkS3nnnHSxcuBAHDx7Eb7/9Jj/m66+/xtq1axEcHKzDnhIRUV/Va+bIqNVq7NixA3V1dYiJiUFCQgKampowYcIE+TH+/v5wc3PD0aNHr7kclUqFmpoarR/qesbGxpg8eTIkSQIAREdHIzc396rHbdmyBQsWLOjh3hERUX+h0yMyAJCcnIyYmBg0NDRg0KBB+OabbzB06FAkJSXByMgIlpaWWo+3t7dHaWnpNZe3fv16rF279qr2+Ph4DBo0CAAQGhqK2tpaZGdny/f7+/vDwMBAPq0FAB4eHrCxsUFCQoJWfXd3dyQlJaGxsREAYGFhAT8/P5w5c0YOTkqlEiEhIcjNzcW5c+fk50dFRaGsrAz5+flyW1BQEBobG5Geni63eXt7w9TUFCdPnpTbXFxcAABzfdRQtOQHZNZIOFKqwB/d1bBStrSdawC+yzfAOCcNPAYJAIBKA3yeZYAIWw1CrIW8zO3ZCjibArc7aHD8+HEAQEBAACRJQlpamta6sLa2RmJiotzm4OAANzc3nDhxAs8++yxGjBiBjIwM+Pr64vTp08jKysLPP/+MVatWAQBycnJQXl4uP3/48OEoKSlBQUGB1rpwMRWY6KyR2/YXKVDVCNznebktrlxC8gUF5vmq8fuqQEa1hF/KFJjuoYalUUtbWT3wfYEBxjtp4D5I4Pjx4xgwYADCw8NRUFCAkpISeZlhYWGoqqpCTk6O3BYQEAAAOH36tNzm6ekJS0tLnDhxQm5zdHSEq6srEhMT0dzcDACwsrKCj48P0tLScPHiRQCAiYkJgoKCcPbsWZw/f/666yI4OBj19fXIzMyU23x9faFUKpGcnCy3ubq6wtHRUX79AMDOzg6enp44deoUGhoaAACTXdX4ocAAE5w0cPv9fVGvBr7INkCUnQZBVpffF59lKeBpJjDa/nLb3nwFFBIwxfXy63CoVIGiOmjVdnJygouLCxISEqBWq29pXUiShKioKBQXF6OwsFBeZkhICOrq6uTTlwDg5+cHIyMjrXXh5uYGe3t7xMXFyW2DBw+Gh4cHTp48CZVKBQAwNzeHv78/0tPTUV1dDQAwMjJCaGgo8vLytObiRUREoKKiQiuoBwYGQq1W48yZM3LbkCFDYGZmhqSkJLnN2dkZzs7OWuvC2toa3t7eSE1NRV1dHQBg4MCBGDZsGLKzs1FRUQEAUCgUiIyMRFFREYqKim64LgwNDZGSkiK3ubu7w87ODvHx8XJb6/ar7bpo3X61XRet268r10VkZCTKy8uRl5cntw0bNgxNTU033H61rov4+HhoNC3vIRsbGwwZMgQpKSm4dOkSAMDU1BQAcIejBl5mLe+/Jg3wWZYBwmw0CLO5/J7ccVYBB5OWx7baV6BAowa41/1y2//KJGTXSHjE5/J2ru22fL5vy2tTWCfhP0UK3OWihtPAlufWNgFf5RggZrAGAZaXa2/NUCDAUiB68OW23bkKmA4AJrlcrl1ZWdlrtuXzfdX4pUxCTq2Eh70v9zH5goS4cgUeGqKGiUFLW/5FCfuLFZjsqoaDSUtbdROwK8cAo+w18LO4XBtAu9tylUqFjIwMuc3HxwcmJiZaUw3a237Z2trCy8tLa398PZIQQtz4Yd2nsbER+fn5qK6uxtdff42PPvoIhw4dQlJSEubNmyf/sbUaPnw4xo4di1deeaXd5alUKq3n1NTUwNXVFdXV1TA3N+/WsfQEj1Xfd9uyczdM6dDzXn75ZezduxexsbEYOHCg3P7iiy8iOTkZX3755S0trzeOsS/geiV90dPvVV38bfSlmt31919TUwMLC4sb7r91fkTGyMgI3t7eAFr+84mLi8Nbb72FBx98EI2NjaiqqtI6KlNWVgYHB4drLk+pVEKpVHZ3t+l3r732Gnbv3o39+/drhRghBLZu3Yr33ntPh70jIqK+rtfMkWml0WigUqkQEREBQ0NDxMbGyvelp6cjPz8fMTExOuwhtXr99dfxxRdf4L///e9VpwAPHDiA5uZm3HnnnbrpHBER9Qs6PSKzevVq3H333XBzc0NtbS22b9+OgwcP4qeffoKFhQUWLFiAlStXwtraGubm5li2bBliYmL4iaVeoLCwEE8++SS8vLwwduxYAC1Hw1o/sbRlyxbMmzcPCkWvy8pERNSH6DTInDt3Do888ghKSkpgYWGB4OBg/PTTT/J/8W+88QYUCgVmzJgBlUqFSZMm4d1339Vll+l3Li4uuN70qu3bt/dgb4iIqL/SaZDZsmXLde83NjbGpk2bsGnTph7qERH1NQ0NDZg5cybS0tJgYmKCwYMH47333oO3tzfuuOMO5OXlwcLCAgAwZ84crFixQsc9JqJbofPJvkRE3e1aF28EWo78Tps2Taf9I6KO4wQGIurTbvbijUSknxhkiKhfeeutt3DvvffKt1etWoWgoCA8+OCDOHv2rA57RkQdwVNLRNRvvPzyy8jKypIv6/DZZ5/B1dUVQghs2rQJU6dO1bqiNRH1fjwiQ0T9QuvFG/ft2ydfvNHV1RVAy1cjLF26FGfPnpW/IoCI9AODDBH1ee1dvLG5uVnre4R27doFe3t72NjY6KiXRNQRPLVERH3atS7eeODAAUyZMgUqlQoKhQK2trb49ttvddxbIrpVDDJ0Q/r2RWNEbV3v4o1tvxmaiPQTTy0RERGR3mKQISIiIr3FIENERER6i0GGiIiI9BaDDBEREektBhkiIiLSWwwyREREpLcYZIiIiEhv8YJ4RNRndNfFGwFewJGot+IRGSIiItJbDDLdoKGhAdOmTYOvry9CQkJw5513IisrCwDw8ssvw8/PDwqFAnv27NFtR4mIiPQcg0w3WbRoEdLT03Hy5Ence++9WLhwIQBgwoQJ2LdvH8aMGaPjHhIREek/BpluYGxsjMmTJ0OSJABAdHQ0cnNzAQDDhw+Hl5eXDntHRETUdzDI9IC33noL9957r667QURE1OfwU0vd7OWXX0ZWVhZiY2N13RUiIqI+h0GmG7322mvYvXs39u/fj4EDB+q6O0RERH0Og0w3ef311/HFF19g//79sLS01HV3iIiI+iTOkekGhYWFePLJJ1FVVYWxY8ciNDQUI0aMAACsW7cOLi4uOHr0KBYuXAgXFxeUl5fruMdERET6iUdkuoGLiwuEEO3et2bNGqxZs6aHe0RERNQ38YgMERER6S0GGSIiItJbDDJERESktxhkiIiISG8xyBAREZHeYpAhIupiTzzxBDw8PCBJEpKSkuT2H374AeHh4QgNDcWwYcPw6aef6q6TRH0EgwwRURe777778Msvv8Dd3V1uE0Jg9uzZ+OSTT5CUlITvvvsOjz32GGpra3XYUyL9x+vIdILHqu+7bdm5G6Z027KJqHuNGTOm3XZJklBVVQUAqKmpgY2NDZRKZQ/2jKjv0ekRmfXr1yMqKgpmZmYYPHgwpk2bhvT0dK3HNDQ0YMmSJbCxscGgQYMwY8YMlJWV6ajHREQdI0kSdu7cienTp8Pd3R2jR4/Gp59+CiMjI113jUiv6TTIHDp0CEuWLMGxY8fw3//+F01NTZg4cSLq6urkx6xYsQJ79+7FV199hUOHDqG4uBjTp0/XYa+JiG5dc3Mz1q1bh927dyMvLw+xsbF4+OGHcf78eV13jUiv6fTU0o8//qh1+5NPPsHgwYORkJCAMWPGoLq6Glu2bMH27dsxbtw4AMDWrVsREBCAY8eOITo6WhfdJiK6ZUlJSSguLpZPO0VFRcHFxQUnTpzAnXfeqePeEemvXjXZt7q6GgBgbW0NAEhISEBTUxMmTJggP8bf3x9ubm44evRou8tQqVSoqanR+iEi0jVXV1eUlJTg9OnTAICsrCxkZ2fDz89Pxz0j0m+9ZrKvRqPB8uXLMWrUKAwbNgwAUFpaCiMjI1haWmo91t7eHqWlpe0uZ/369Vi7du1V7fHx8Rg0aBAAIDQ0FLW1tcjOzpbv9/f3h4GBAVJTU+U2Dw8P2NjYICEhQau2u7s7kpKSMN9XDQAorJPwnyIF7nJRw2lgy+Nqm4CvcgwQM1iDAMvLXyC5NUOBAEuB6MGX23bnKmA6AJjkopHbKisrYWpqipMnT8ptLi4uAIC5PmoopJa2zBoJR0oV+KO7Gla/zxk81wB8l2+AcU4aeAxqqaPSAJ9nGSDCVoMQ68u1t2cr4GwK3O6gwfHjxwEAAQEBkCQJaWlpAID5vmr8UiYhp1bCw96X+5h8QUJcuQIPDVHDxKClLf+ihP3FCkx2VcPBpKWtugnYlWOAUfYa+Flof5lmSUkJCgoK5NtBQUFwMRWY6Hy5zv4iBaoagfs8L7fFlUtIvqDAPF81fl8VyKiW8EuZAtM91LD8fdpBWT3wfYEBxjtp4D5I4Pjx4xgwYADCw8NRUFCAkpISeZlhYWGoqqpCTk6O3BYQEAAA8s4HADw9PWFpaYkTJ07IbY6OjnB1dUViYiKam5sBAFZWVvDx8UFaWhouXrwIADAxMUFQUBDOnj2rdUph+PDhV62L4OBg1NfXIzMzU27z9fWFUqlEcnKy3Obq6gpHR0f59QMAOzs7eHp64tSpU2hoaAAATHZV44cCA0xw0sDt9/dFvRr4ItsAUXYaBFldfm0+y1LA00xgtP3ltr35CigkYIrr5dfhUKkCRXXQqu3k5AQXFxckJCRArVbf0rqQJAlRUVEoLi5GYWGhvMyQkBDU1dUhKytLbvPz84ORkZHWuhhqqcHpKgnzfC/38XSVhKPnFLjfUw0zw5a24kvAj4UGmOisgYtpyxjrmoGdZw0wwk6DwDbrYlumAkPMhdYYAwMDoVarcebMGbltyJAhMDMzQ1JSEtavX4///e9/qKysxKRJk2BkZIRdu3bhqaeewrRp02BsbIz6+nqsWLECpaWlqKmpwbBhw5CdnY2KigoAgEKhQGRkJIqKilBUVHTDdWFoaIiUlBS5zd3dHXZ2doiPj5fbWrdfJ0+ehEqlAgBYWFjAz88P6enp8j+TSqUSISEhyMvL05qTGBkZifLycuTl5cltw4YNQ1NTk9b8Rm9v76u2X87OznB2dkZ8fDw0mpbXx8bGBkOGDEFKSgouXboEADA1NQUA3OGogZdZy+vQpAE+yzJAmI0GYTaXX5sdZxVwMGl5bKt9BQo0aoB73S+3/a9MQnaNhEd8Lm/nuC3v/m25SqVCRkaG3Obj4wMTExOcOnVKbmtv+2VrawsvLy+t/fH1SOJaX9PcwxYvXox9+/bhl19+kV/k7du3Y968efIfXKvhw4dj7NixeOWVV65ajkql0np8TU0NXF1dUV1dDXNz8y7tsy4+tdSXavamMfYH/WG99ocx9gc9/Tr2pe2qLmp2199GTU0NLCwsbrj/7hWnlpYuXYrvvvsOP//8sxxiAMDBwQGNjY3yxxVblZWVwcHBod1lKZVKmJuba/0QUe9xrYvFqVQqLF26FD4+PggKCsLs2bN110ki0hs6DTJCCCxduhTffPMNDhw4AE9PT637IyIiYGhoiNjYWLktPT0d+fn5iImJ6enuElEXaO9icQCwatUqSJKEjIwMJCcn47XXXtNRD4lIn+h0jsySJUuwfft2/Pvf/4aZmZk878XCwgImJiawsLDAggULsHLlSlhbW8Pc3BzLli1DTEwMP7FEpKfau1hcXV0dtmzZgsLCQkhSy6SBax11JSJqS6dHZN577z1UV1fjjjvugKOjo/yzc+dO+TFvvPEGpk6dihkzZmDMmDFwcHDA7t27ddhrIupq2dnZsLa2xssvv4zIyEjcdtttWkdiiYiuRadHZG5mnrGxsTE2bdqETZs29UCPiEgXmpubkZeXh6FDh2LDhg3ytVVSU1Nhb2+v6+4RUS/WKyb7ElH/5ubmBoVCgVmzZgFo+Si8p6en1keriYjawyBDRDpna2uL8ePH46effgIA5OTkICcnR76ODxHRtfSaC+IRUf/w2GOP4fvvv0dpaSkmTZoEMzMzZGVl4f3338eCBQvw9NNPQ6FQ4IMPPoCzs7Ouu3tdvG4Nke4xyBBRj/rggw/abffy8sLPP//cw70hIn3HU0tERHruWhcZbLV161ZIkoQ9e/b0eN+IuhuDDBGRnrvWRQYBIDc3Fx9++CGvvUV9FoMMEZGeGzNmjNbXu7TSaDRYuHAh3n77bSiVSh30jKj7McgQEfVRr7/+OkaNGoWIiAhdd4Wo23CyLxFRH5SSkoJdu3bh8OHDuu4KUbdikCEi6oOOHDmC3Nxc+Pj4AABKS0uxaNEilJSUYPHixTruHVHX4aklIqI+aPHixSgpKUFubi5yc3MRHR2NzZs3M8RQn8MjMkTULXixuJ5zrYsMEvUHDDJERHruWhcZbOvgwYPd3xEiHeCpJSIiItJbDDJERESktxhkiIiISG8xyBAREZHeYpAhIiIivcUgQ0RERHqLQYaIiIj0FoMMERER6S1eEI+ISI/wislE2nhEhoiIiPQWgwwRERHpLQYZIiIi0lsMMkRERKS3GGSIiIhIbzHIEBERkd5ikCEiIiK9xSBDREREeotBhoiIiPQWgwwRERHpLQYZIiLqtB9++AHh4eEIDQ3FsGHD8Omnn+q6S9RP8LuWiIioU4QQmD17Ng4ePIjg4GDk5ubC398f06dPh5mZma67R30cj8gQEVGnSZKEqqoqAEBNTQ1sbGygVCp12ynqF3QaZA4fPox77rkHTk5OkCQJe/bs0bpfCIHnnnsOjo6OMDExwYQJE5CZmambzhIRUbskScLOnTsxffp0uLu7Y/To0fj0009hZGSk665RP6DTIFNXV4eQkBBs2rSp3ftfffVVbNy4Ee+//z5+++03mJqaYtKkSWhoaOjhnhIR0bU0Nzdj3bp12L17N/Ly8hAbG4uHH34Y58+f13XXqB/Q6RyZu+++G3fffXe79wkh8Oabb2LNmjW49957AQDbtm2Dvb099uzZg5kzZ/ZkV4mI6BqSkpJQXFyMMWPGAACioqLg4uKCEydO4M4779Rx76iv67VzZHJyclBaWooJEybIbRYWFhgxYgSOHj16zeepVCrU1NRo/RARUfdxdXVFSUkJTp8+DQDIyspCdnY2/Pz8dNwz6g967aeWSktLAQD29vZa7fb29vJ97Vm/fj3Wrl17VXt8fDwGDRoEAAgNDUVtbS2ys7Pl+/39/WFgYIDU1FS5zcPDAzY2NkhISNCq7+7ujqSkJMz3VQMACusk/KdIgbtc1HAa2PK42ibgqxwDxAzWIMBSyM/fmqFAgKVA9ODLbbtzFTAdAExy0chtlZWVMDU1xcmTJ+U2FxcXAMBcHzUUUktbZo2EI6UK/NFdDavf59WdawC+yzfAOCcNPAa11FFpgM+zDBBhq0GI9eXa27MVcDYFbnfQ4Pjx4wCAgIAASJKEtLQ0AMB8XzV+KZOQUyvhYe/LfUy+ICGuXIGHhqhhYtDSln9Rwv5iBSa7quFg0tJW3QTsyjHAKHsN/Cwu1waAkpISFBQUyLeDgoLgYiow0flynf1FClQ1Avd5Xm6LK5eQfEGBeb5q/L4qkFEt4ZcyBaZ7qGH5+6n5snrg+wIDjHfSwH2QwPHjxzFgwACEh4ejoKAAJSUl8jLDwsJQVVWFnJwcuS0gIAAA5A00AHh6esLS0hInTpyQ2xwdHeHq6orExEQ0NzcDAKysrODj44O0tDRcvHgRAGBiYoKgoCCcPXtW67D78OHDr1oXwcHBqK+v15oX5uvrC6VSieTkZLnN1dUVjo6O8usHAHZ2dvD09MSpU6fkU7GTXdX4ocAAE5w0cPv9fVGvBr7INkCUnQZBVpdfm8+yFPA0Exhtf7ltb74CCgmY4nr5dThUqkBRHbRqOzk5wcXFBbO81VD+/q9S7kUJB4oVmOqmxmDjlrYLKuCbPAPc5qCBj3lLHY0APsk0QIi1BhG2l2t/eVYBG2NgvFNL7ePHj8PPzw9GRkZa62KopQanqyTM873cx9NVEo6eU+B+TzXMDFvaii8BPxYaYKKzBi6mLXXqmoGdZw0wwk6DwDbrYlumAkPMhdYYAwMDoVar5W0AABwsUaC0Hpjpdbn2iQoJJyoUeNhbDcPf18XZWgkHSxT4g5satr+viwoV8O88A9zuoMGQ39dFfHw8IiMjUVRUhKKiInmZpgME7IyBcU6X6/xYqECDGpjmfrnt6DkJ6dUS5vpcbkurknDsnAIPeKox6Pd1UXRJwk+FCkxyubwNUCqVCAkJQV5eHsrKyuTnR0ZGory8HHl5eXLbsGHDYGxsjKeeegr33HMPJEmCoaEh3njjDZSWlsrba2dnZzg7OyM+Ph4aTUufbGxsMGTIEKSkpODSpUst4zM1BQDc4aiBl1nLumjSAJ9lGSDMRoMwm8uvzY6zCjiYtDy21b4CBRo1wL1t1sX/yiRk10h4xOfyGLkt7/5tuUqlQkZGhtzm4+MDExMTnDp1Sm5rb/tla2sLLy8vrf3x9UhCCHHjh3U/SZLwzTffYNq0aQCAX3/9FaNGjUJxcTEcHR3lxz3wwAPyxLL2qFQqqFQq+XZNTQ1cXV1RXV0Nc3PzLu2zx6rvu3R5beVumNLna/amMfYHPb1e+9J79Vo1+8MYdYGvo37V7K73TU1NDSwsLG64/+61p5YcHBwAQOu/gdbbrfe1R6lUwtzcXOuHiIiI+qZeG2Q8PT3h4OCA2NhYua2mpga//fYbYmJidNgzIiIi6i10Okfm4sWLyMrKkm/n5OQgKSkJ1tbWcHNzw/Lly7Fu3Tr4+PjA09MTzz77LJycnOTTT0RERNS/6TTIxMfHY+zYsfLtlStXAgDmzJmDTz75BE899RTq6uqwaNEiVFVVYfTo0fjxxx9hbGysqy4TERFRL6LTIHPHHXfgenONJUnCCy+8gBdeeKEHe0VERET6otfOkSEiIiK6EQYZIiIi0lu99oJ4RESke/3hujWk33hEhoiIiPQWgwzRLdq6dSskScKePXv6dE0iIn3AIEN0C3Jzc/Hhhx8iOjq6T9ckItIXDDJEN0mj0WDhwoV4++23oVQq+2xNIiJ9wiBDdJNef/11jBo1ChEREX26JhGRPuGnlohuQkpKCnbt2oXDhw/36ZpERPqGQYboJhw5cgS5ubnw8fEBAJSWlmLRokUoKSnB4sWL+0xNIiJ9w1NLRDdh8eLFKCkpQW5uLnJzcxEdHY3Nmzd3a6DQRU0iIn3DIENERER6i6eWiDrg4MGD/aImEVFvxyMyREREpLcYZIiIiEhvMcgQERGR3mKQISIiIr3FIENERER6i0GGiIiI9BaDDBEREektBhkiIiLSW7wgHhEAj1Xfd8tyczdM6dF616tJRNQX8YgMERER6S0GGSIiItJbDDJERESktxhkiIiISG8xyBAREZHeYpAhIiIivcUgQ0RERHqLQYaIiIj0FoMMERER6S0GGSIiItJbDDJERESktxhkiIiISG8xyBAREZHeYpAhIiIivaUXQWbTpk3w8PCAsbExRowYgePHj+u6S0RERNQL9Pogs3PnTqxcuRLPP/88EhMTERISgkmTJuHcuXO67hoRERHp2ABdd+BGXn/9dTz66KOYN28eAOD999/H999/j48//hirVq266vEqlQoqlUq+XV1dDQCoqanp8r5pVJe6fJmtrtXfvlSTY+wevaUmx9j19XRRk2Ps+np9rWZ37F/bLlcIcf0Hil5MpVIJAwMD8c0332i1P/LII+IPf/hDu895/vnnBQD+8Ic//OEPf/jTB34KCgqumxV69RGZ8+fPQ61Ww97eXqvd3t4eZ86cafc5q1evxsqVK+XbGo0GlZWVsLGxgSRJ3drf66mpqYGrqysKCgpgbm7e5+rpomZ/GKMuanKMfaMmx9g3avaHMV6LEAK1tbVwcnK67uN6dZDpCKVSCaVSqdVmaWmpm860w9zcvEffGD1dTxc1+8MYdVGTY+wbNTnGvlGzP4yxPRYWFjd8TK+e7GtrawsDAwOUlZVptZeVlcHBwUFHvSIiIqLeolcHGSMjI0RERCA2NlZu02g0iI2NRUxMjA57RkRERL1Brz+1tHLlSsyZMweRkZEYPnw43nzzTdTV1cmfYtIXSqUSzz///FWnvfpKPV3U7A9j1EVNjrFv1OQY+0bN/jDGzpKEuNHnmnTvnXfewT/+8Q+UlpYiNDQUGzduxIgRI3TdLSIiItIxvQgyRERERO3p1XNkiIiIiK6HQYaIiIj0FoMMERER6S0GGSIiItJbDDLUY/rDvPL+MEYiot6EQUbHrtzx9cUdoVqt1rqt0Wh01JPu0x/G2KovvkevpT+NlUhf9foL4vVl6enp+Pzzz5Gfn4/Ro0dj9OjR8Pf3h0ajgULRPRmzrKwM1dXV8PX17ZblX+n06dN4++23UVxcjICAANx3332IiIjokdpAy46ou78stD+MEQBUKhWUSiUaGxuhVCp7rC7Qc2OsqalBfX09jIyMYGVlBUmSenScutDXxwdwjH0dj8joSFpaGkaMGIG0tDRkZmbio48+wp133onY2FgoFIpu+U/w9OnTGD58OJ599lmkpqZ2+fKvdObMGURHR+PSpUsYMGAAEhISMGrUKHz22WfdWrewsBAJCQkA0O1/2P1hjEDL+/WRRx7B+PHj8fDDD+PAgQPdXjcnJwcHDx4EADlQdKfk5GTcfffdGDlyJCZNmoT58+ejubm5x3YOPXUUr7KyEjk5OTh79iyAnnn/tLryyGV36Q9jPHfuHJKTk3H8+HEAPTPG1r/B5ubmbq91SwT1uObmZjF79mwxa9Ysue3EiRNiwYIFwsDAQHz33XdCCCHUanWX1SwqKhIjR44UISEhYvjw4WLBggUiOTm5y5bfnj/96U9i2rRp8u2ysjKxZs0aYWBgIN59910hhBAajaZLa545c0bY29uLqKgoceTIkS5ddnv6wxjT09OFubm5WLRokVi6dKl48MEHhSRJ4sUXXxSVlZXdVtPGxkbY2tqKvXv3yu1dvS5b5ebmCjs7O/Hkk0+KXbt2iVdffVX4+PiIoKAgkZmZ2S01hRAiMzNT/P3vfxd1dXVCiK79m2/PyZMnRUhIiHB3dxdDhgwRkyZNEnl5ed1a8/Tp0+LRRx8VNTU1QoiW7V936g9jTEpKEj4+PsLT01PY29uL8PBwceTIEfl91B1SUlLE5MmTxYULF4QQQjQ1NXVbrVvFIKMDjY2N4vbbbxerVq3Saj937pxYvHixMDY2FkePHu3SmrGxsWLSpEkiKSlJfPLJJyI8PLzbw8z06dPFggULrmp/+eWXhSRJ4vvvvxdCdN3OqaSkRNxxxx1i1KhR4u677xYTJ04Uhw8f7pJlX0t/GOPf/vY3ceedd2q1bd68WUiSJFatWtXlG8+ysjJx1113iYkTJ4pZs2aJoUOHin//+9/y/d0RZnbt2iUiIyNFdXW13JadnS1GjBghAgICRFlZmRCia4NGZmamGDx4sLCxsRErV67s9jBTUFAgnJycxKpVq8TBgwfFV199JSIiIoSbm5vYv39/t+x8s7KyhLOzszA2NhYzZszo9h19fxhjSUmJ8PLyEs8884w4efKkiIuLExMmTBCOjo7io48+kut3pbNnzwpPT08hSZKIiIiQw0x3B7abxSCjI0uWLBExMTFX/Uebn58vZsyYISZPnqy1Ue2s+vp68euvv8q3P/74YznMnDp1Sm5v3Ul0xcb073//u3B1dRVFRUVay25sbBSPP/64CAgIECUlJZ2u0youLk6MHz9e/O9//xP79u3rkR19fxjj448/Lv7whz8IIVreF63vjW3btgmFQiE2b94shOi6gJGamiqmTp0q9u/fLxITE8XcuXO7Pcy88847wtbWVr7dOsbi4mIREhIiRo0a1aX1qqqqxLRp08R9990n/vrXv4oRI0aI5cuXd2uYOXDggBg6dKgoLi6W25qbm8Xdd98tHB0d5X+euqp2bW2tmDVrlrjvvvvEm2++KaKjo8W9997brTv6/jDG+Ph44e3tLc6cOaPVPm/ePOHm5ia2b9/epX8fdXV14oknnhAzZswQO3fuFNHR0SI4OLhXhRkGGR3ZuXOnCA0NFf/85z+vStCffPKJcHJyEvn5+V1a88o3d3tHZtauXStOnjzZ4RptNxC//fabGDVqlFi6dOlV/9Hu379fODk5iRMnTnS4VnuSkpLk37///nt5R3/o0KGr+tgVG7OjR4+KkSNH9ukxbtq0SQwcOFBkZGQIIVo2XK3vpRdffFFYWlqKrKysTtdpq+1GOj4+XsyZM0cMHTpU7NmzR27vig1o6zjy8vKEs7OzWL9+vXxf67r73//+J7y9vcWOHTs6Xa/tsp955hmxY8cOoVKpxAsvvCBGjBgh/vznP7cbZrpix/Tll18KS0tL0dDQIIQQQqVSyfeNHz9eBAQEdHlAXL9+vfjss89Ec3Oz+Oyzz7p9R98fxvjzzz8LW1tbkZ2dLYQQWkdEH3roIeHo6CjOnTsnhOi6wL9582axfft2IYQQv/zyS68LMwwyPSAnJ0ds3rxZfPTRR+LHH3+U25cuXSp8fX3Fu+++KyoqKuT21NRU4e3tLVJTU7u8phDab7rWMLNw4ULxwAMPCIVC0aG6rW/oK5e/YcMGER4eLv7617+KwsJCub2wsFD4+PiIX3755ZZrXel6O+sffvhB3HXXXWLSpEnyUYs///nP4tixY7dcJysrS2zYsEG8+OKLYtu2bXL7P//5TxEaGtqtY7yerhxjq7avYWFhobjrrrvE5MmTRW5urhDi8vnxtLQ04eLiIn766adOjODqmldKSEiQw0zrkZknnnhC7Nq1q0O1Wnd0jY2NQgghqqurxfLly8Vtt90mb7BbVVdXC19fX/HSSy91qNaVWsfZ1NQk72guXbok1q5dK4eZS5cuafWzK9TW1gpXV1exZMkSua11R19UVCS8vLzEq6++2iW12tuBqlQqsW3btqt29PX19Z0++txar7a2Vri4uPTIGNu+X1vrd+cY29YKDAzUmpvX9n0SEBAgli1b1mW1rtTc3CwOHz58VZi5dOmSOHv2bLfP82oPg0w3O3XqlLCxsRHR0dFiyJAhYtCgQWLu3LnyG3zBggVi2LBhYvny5SIrK0uUl5eLp556Svj6+orz5893Wc2FCxdedbi11ZYtW4ShoaGwsLDo0NGDtLQ04enpKZ599lm5rXUHIYQQzz33nBgxYoS45557RFJSksjMzBSrVq0S7u7unTrt0jY8XfnH0/YPsPUUzF133SWmTZsmJEkSiYmJt1QrOTlZWFhYiNtvv11ERUUJpVIp7rrrLvm03Lp160RUVFSXj7FtePrss8+07mv7GnbFGIUQory8vN3lf/7552L06NHivvvuk/8TFKLlFElAQIDWhNyuqimE9uvYGmaCgoLEpEmTOjzGlJQU8cc//lFMmDBBTJo0SRw8eFAI0XJUZsqUKeL2228XH3/8sdZz7rrrLvHaa69d1adbcWV4atW6o21oaBBr164V0dHRYvny5eLChQtiwYIFYvr06R2q17avGo1GNDc3i7feekuEhoZq7czVarVoaGgQY8aMEStXruxwrfZqtmob3j799FN5R3/+/Hnx2GOPiUmTJnVo8uilS5fk/rfW3LhxowgODu62MV5Zs1Xr69rVY2yrdTu3d+9e4eHhIZ544gn5vtb30cyZM8UjjzzSqTpttX0dW+trNBpx6NAhOcyUlZWJpUuXitGjR3frhONrYZDpRrW1tSImJkZOxyUlJWLfvn3C2tpajB8/Xj4VsXbtWnHbbbfJE6kcHBw6tIG+Uc277rpL6xSAWq0Wzc3N4oknnhBWVlYiJSXlluvl5+eL0NBQ4ePjI4YNGybWrl0r39f2sO7WrVvF3XffLSRJEsOGDRPu7u4dHqMQ7Yen64WZvXv3CisrK2Fpaal1auZmXLp0SUyaNEn86U9/EkK0/HeVlpYmvL29xciRI+UjH9u2bevSMbYXnqZMmaJ1pKXtjr8zYxSiZZ0aGxtrTV5uu9P98MMPxR133CGCg4PF/v37xdGjR8UzzzwjHB0dO3watL2a1wszx44dEy4uLsLKyqpDp0AzMjLkT2D99a9/Fffdd5+QJEmsWbNG1NXViZycHPHAAw+IoKAgMXv2bPHZZ5+Jxx9/XJibm8un1jriyvB06NAhrb+P1jG3hpmRI0cKHx8fMWjQoA5N/M/MzBTHjx8XQrT8XbSuw6KiIrFkyRIRERGh9bcqhBDTpk0TTz/9tBCiY2HtyppXal1mU1OT2LZtmxg5cqSwtbUVpqamHTp6mJycLMaPHy+io6NFYGCg2LZtm7hw4YKoqqoSS5cuFeHh4V0+xitrfvbZZ/L8OCG0A1tXjDE9PV0+2tm2v1VVVeK1114Tvr6+4tFHH9V6zsyZM8Wjjz6q9bp3Rc0raTQacfjwYTFq1CgxYMAAYWpqKn777bdbrtcVGGS6UX19vQgPD7/q3Hp6erqwtbUVU6dOldvKysrEvn37xC+//CIKCgq6rea0adO0dhTHjx8XkiSJuLi4W66l0WjEK6+8IiZPniz+85//iOeff174+/tfM8wI0TJvJjU1tVNHKa4XntoLM2q1WixfvlyYmZl1+FNao0aNkv/Da/2vqqioSAQHB4tRo0bJ56Sbm5u7ZIzXC09jxowRBw4ckB/bOgG3M2MsLCwUw4cPF+Hh4cLJyUksWrRIvq/ta3jgwAExe/ZsoVQqRUBAgPD39+9wWLtezfZOM6nVarFy5UphbGzc4ddxzZo1YuLEiVptGzduFNbW1uIvf/mLaGxsFMXFxeKjjz4S4eHhIioqSowdO7ZDwbDVtcLT888/r/Wx4NYxV1dXi6CgIGFlZaU1Ef9mpaenCxMTEyFJkvj555+FENqTtPPz88VTTz0lhgwZIiZMmCA2bNgg5s+fLwYNGiROnz7doTFeq+aVWneMFy9eFKNHjxZWVlYdei2zs7OFlZWVWLJkiXj77bfFsmXLhKWlpVi4cKHIysoSlZWV4umnnxZeXl5dNsb2alpZWYlFixaJ+Ph4+XGt4+7sGDMyMoSxsbGQJEl89dVXQoiW9de6DisqKsS7774rXFxcRFhYmFi8eLGYNWuWGDhwYIf+Kb1ezWupr68XU6ZMEdbW1h2u2RUYZLrRxYsXhbOzs9aOtvU/3JMnTwpTU1Px97//vcdrvvjii1rPaXuK5laVlJSITz75RAjREsZaw0zbcV15KL0zbiY8XbkTPHXqlHB2dtba2NxKvfr6ehEZGSkef/xxub11515SUiKsra3F4sWLOziia7teeLr99tu1Am9ycnKnxrhlyxYxffp08fPPP4utW7cKe3t7rWBx5WH006dPi4KCAq3TQl1d88rD8BkZGWLkyJGdOsr15JNPykGm7fLff/99MXDgQLFp0yatx9fX14v6+voO1xPi2uHJxsZGPP3006K0tFRuV6lUYvny5WLgwIEdCjHl5eVi6tSpYsqUKeL//u//hJWVlYiNjRVCaIeZyspKsX//fjFx4kQxbtw48Yc//KHDk/xvVPNKTU1NYs2aNcLY2LjDAfG1114TY8aM0Wr7/PPPRVBQkJg1a5bIy8sTdXV1XTbG69UMDg4WjzzyiFZYaWxs7NQYL1y4IO677z4xY8YMsWzZMqFQKMTOnTuFENphRqVSiezsbDF37lxx//33X9WPrqx5pebmZrFhwwZhZGTU5R9ouFUMMt3sn//8p3BxcdGaQ9C6Y1+3bp0YMWKEqKio6NIJUjdbs3VD3pWz+IuLi9sNM3v27Omyme03E56uXJ8dnWjXum527dollEql1iTf1h3ctm3bhIeHh8jNze2SdXmz4Wnp0qVaz+vMZMLy8nLx9ddfCyFaxvXxxx8Le3t7rcPWbSendoWbqXnle+bixYudqvnWW28JMzMz+XRA26NNa9euFaampl1+8bTrhSdTU1Px3nvvCSEuv2eXLVvWoUAqREtonzVrlvjPf/4jMjMzxbx584SVlZXYv3+/EKJlfba3renMPxs3qtlevZdeeqnToSI0NFTU1tZqLf+rr74S3t7eYvXq1Vc9p7P/UF2vpo+Pj/jb3/6mdTrn5Zdf7vAYs7OzxZ///Gexd+9eUVtbK1atWiUUCoV8pP1ap406s429Uc326n388cciLS2twzW7CoNMFyouLha//fab+PHHH+U3VE5Ojrj//vvFbbfddtUnO95//30REBDQqclRPV2zvXpCXH0evjVcPP/882L58uVCkiStc8ld6XrhqXXjdSs74NZxtd1YVVRUiCeeeEJ4eXld9amW3bt3d2py9pVuJTzl5eXJ/e1oyGjvebW1tfJRkrbB4rPPPuuSHf2t1szJybnm826FSqUSY8aMEdHR0fLr1bpOS0pKhKurq9i9e3enalzpRuFp0KBBXXqphbb/kaenp4u5c+cKKysr8d///lcIcXluXGePNN1KzdbJxl31KaydO3cKExMT+ehc23X63nvvCSMjo6tOdXT2vdORmp3R9orS1dXV4umnnxYKhUJ88cUXQojL67TtJ147O8Yb1RSiZfvYXVf07igGmS5y8uRJ4e7uLnx9fYWFhYXw8/MTX3zxhWhsbBRxcXFi6tSpIioqSn5DNDY2iqeeekrcfvvtHb4SY0/XvLKev7+/2L59u/yH1DbMFBcXi+eee05IkiSsrKw6/B9m67J6KjwlJyeLO+64Q96xtA0zKSkpYtGiRcLBwUFs3LhR1NfXi4sXL4pnnnlGhIeHd+qPu6fDU3v1rlRTU6N1ymflypVCkqQOB5merpmeni6eeuopMXfuXPHmm2/Kk3VjY2PF8OHDxfjx47V2ApWVlcLf379Tn8Bqjy7CU1sZGRlysGg9SvKXv/xFfP755932lQ/dVbPtc//4xz8KV1dX+UMTbUOSt7e32LhxY4fr6LLmtf4+amtr5WDRepTkySefFBs2bOiyT0PdSs2unDLQWQwyXeDcuXPC399fPPPMMyI7O1sUFRWJBx98UPj6+oq1a9eKhoYGkZSUJB5//HExYMAAERISIqKjo4WVlVWHzy32dM1r1QsICBDPP/98uxdgevjhh4W5uXmnrofTk+EpJydHeHt7C0mShI+PjzwHpe1GIjMzU6xbt04olUrh7e0tQkJChJ2dXac/ndST4el69a5UW1srtmzZIiRJEtbW1h0OpD1dMzU1VVhYWIi77rpLzJgxQ1hYWIhx48bJR7f27t0rhg8fLjw9PcVPP/0kDhw4INasWSMcHBw6dcSpp8PTteoJof2+bQ0WgwcPFlOnThWSJHX4tEdP1ywrK2v3UgspKSli1KhRwtPTU2u+WF1dnQgLC7vqcgW9ueaV9a6lNVgolUoxduxYIUlSh+cZ6aJmd2GQ6QKpqanCw8Pjqg3u008/LQIDA8Vrr70mNBqNuHjxojh69Kh48cUXxfvvv9+pL6Pr6ZrXqxcUFCReffVVrdNVH330kbC0tOzUDr4nw1N9fb1Ys2aN+OMf/yhiY2PFmDFjhLu7e7thRoiWya5btmwRO3bskE97dERPh6dr1btesJg3b54YNGhQhwNpT9dUqVRi9uzZWqenMjMzxYMPPiiioqLEBx98IIRo+ej3Qw89JOzs7ISvr68IDAwUCQkJt1yvVU+Hp/bqTZgwQXz44YfyY9q+j1JTU4Wrq6uwtrbu8I6op2umpaUJIyMjcd9997U7B+z48ePijjvuEJaWluKDDz4QX3zxhVi1apWwsbHRut5Rb655o3pXOn/+vAgICBDW1tYdDqO6qNmdGGS6wIkTJ4Szs7N8VdXWq3IK0XL1UXd39y5/8Xu65o3qeXp6atUrLS0VZ8+e7VTNng5P27dvlw+f5ubmittuu00rzNzMqZFb0dPh6Ub12hvX7t27hbu7e4ePxOiiphBC3HnnnfInoNp+DcHcuXPFqFGjxA8//CA/9vTp06KoqKjDn8ASoufD0/XqRUdHi7feektubz1quXz5cmFoaNjhT7X0dM3S0lIxcuRIMW7cOGFrayvuv//+dne6lZWVYuXKlSIgIED4+fmJESNGdHgb0NM1b7ZeK7VaLVasWCEkSerQp9p0VbO7Mch0UOuEuVajR4/W+mhe23OnkZGRYubMmXpXs6P1uurTST0RntRqdbvnejUajcjOzpZ3vK1fPVBfXy8SExO77OqVPR2eblTvyjrnz5/X+tqF3l6zublZNDY2innz5on77rtPNDQ0yNcSEqLlkxkxMTHigQcekJ/TVfNEejo8Xa/ebbfdJr799lv5senp6WLKlCmdOkLa0zX37dsn/u///k/ExcWJ3377TVhbW193p1tYWCguXLjQqctJ9HTNW61XUFAgHn/88U593FkXNbsbg0wHpKamilmzZonx48eLhQsXioMHD4qEhAQxZMgQcf/998uPa/2PeuXKleKee+7Rq5q6GKMQPRue2o7xscceE9999518X+tGOisrSw4zZ8+eFUuWLBGRkZGd2lj2dHi61XoNDQ0iMTFR1NbWdqieLmpe+fofPHhQGBgYaB0laH3MwYMHhUKh6LJPmPR0eLrZeg8++KDW8zrzeuqi5rlz5+SL6wnR8gWtrTvdqqoqub2zE111WfNm67V9v7T9h05fanY3BplbdObMGWFhYSFmzpwpVq1aJUJCQkRUVJRYvHix2L59u/Dy8hLTpk0TjY2N8h/57NmzxcyZMzt8HY6erqmLMQrRs+GpvTFGRkaK5cuXy49pHUd2dra44447hCRJwtTUVL4Me2fH2BPhSRdhradrpqeni9dee03ru8SEaLnuh0Kh0Jq/IUTLdzYFBAR0am6TED0fnjpar7OhqSdrXusfktbtzLFjx7SOIDQ2Nop3331X/Oc//+lQPV3U7Gi9K7/8t7fX7EkMMrdAo9GIZ555Rus/q5qaGvHCCy+I4cOHi//7v/8Te/bsEb6+vsLX11dMmzZNPPDAA8LU1LTD56V7uqYuxihEz4ana41x3bp1IjQ09KrvLlGpVGLmzJnC2tq6U5/A6unwpIuw1tM1MzMzhbW1tZAkSaxevVrrVE1dXZ1Yu3at/F1KiYmJoqKiQqxatUp4e3vLk8U7oqfDky7CWm8Z45VaT4c88MADYt68ecLQ0FDrO+R6c83+MEZdYJC5RXPnzr3qMtU1NTXiH//4h4iJiRGvvvqqqKmpEU8//bRYuHChWLp0aad2frqo2dP1dBGerjXG1157TURGRooNGzbIfdu4caMwMDDo1PyCng5PughrPV3z4sWLYv78+WLu3Lli06ZNQpIk8de//lUroKjVavHpp58KBwcH4ezsLPz9/YWTk1OnPp3U0+FJF2GtN42xPb/88ov88fyOvpY9XbM/jFFXGGRuUut/kRs3bhSjRo0SZ86c0bq/srJSLFy4UIwYMaLdrz3Xh5q6GGOrngpPNzPGRx99VIwcOVI+v//tt9926puPW/V0eOrpej1d89KlS2LTpk3yROKdO3e2G2aEaPkI+KFDh8S+ffs6NXm5p8OTLsJabxnjtXa6KpVKPP7448LMzKzDobuna/aHMeoSg8wtysrKEra2tmL+/Pnyjq5155ifny8kSRLff/+9/Piu+ERET9fsyXq6Ck83M8a2nzLpjJ4OT7oIa7oKiFd+99KOHTuEJEniL3/5i7zBbmpq6rLvT+rp8KSLsNabxtjeTvf48eMiMDCwU3PVerpmfxijLjHIdMCBAweEUqkUS5Ys0XpDlJSUiJCQEPHrr7/qfc2erqeLgNgbx9hV4UkX9XRVU4iWyYytdb744gt5g11UVCRWrFghpk+fLi5evNgl75ueDk89XU8XNa9Xr/VrHdRqtXxl6K74rp+ertkfxqgrDDId9O233wqlUimmT58uduzYIdLS0sSqVauEo6Oj1qWr9blmT9fTRUDs62PsD6G7VduPA+/YsUMYGhoKPz8/MWDAgG65BkZPhidd1NNFzRvVmzZtWpd/FLina/aHMfY0BplOSEhIELfffrtwd3cXQ4YMEb6+vp2eY9DbavZ0PV0ExL4+xv4QultpNBp5gz1u3DhhbW3drVcj7enw1NP1dFHzevW66++yp2v2hzH2JAaZTqqurhY5OTni1KlTnbpKZ2+u2dP1dBEQ+/oY+0PobtXc3CxfUr0nvhdGF+GpJ+vpoibH2Hdq9gQGGeqVdBEQe1pfD6S6qtnc3Cw++uijHr2kek+Hp56up4uaHGPfqdndBoCoFzI3N4e5ubmuu9GtenqMulinuqhpYGCA+fPnQ5KkHq0bGBiIxMREBAcH98l6uqjJMfadmt1JEkIIXXeCiEjfCSF6NDz1dD1d1OQY+07N7sQgQ0RERHpLoesOEBEREXUUgwwRERHpLQYZIiIi0lsMMkRERKS3GGSIiIhIbzHIEBERkd5ikCEiIiK9xSBDREREeotBhoiIiPTW/wP0s5b5VtbgUAAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from qbraid.visualization import plot_histogram\n", - "\n", - "plot_histogram(counts)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "de8bcb0f", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.11.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "23827387", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "\n", + "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '..')))" + ] + }, + { + "cell_type": "markdown", + "id": "338ad3ca", + "metadata": {}, + "source": [ + "## Quantum Fourier Transform\n", + "#### In this tutorial, we will demonstrate the capabilities of qBraid Algorithms QFT and Inverse QFT (IQFT) Modules\n", + "Begin by importing the modules from qBraid Algorithms library" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ccfc911a", + "metadata": {}, + "outputs": [], + "source": [ + "import pyqasm\n", + "from qbraid_algorithms import qft\n", + "from qbraid_algorithms import iqft" + ] + }, + { + "cell_type": "markdown", + "id": "87d3980f", + "metadata": {}, + "source": [ + "We can load both QFT and IQFT as PyQASM modules by calling the `generate_program` method, passing the number of qubits to apply the operations to." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ff7dc849", + "metadata": {}, + "outputs": [], + "source": [ + "num_qubits = 3\n", + "qft_module = qft.generate_program(num_qubits)\n", + "iqft_module = iqft.generate_program(num_qubits)" + ] + }, + { + "cell_type": "markdown", + "id": "b68eeb17", + "metadata": {}, + "source": [ + "We can perform all standard [PyQASM](https://docs.qbraid.com/pyqasm/user-guide/overview) operations on the module, such as unrolling." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e8c1a74d", + "metadata": {}, + "outputs": [], + "source": [ + "qft_module.unroll()\n", + "iqft_module.unroll()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4ff9ef62", + "metadata": {}, + "outputs": [], + "source": [ + "qft_str = pyqasm.dumps(qft_module)\n", + "iqft_str = pyqasm.dumps(iqft_module)" + ] + }, + { + "cell_type": "markdown", + "id": "d626c8d5", + "metadata": {}, + "source": [ + "Below, we display a preview of the the unrolled QFT circuit, followed by the IQFT circuit." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b2795398", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "qubit[3] q;\n", + "bit[3] b;\n", + "h q[0];\n", + "rz(0.7853981633974483) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "...\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "h q[2];\n", + "swap q[0], q[2];\n", + "b[0] = measure q[0];\n", + "b[1] = measure q[1];\n", + "b[2] = measure q[2];\n", + "\n" + ] + } + ], + "source": [ + "print(\"\\n\".join(qft_str.split(\"\\n\")[:10]) + \"\\n...\\n\" + \"\\n\".join(qft_str.split(\"\\n\")[-10:]))" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "385c7c14", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "qubit[3] q;\n", + "bit[3] b;\n", + "h q[2];\n", + "rz(-0.7853981633974483) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "...\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "h q[0];\n", + "swap q[0], q[2];\n", + "b[0] = measure q[0];\n", + "b[1] = measure q[1];\n", + "b[2] = measure q[2];\n", + "\n" + ] + } + ], + "source": [ + "print(\"\\n\".join(iqft_str.split(\"\\n\")[:10]) + \"\\n...\\n\" + \"\\n\".join(iqft_str.split(\"\\n\")[-10:]))" + ] + }, + { + "cell_type": "markdown", + "id": "35ada7c7", + "metadata": {}, + "source": [ + "## Using Quantum Fourier Transform in your own OpenQASM3 program\n", + "#### qBraid algorithms makes it easy to incorporate QFT and IQFT into your own OpenQASM3 circuit.\n", + "To use a QFT/IQFT in your circuit, first generate the subroutine using the `save_to_qasm` method, which takes the number of qubits to use. The method will create a QASM3 file containing the algorithm as a subroutine within your current working directory." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3da3ed9d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Subroutine 'qft' has been added to /Users/lukeandreesen/qbraid_algos/examples/qft.qasm\n", + "Subroutine 'iqft' has been added to /Users/lukeandreesen/qbraid_algos/examples/iqft.qasm\n" + ] + } + ], + "source": [ + "qft.save_to_qasm(3)\n", + "iqft.save_to_qasm(3)" + ] + }, + { + "cell_type": "markdown", + "id": "9fb21cfd", + "metadata": {}, + "source": [ + "To use the QFT subroutine in your own circuit, add `include \"qft.qasm\";` to your OpenQASM file, and call the `qft` method by passing an appropriately sized register of qubits." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "59fb5dec", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\r\n", + "include \"stdgates.inc\";\r\n", + "\r\n", + "\r\n", + "def qft(qubit[3] q) {\r\n", + " int n = 3;\r\n", + " for int[16] i in [0:n - 1] {\r\n", + " h q[i];\r\n", + " for int[16] j in [i + 1:n - 1] {\r\n", + " int[16] k = j - i;\r\n", + " cp(2 * pi / (1 << (k + 1))) q[j], q[i];\r\n", + " }\r\n", + " }\r\n", + "\r\n", + " for int[16] i in [0:(n >> 1) - 1] {\r\n", + " swap q[i], q[n - i - 1];\r\n", + " }\r\n", + "}" + ] + } + ], + "source": [ + "%cat qft.qasm" + ] + }, + { + "cell_type": "markdown", + "id": "ab150803", + "metadata": {}, + "source": [ + "To use the IQFT subroutine in your own circuit, add `include \"qft.qasm\";` to your OpenQASM file, and call the `qft` method by passing an appropriately sized register of qubits." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "66248d06", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\r\n", + "include \"stdgates.inc\";\r\n", + "\r\n", + "def iqft(qubit[3] q) {\r\n", + " int n = 3;\r\n", + " \r\n", + " for int[16] i in [0:n-1] {\r\n", + " int[16] target = n - i - 1;\r\n", + " for int[16] j in [0:(n - target - 2)] {\r\n", + " int[16] control = n - j - 1;\r\n", + " int[16] k = control - target;\r\n", + " cp(-2 * pi / (1 << (k + 1))) q[control], q[target];\r\n", + " }\r\n", + " h q[target];\r\n", + " }\r\n", + "\r\n", + " for int[16] i in [0:(n >> 1) - 1] {\r\n", + " swap q[i], q[n - i - 1];\r\n", + " }\r\n", + "}" + ] + } + ], + "source": [ + "%cat iqft.qasm" + ] + }, + { + "cell_type": "markdown", + "id": "d94efa88", + "metadata": {}, + "source": [ + "## Running Algorithms on qBraid\n", + "Running algorithms on qBraid is simple. First, import QbraidProvider from qBraid Runtime. Visit [here](https://docs.qbraid.com/sdk/user-guide/providers/native#qbraidprovider) for more information." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "f2b39f1a", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/homebrew/lib/python3.11/site-packages/pennylane/capture/capture_operators.py:33: RuntimeWarning: PennyLane is not yet compatible with JAX versions > 0.4.28. You have version 0.4.30 installed. Please downgrade JAX to <=0.4.28 to avoid runtime errors.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "from qbraid.runtime import QbraidProvider" + ] + }, + { + "cell_type": "markdown", + "id": "e12616de", + "metadata": {}, + "source": [ + "If you have not yet configured QbraidProvider, provide your API key." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "4bb1e7e5", + "metadata": {}, + "outputs": [], + "source": [ + "# provider = QbraidProvider(api_key='API_KEY')\n", + "provider = QbraidProvider()" + ] + }, + { + "cell_type": "markdown", + "id": "629d7b9e", + "metadata": {}, + "source": [ + "We'll run our program on qBraid's QIR simulator." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "80ee5d99", + "metadata": {}, + "outputs": [], + "source": [ + "device = provider.get_device('qbraid_qir_simulator')" + ] + }, + { + "cell_type": "markdown", + "id": "4dce4910", + "metadata": {}, + "source": [ + "To run the job, simply pass a QASM string to the device. If you have a PyQASM module, use `dumps` to generate a QASM string" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "293bfb68", + "metadata": {}, + "outputs": [], + "source": [ + "module = qft.generate_program(4)\n", + "qasm_str = pyqasm.dumps(module)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "8a26081c", + "metadata": {}, + "outputs": [], + "source": [ + "job = device.run(qasm_str, shots=500)" + ] + }, + { + "cell_type": "markdown", + "id": "d3c7c3a9", + "metadata": {}, + "source": [ + "We can now get the counts from the job results." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "7e1a7a8a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'0000': 21, '0001': 39, '0010': 27, '0011': 35, '0100': 4, '0101': 4, '0110': 34, '0111': 16, '1000': 25, '1001': 18, '1010': 14, '1011': 8, '1100': 54, '1101': 58, '1110': 69, '1111': 74}\n" + ] + } + ], + "source": [ + "results = job.result()\n", + "counts = results.data.get_counts()\n", + "print(counts)" + ] + }, + { + "cell_type": "markdown", + "id": "65f39cab", + "metadata": {}, + "source": [ + "Finally, we can plot the results using qBraid Visualization." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "b201c84b", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from qbraid.visualization import plot_histogram\n", + "\n", + "plot_histogram(counts)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "de8bcb0f", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/qbraid_algorithms/bells_inequality/__init__.py b/qbraid_algorithms/bells_inequality/__init__.py index d1f04b0..9f0ec98 100644 --- a/qbraid_algorithms/bells_inequality/__init__.py +++ b/qbraid_algorithms/bells_inequality/__init__.py @@ -56,12 +56,12 @@ .. autosummary:: :toctree: ../stubs/ - load_program + generate_program """ -from .bells_inequality import load_program +from .bells_inequality import generate_program __all__ = [ - "load_program", + "generate_program", ] diff --git a/qbraid_algorithms/bells_inequality/bells_inequality.py b/qbraid_algorithms/bells_inequality/bells_inequality.py index 83340db..b48ab18 100644 --- a/qbraid_algorithms/bells_inequality/bells_inequality.py +++ b/qbraid_algorithms/bells_inequality/bells_inequality.py @@ -23,12 +23,12 @@ from pyqasm.modules.base import QasmModule -def load_program() -> QasmModule: +def generate_program() -> QasmModule: """ Load the Bell's inequality circuit as a pyqasm module. Returns: pyqasm module containing the Bell's inequality circuit """ - qasm_path = Path(__file__).parent / "bells_inequality.qasm" + qasm_path = Path(__file__).parent.parent / "qasm_resources/bells_inequality.qasm" return pyqasm.load(str(qasm_path)) diff --git a/qbraid_algorithms/bernstein_vazirani/__init__.py b/qbraid_algorithms/bernstein_vazirani/__init__.py index c770418..ef840f2 100644 --- a/qbraid_algorithms/bernstein_vazirani/__init__.py +++ b/qbraid_algorithms/bernstein_vazirani/__init__.py @@ -55,11 +55,11 @@ .. autosummary:: :toctree: ../stubs/ - load_program - generate_subroutine + generate_program + save_to_qasm generate_oracle """ -from .bernvaz import generate_oracle, generate_subroutine, load_program +from .bernvaz import generate_oracle, generate_program, save_to_qasm -__all__ = ["load_program", "generate_subroutine", "generate_oracle"] +__all__ = ["generate_program", "save_to_qasm", "generate_oracle"] diff --git a/qbraid_algorithms/bernstein_vazirani/bernvaz.py b/qbraid_algorithms/bernstein_vazirani/bernvaz.py index 4509419..45bc73a 100644 --- a/qbraid_algorithms/bernstein_vazirani/bernvaz.py +++ b/qbraid_algorithms/bernstein_vazirani/bernvaz.py @@ -28,7 +28,7 @@ from qbraid_algorithms.utils import _prep_qasm_file -def load_program(bitstring: Union[str, list[int]]) -> QasmModule: +def generate_program(bitstring: Union[str, list[int]]) -> QasmModule: """ Load the Bernstein-Vazirani circuit as a pyqasm module. @@ -42,9 +42,11 @@ def load_program(bitstring: Union[str, list[int]]) -> QasmModule: # Load the Bernstein-Vazirani QASM files into a staging directory temp_dir = tempfile.mkdtemp() - bernvaz_src = Path(__file__).parent / "bernvaz.qasm" + bernvaz_src = Path(__file__).parent.parent / "qasm_resources/bernvaz.qasm" bernvaz_dst = os.path.join(temp_dir, "bernvaz.qasm") - bernvaz_sub_src = Path(__file__).parent / "bernvaz_subroutine.qasm" + bernvaz_sub_src = ( + Path(__file__).parent.parent / "qasm_resources/bernvaz_subroutine.qasm" + ) bernvaz_sub_dst = os.path.join(temp_dir, "bernvaz_subroutine.qasm") shutil.copy(bernvaz_src, bernvaz_dst) shutil.copy(bernvaz_sub_src, bernvaz_sub_dst) @@ -63,7 +65,7 @@ def load_program(bitstring: Union[str, list[int]]) -> QasmModule: return module -def generate_subroutine( +def save_to_qasm( bitstring: Union[str, list[int]], quiet: bool = False, path: Optional[str] = None ) -> None: """ @@ -79,7 +81,9 @@ def generate_subroutine( None """ # Copy the B-V subroutine QASM file to the specified or current working directory - bernvaz_src = Path(__file__).parent / "bernvaz_subroutine.qasm" + bernvaz_src = ( + Path(__file__).parent.parent / "qasm_resources/bernvaz_subroutine.qasm" + ) if path is None: bernvaz_dst = os.path.join(os.getcwd(), "bernvaz.qasm") else: @@ -111,7 +115,7 @@ def generate_oracle( None """ # Copy the oracle QASM file to the specified or current working directory - oracle_src = Path(__file__).parent / "oracle.qasm" + oracle_src = Path(__file__).parent.parent / "qasm_resources/oracle.qasm" if path is None: oracle_dst = os.path.join(os.getcwd(), "oracle.qasm") else: diff --git a/qbraid_algorithms/cli/generate.py b/qbraid_algorithms/cli/generate.py index 2593625..d5aeb21 100644 --- a/qbraid_algorithms/cli/generate.py +++ b/qbraid_algorithms/cli/generate.py @@ -80,7 +80,7 @@ def generate_qft( target_file = os.path.join(target_dir, output) # Generate the subroutine using the path parameter - qft.generate_subroutine(qubits, quiet=True, path=target_dir) + qft.save_to_qasm(qubits, quiet=True, path=target_dir) # Rename to custom output if needed generated_file = os.path.join(target_dir, "qft.qasm") @@ -153,7 +153,7 @@ def generate_iqft( target_file = os.path.join(target_dir, output) # Generate the subroutine using the path parameter - iqft.generate_subroutine(qubits, quiet=True, path=target_dir) + iqft.save_to_qasm(qubits, quiet=True, path=target_dir) # Rename to custom output if needed generated_file = os.path.join(target_dir, "iqft.qasm") @@ -267,7 +267,7 @@ def generate_bernvaz( typer.echo("Bernstein-Vazirani oracle generated successfully.") else: # Generate complete circuit - bv.generate_subroutine(secret, quiet=True, path=target_dir) + bv.save_to_qasm(secret, quiet=True, path=target_dir) generated_file = os.path.join(target_dir, "bernvaz.qasm") typer.echo("Bernstein-Vazirani circuit generated successfully.") @@ -365,7 +365,7 @@ def generate_qpe( target_file = os.path.join(target_dir, output) # Generate the subroutine using the path parameter - qpe.generate_subroutine(unitary_file, qubits, quiet=True, path=target_dir) + qpe.save_to_qasm(unitary_file, qubits, quiet=True, path=target_dir) # Rename to custom output if needed generated_file = os.path.join(target_dir, "qpe.qasm") diff --git a/qbraid_algorithms/embedding/prep_sel.py b/qbraid_algorithms/embedding/prep_sel.py index 3284b71..a003491 100644 --- a/qbraid_algorithms/embedding/prep_sel.py +++ b/qbraid_algorithms/embedding/prep_sel.py @@ -20,7 +20,7 @@ """ # ruff: noqa: E731 # pylint: disable=unnecessary-lambda-assignment -#lambda error suppressed as a single parameter automated generation of a 2d numpy matrix is too obtuse a function call +# lambda error suppressed as a single parameter automated generation of a 2d numpy matrix is too obtuse a function call # TODO: fix too many locals, unused variables too but thats more of a loop control varaible problem # pylint: disable=too-many-locals,unused-variable # mypy: disable_error_code="call-arg,import-untyped" @@ -53,7 +53,7 @@ def prep_select(self, qubits, matrix, approximate=0): """ # print(matrix) # Handle both matrix and pre-computed operator chain inputs - if isinstance(matrix[0],tuple) : + if isinstance(matrix[0], tuple): op_chain = matrix gate_id = abs(hash(tuple(matrix))) # BUG FIX: Use tuple for abs(hashable else: @@ -61,7 +61,7 @@ def prep_select(self, qubits, matrix, approximate=0): gate_id = abs(hash(tuple(op_chain))) # BUG FIX: Use tuple for abs(hashable # Calculate required ancilla qubits - qb = max(int(np.ceil(np.log2(len(op_chain)))),1) + qb = max(int(np.ceil(np.log2(len(op_chain)))), 1) name = f"PS_{len(qubits)}_{gate_id}" print(op_chain) # Claim quantum resources @@ -84,13 +84,15 @@ def prep_select(self, qubits, matrix, approximate=0): # Generate unique qubit argument names names = string.ascii_letters - qargs = [names[i // len(names)] + names[i % len(names)] - for i in range(len(qubits) + qb)] + qargs = [ + names[i // len(names)] + names[i % len(names)] + for i in range(len(qubits) + qb) + ] - std.begin_gate(name,qargs) - nprep, mapping = prep.prep(qargs[:qb],[a[1] for a in op_chain]) - nsel = sel.select(qargs[qb:],qargs[:qb],[a[0] for a in op_chain],mapping) - prep.inverse_op(prep.prep,[qargs[:qb],[a[1] for a in op_chain]]) + std.begin_gate(name, qargs) + nprep, mapping = prep.prep(qargs[:qb], [a[1] for a in op_chain]) + nsel = sel.select(qargs[qb:], qargs[:qb], [a[0] for a in op_chain], mapping) + prep.inverse_op(prep.prep, [qargs[:qb], [a[1] for a in op_chain]]) std.end_gate() # Register and execute gate @@ -168,22 +170,21 @@ def prep(self, qubits, dist): # print("qubits",qubits) name = f"PREP_{abs(hash(tuple(dist)))}" # BUG FIX: Use tuple for abs(hashing if name in self.gate_ref: - self.call_gate(name, qubits[-1],qubits[:-1]) # BUG FIX: Simplified call + self.call_gate(name, qubits[-1], qubits[:-1]) # BUG FIX: Simplified call return name, {} # Build preparation circuit sys = GateBuilder() std = sys.import_library(std_gates) std.call_space = "{}" - qb = max(int(np.ceil(np.log2(len(dist)))),1) + qb = max(int(np.ceil(np.log2(len(dist)))), 1) # Generate parameter angles and mapping angles, mapping = self.gen_prep_angles(dist) # Create qubit argument names names = string.ascii_letters - qargs = [names[i // len(names)] + names[i % len(names)] - for i in range(qb)] + qargs = [names[i // len(names)] + names[i % len(names)] for i in range(qb)] std.begin_gate(name, qargs) @@ -200,19 +201,19 @@ def prep(self, qubits, dist): # Odd-indexed controls for j in range(1, qb, 2): if angle_idx < len(angles): # BUG FIX: Bounds checking - std.cry(angles[angle_idx], qargs[j-1], qargs[j]) + std.cry(angles[angle_idx], qargs[j - 1], qargs[j]) angle_idx += 1 # Even-indexed controls for j in range(2, qb, 2): if angle_idx < len(angles): # BUG FIX: Bounds checking - std.cry(angles[angle_idx], qargs[j-1], qargs[j]) + std.cry(angles[angle_idx], qargs[j - 1], qargs[j]) angle_idx += 1 std.end_gate() self.merge(*sys.build(), name) - self.call_gate(name, qubits[-1],qubits[:-1]) # BUG FIX: Simplified call + self.call_gate(name, qubits[-1], qubits[:-1]) # BUG FIX: Simplified call return name, mapping def gen_prep_angles(self, dist): @@ -226,17 +227,20 @@ def gen_prep_angles(self, dist): Optimized angles and index mapping """ # Gate definitions - y_rot = lambda t: np.array([[np.cos(t/2), -np.sin(t/2)], - [np.sin(t/2), np.cos(t/2)]]) - cy_rot = lambda t: np.block([[np.eye(2), np.zeros((2,2))], - [np.zeros((2,2)), y_rot(t)]]) - - qb = max(int(np.ceil(np.log2(len(dist)))),1) + y_rot = lambda t: np.array( + [[np.cos(t / 2), -np.sin(t / 2)], [np.sin(t / 2), np.cos(t / 2)]] + ) + cy_rot = lambda t: np.block( + [[np.eye(2), np.zeros((2, 2))], [np.zeros((2, 2)), y_rot(t)]] + ) + + qb = max(int(np.ceil(np.log2(len(dist)))), 1) # print(qb,np.ceil(np.log2(len(dist))),dist) # Normalize and pad distribution padded_size = 2**qb - ref_dist = np.pad(dist, (0, padded_size - len(dist)), - mode="constant", constant_values=0) + ref_dist = np.pad( + dist, (0, padded_size - len(dist)), mode="constant", constant_values=0 + ) ref_dist = ref_dist / np.linalg.norm(ref_dist) sorted_dist = np.sort(ref_dist) sort_indices = np.argsort(ref_dist) @@ -256,10 +260,14 @@ def render_state(params): # Apply controlled rotations for layer in range(2): # Build controlled gates - dy = cy_rot(params[param_idx]) if param_idx < len(params) else np.eye(4) + dy = ( + cy_rot(params[param_idx]) + if param_idx < len(params) + else np.eye(4) + ) param_idx += 1 - for _ in range(1, qb//2): + for _ in range(1, qb // 2): if param_idx < len(params): dy = np.kron(cy_rot(params[param_idx]), dy) param_idx += 1 @@ -269,7 +277,7 @@ def render_state(params): # Upper controlled gates uy = np.eye(2) - for _ in range((qb-1)//2): + for _ in range((qb - 1) // 2): if param_idx < len(params): uy = np.kron(cy_rot(params[param_idx]), uy) param_idx += 1 @@ -288,11 +296,10 @@ def cost_function(params): return 1 - np.abs(np.inner(sorted_dist, sorted_sim)) # Optimize parameters - num_params = qb + 2*2*(qb//2) # BUG FIX: More accurate parameter count - result = minimize(cost_function, x0=np.ones((num_params))*.1) + num_params = qb + 2 * 2 * (qb // 2) # BUG FIX: More accurate parameter count + result = minimize(cost_function, x0=np.ones((num_params)) * 0.1) # print(result) - # Create mapping from original to sorted indices final_state = render_state(result.x) mapping = dict(zip(sort_indices, np.argsort(final_state))) @@ -322,13 +329,17 @@ def select(self, qubits, anc, operators, mapping): name = f"SEL_{gate_id}" if name in self.gate_ref: - self.call_gate(name, qubits[-1],anc + qubits[:-1]) # BUG FIX: Proper argument order + self.call_gate( + name, qubits[-1], anc + qubits[:-1] + ) # BUG FIX: Proper argument order return name # Generate argument names names = string.ascii_letters - qargs = [names[i // len(names)] + names[i % len(names)] - for i in range(len(qubits) + len(anc))] + qargs = [ + names[i // len(names)] + names[i % len(names)] + for i in range(len(qubits) + len(anc)) + ] sys = GateBuilder() std = sys.import_library(std_gates) @@ -360,21 +371,24 @@ def select(self, qubits, anc, operators, mapping): if isinstance(op, str): # Pauli string operator - pauli_lib.controlled_op(pauli_lib.pauli_operator, - [qargs, op], n=len(anc)) + pauli_lib.controlled_op( + pauli_lib.pauli_operator, [qargs, op], n=len(anc) + ) else: # Custom gate library operator op_lib = sys.import_library(op) - op_lib.controlled(qargs[len(anc):], qargs[:len(anc)]) + op_lib.controlled(qargs[len(anc) :], qargs[: len(anc)]) prev_gray = gray_code for j in range(len(anc)): - if (prev_gray>>j)%2: + if (prev_gray >> j) % 2: std.x(qargs[j]) std.end_gate() self.merge(*sys.build(), name) - self.call_gate(name, qubits[-1],anc + qubits[:-1]) # BUG FIX: Proper argument order + self.call_gate( + name, qubits[-1], anc + qubits[:-1] + ) # BUG FIX: Proper argument order return name @@ -397,24 +411,27 @@ def pauli_operator(self, qubits, op): return None # Validate Pauli string - valid_symbols = {'I', 'X', 'Y', 'Z'} + valid_symbols = {"I", "X", "Y", "Z"} if not all(ch in valid_symbols for ch in op): print(f"Invalid Pauli string: {op}") return None if op in self.gate_ref: - self.call_gate(op, qubits[-1],qubits[:-1]) + self.call_gate(op, qubits[-1], qubits[:-1]) return op # BUG FIX: Correct qubit count if len(op) > len(qubits): - print(f"Pauli string length {len(op)} doesn't match qubit count {len(qubits)}") + print( + f"Pauli string length {len(op)} doesn't match qubit count {len(qubits)}" + ) return None # Create new Pauli operator gate names = string.ascii_letters - qargs = [names[i // len(names)] + names[i % len(names)] - for i in range(len(op))] # BUG FIX: Use len(op) + qargs = [ + names[i // len(names)] + names[i % len(names)] for i in range(len(op)) + ] # BUG FIX: Use len(op) sys = GateBuilder() std = sys.import_library(std_gates) @@ -424,13 +441,13 @@ def pauli_operator(self, qubits, op): # Apply Pauli gates for i, gate in enumerate(op): match gate: - case 'I': + case "I": pass # Identity - no operation - case 'X': + case "X": std.x(qargs[i]) # BUG FIX: Use qargs instead of index - case 'Y': + case "Y": std.y(qargs[i]) - case 'Z': + case "Z": std.z(qargs[i]) case _: print(f"Unknown Pauli gate: {gate}") @@ -438,5 +455,5 @@ def pauli_operator(self, qubits, op): std.end_gate() self.merge(*sys.build(), op) - self.call_gate(op, qubits[-1],qubits[:-1]) + self.call_gate(op, qubits[-1], qubits[:-1]) return op diff --git a/qbraid_algorithms/embedding/toeplitz.py b/qbraid_algorithms/embedding/toeplitz.py index 55617df..b30f288 100644 --- a/qbraid_algorithms/embedding/toeplitz.py +++ b/qbraid_algorithms/embedding/toeplitz.py @@ -15,8 +15,8 @@ """ Toeplitz and Diagonal Gate Libraries for Quantum Algorithms. -NOTE (WIP, untested): This implementation requires claiming ancilla qubits/clbits -to operate. Ancilla claiming embeddings are a future completion task once +NOTE (WIP, untested): This implementation requires claiming ancilla qubits/clbits +to operate. Ancilla claiming embeddings are a future completion task once QASM subroutines have been fully debugged in PyQASM. This module provides: @@ -84,7 +84,7 @@ def real_toeplitz(self, qubits, vals, ancilla=True): else: line = np.concatenate((vals, [0], np.flip(vals))) circ_mat = scp.linalg.circulant(line[:-1]) - circ_mat = circ_mat[:len(vals), :len(vals)] + circ_mat = circ_mat[: len(vals), : len(vals)] # Diagonalize via FFT dft = np.fft.fft(np.eye(2 * len(vals))) @@ -107,7 +107,9 @@ def real_toeplitz(self, qubits, vals, ancilla=True): std.begin_gate(name, qargs) qft.inverse_op(qft.QFT, (qargs[1:],)) - diagonal.controlled_op(diagonal.diag_scale, (qargs[1:], diag_vals, ([qargs[0]], 0))) + diagonal.controlled_op( + diagonal.diag_scale, (qargs[1:], diag_vals, ([qargs[0]], 0)) + ) qft.QFT(qargs[1:]) std.end_gate() @@ -201,7 +203,7 @@ def diag(self, qubits, vals, depth=3): Returns: str: Gate name. """ - print("building diagonal gate:",qubits, vals, depth) + print("building diagonal gate:", qubits, vals, depth) qb = int(np.log2(len(vals)) + 0.01) name = f"diag{qb}_{np.abs(hash(tuple(vals)))}" @@ -211,10 +213,7 @@ def diag(self, qubits, vals, depth=3): # Argument names names = string.ascii_letters - qargs = [ - names[i // len(names)] + names[i % len(names)] - for i in range(qb) - ] + qargs = [names[i // len(names)] + names[i % len(names)] for i in range(qb)] # Build subcircuit sys = GateBuilder() @@ -223,7 +222,7 @@ def diag(self, qubits, vals, depth=3): std.begin_gate(name, qargs) std.x(qargs[0]) - std.call_gate("p",qargs[0],phases=projection[0] ) + std.call_gate("p", qargs[0], phases=projection[0]) std.x(qargs[0]) # Apply projections @@ -234,11 +233,11 @@ def diag(self, qubits, vals, depth=3): pindex += 1 continue if len(c) == 1: - std.call_gate("p",qargs[c[0]],phases=projection[pindex] ) + std.call_gate("p", qargs[c[0]], phases=projection[pindex]) else: std.controlled_op( "p", - ( qargs[c[0]], [qargs[n] for n in c[1:]], projection[pindex]), + (qargs[c[0]], [qargs[n] for n in c[1:]], projection[pindex]), n=len(c) - 1, ) pindex += 1 @@ -248,7 +247,7 @@ def diag(self, qubits, vals, depth=3): self.call_gate(name, qubits[-1], qubits[:-1]) return name - def phase_projector(self,target, depth): + def phase_projector(self, target, depth): """ Construct a phase projector decomposition. @@ -267,7 +266,7 @@ def phase_projector(self,target, depth): for c in [list(combo) for combo in combinations(range(qb), i + 1)]: r = np.ones(2**qb) for e in c: - r *= ((basis / (2**e)).astype(int) % 2) + r *= (basis / (2**e)).astype(int) % 2 if i == 0 and c == [0]: space.append(np.logical_xor(r, np.ones(2**qb))) diff --git a/qbraid_algorithms/evolution/gqsp.py b/qbraid_algorithms/evolution/gqsp.py index 99a9eee..a08b7a8 100644 --- a/qbraid_algorithms/evolution/gqsp.py +++ b/qbraid_algorithms/evolution/gqsp.py @@ -49,7 +49,7 @@ class GQSP(GateLibrary): """ # Class-level symbolic matrix for GQSP operations - U = sp.Matrix([[sp.Symbol("id"), 0], [0, sp.Symbol('H')]]) + U = sp.Matrix([[sp.Symbol("id"), 0], [0, sp.Symbol("H")]]) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -67,7 +67,7 @@ def GQSP(self, qubits, phases, hamiltonian, depth=3): Returns: Gate name """ - name = f'GQSP_{depth}_{hamiltonian.name}' + name = f"GQSP_{depth}_{hamiltonian.name}" # Claim ancilla resources anc_q = self.builder.claim_qubits(1) @@ -75,7 +75,7 @@ def GQSP(self, qubits, phases, hamiltonian, depth=3): # Use existing gate if available if name in self.gate_ref: - self.call_gate(name,qubits[-1],anc_q+qubits[:-1],phases=phases) + self.call_gate(name, qubits[-1], anc_q + qubits[:-1], phases=phases) self.measure(anc_q, anc_c) return name # BUG FIX: Add return statement @@ -86,8 +86,10 @@ def GQSP(self, qubits, phases, hamiltonian, depth=3): # Generate unique qubit and parameter names names = string.ascii_letters - qargs = [names[i // len(names)] + names[i % len(names)] - for i in range(len(qubits) + 1)] + qargs = [ + names[i // len(names)] + names[i % len(names)] + for i in range(len(qubits) + 1) + ] angles = [f"θ{names[i]}" for i in range(depth * 2 + 1)] std.begin_gate(name, qargs, params=angles) @@ -110,7 +112,7 @@ def GQSP(self, qubits, phases, hamiltonian, depth=3): # Register and apply gate self.merge(*sys.build(), name) - self.call_gate(name,qubits[-1],anc_q+qubits[:-1],phases=phases) + self.call_gate(name, qubits[-1], anc_q + qubits[:-1], phases=phases) self.measure(anc_q, anc_c) return name @@ -127,16 +129,17 @@ def GQSP_recurse(mat, depth): Symbolic matrix expression for GQSP circuit """ # Y-rotation matrix - r = sp.Symbol(f'r{depth}') - qr = sp.Matrix([[sp.cos(r/2), -sp.sin(r/2)], - [sp.sin(r/2), sp.cos(r/2)]]) + r = sp.Symbol(f"r{depth}") + qr = sp.Matrix( + [[sp.cos(r / 2), -sp.sin(r / 2)], [sp.sin(r / 2), sp.cos(r / 2)]] + ) # Base case: just apply rotation if depth <= 0: return qr * mat # Phase rotation matrix - p = sp.Symbol(f'p{depth}') + p = sp.Symbol(f"p{depth}") rp = sp.Matrix([[1, 0], [0, sp.exp(1j * p)]]) # Recursive GQSP construction @@ -162,8 +165,9 @@ def gen_cost(depth, t=1): time = np.linspace(-1, 1, 50) # Target polynomial coefficients (Taylor series approximation) - poly = np.flip(np.power(1j, range(depth + 1)) / - scp.special.factorial(range(depth + 1))) # BUG FIX: Use scp + poly = np.flip( + np.power(1j, range(depth + 1)) / scp.special.factorial(range(depth + 1)) + ) # BUG FIX: Use scp # Extract and sort symbolic variables syms = expr.free_symbols @@ -195,7 +199,9 @@ def cost(x): resolved = expr.subs(param_dict) # Create numerical evaluator - evaluator = sp.lambdify(srefs[0], resolved, "numpy") # srefs[0] should be 'H' + evaluator = sp.lambdify( + srefs[0], resolved, "numpy" + ) # srefs[0] should be 'H' try: series = evaluator(time) @@ -205,7 +211,7 @@ def cost(x): series = series / np.abs(series[0]) # Compute mean squared error - diff = np.sum(np.abs(series - ref)**2) + diff = np.sum(np.abs(series - ref) ** 2) return float(diff) # BUG FIX: Ensure scalar return except (ValueError, TypeError, ZeroDivisionError): @@ -215,7 +221,7 @@ def cost(x): return cost, names @staticmethod - def find_gqsp_spectrum( depth): + def find_gqsp_spectrum(depth): """ Find optimal GQSP parameters across a spectrum of time values. @@ -245,9 +251,12 @@ def find_gqsp_spectrum( depth): cost_func = GQSP.gen_cost(depth, t)[0] # Optimize parameters - result = minimize(cost_func, x0=x_prev, - method='BFGS', # BUG FIX: Specify optimization method - options={'maxiter': 1000}) + result = minimize( + cost_func, + x0=x_prev, + method="BFGS", # BUG FIX: Specify optimization method + options={"maxiter": 1000}, + ) if result.success: fits.append(result.x) diff --git a/qbraid_algorithms/evolution/h_test_suite.py b/qbraid_algorithms/evolution/h_test_suite.py index 867a01d..9df1f8c 100644 --- a/qbraid_algorithms/evolution/h_test_suite.py +++ b/qbraid_algorithms/evolution/h_test_suite.py @@ -55,6 +55,7 @@ class TransverseFieldIsing(GateLibrary): formulation is not a direct matrix embedding but is intended for use in series product formulation under small time steps """ + name = "TFIM" def __init__(self, reg=3, j=1.0, h=0.5, *args, **kwargs): @@ -66,8 +67,9 @@ def __init__(self, reg=3, j=1.0, h=0.5, *args, **kwargs): # Generate unique qubit argument names names = string.ascii_letters - qargs = [names[i // len(names)] + names[i % len(names)] - for i in range(self.reg_size)] + qargs = [ + names[i // len(names)] + names[i % len(names)] for i in range(self.reg_size) + ] sys = GateBuilder() std = sys.import_library(std_gates) @@ -96,16 +98,15 @@ def __init__(self, reg=3, j=1.0, h=0.5, *args, **kwargs): # self.call_space = " {}" # Register the gate - self.merge(*sys.build(),self.name) + self.merge(*sys.build(), self.name) def apply(self, time, qubits): """Apply TFIM evolution for given time.""" - self.call_gate(self.name, qubits[-1],qubits[:-1], phases=[time]) + self.call_gate(self.name, qubits[-1], qubits[:-1], phases=[time]) def controlled(self, time, qubits, control): """Apply controlled TFIM evolution.""" - self.controlled_op(self.name, (qubits[-1],[control]+qubits[:-1], time), n=1) - + self.controlled_op(self.name, (qubits[-1], [control] + qubits[:-1], time), n=1) class HeisenbergXYZ(GateLibrary): @@ -115,6 +116,7 @@ class HeisenbergXYZ(GateLibrary): Implements all three Pauli interactions between neighboring qubits. Highly non-commuting due to different Pauli matrices on same qubits. """ + name = "HeisenbergXYZ" def __init__(self, reg=3, j_x=1.0, j_y=1.0, j_z=1.0, *args, **kwargs): @@ -124,8 +126,9 @@ def __init__(self, reg=3, j_x=1.0, j_y=1.0, j_z=1.0, *args, **kwargs): self.name = f"HeisenbergXYZ_{self.reg_size}q_j_x{int(100*j_x)}_j_y{int(100*j_y)}_j_z{int(100*j_z)}" names = string.ascii_letters - qargs = [names[i // len(names)] + names[i % len(names)] - for i in range(self.reg_size)] + qargs = [ + names[i // len(names)] + names[i % len(names)] for i in range(self.reg_size) + ] sys = GateBuilder() std = sys.import_library(std_gates) @@ -135,21 +138,21 @@ def __init__(self, reg=3, j_x=1.0, j_y=1.0, j_z=1.0, *args, **kwargs): for i in range(self.reg_size - 1): # XX interaction: exp(-i * j_x * XX * time) - std.ry("pi/2", qargs[i]) # X basis rotation + std.ry("pi/2", qargs[i]) # X basis rotation std.ry("pi/2", qargs[i + 1]) std.cnot(qargs[i], qargs[i + 1]) std.rz(f"{2 * j_x} * time", qargs[i + 1]) std.cnot(qargs[i], qargs[i + 1]) - std.ry("-pi/2", qargs[i]) # Inverse rotation + std.ry("-pi/2", qargs[i]) # Inverse rotation std.ry("-pi/2", qargs[i + 1]) # YY interaction: exp(-i * j_y * YY * time) - std.rx("-pi/2", qargs[i]) # Y basis rotation + std.rx("-pi/2", qargs[i]) # Y basis rotation std.rx("-pi/2", qargs[i + 1]) std.cnot(qargs[i], qargs[i + 1]) std.rz(f"{2 * j_y} * time", qargs[i + 1]) std.cnot(qargs[i], qargs[i + 1]) - std.rx("pi/2", qargs[i]) # Inverse rotation + std.rx("pi/2", qargs[i]) # Inverse rotation std.rx("pi/2", qargs[i + 1]) # ZZ interaction: exp(-i * j_z * ZZ * time) @@ -159,16 +162,15 @@ def __init__(self, reg=3, j_x=1.0, j_y=1.0, j_z=1.0, *args, **kwargs): std.end_gate() # self.call_space = " {}" - self.merge(*sys.build(),self.name) + self.merge(*sys.build(), self.name) def apply(self, time, qubits): """Apply Heisenberg XYZ evolution for given time.""" - self.call_gate(self.name, qubits[-1],qubits[:-1], phases=[time]) + self.call_gate(self.name, qubits[-1], qubits[:-1], phases=[time]) def controlled(self, time, qubits, control): """Apply controlled Heisenberg evolution.""" - self.controlled_op(self.name, (qubits[-1],[control]+qubits[:-1], time), n=1) - + self.controlled_op(self.name, (qubits[-1], [control] + qubits[:-1], time), n=1) class RandomizedHamiltonian(GateLibrary): @@ -178,6 +180,7 @@ class RandomizedHamiltonian(GateLibrary): Applies random combinations of single and two-qubit rotations with controlled dependencies. Designed to test algorithm robustness. """ + name = "RandomHam" def __init__(self, reg=3, seed=42, density=0.7, *args, **kwargs): @@ -191,8 +194,9 @@ def __init__(self, reg=3, seed=42, density=0.7, *args, **kwargs): random.seed(seed) names = string.ascii_letters - qargs = [names[i // len(names)] + names[i % len(names)] - for i in range(self.reg_size)] + qargs = [ + names[i // len(names)] + names[i % len(names)] for i in range(self.reg_size) + ] sys = GateBuilder() std = sys.import_library(std_gates) @@ -201,7 +205,7 @@ def __init__(self, reg=3, seed=42, density=0.7, *args, **kwargs): std.begin_gate(self.name, qargs, params=["time"]) # Random single-qubit rotations - pauli_gates = ['rx', 'ry', 'rz'] + pauli_gates = ["rx", "ry", "rz"] for i in range(self.reg_size): if random.random() < density: gate_type = random.choice(pauli_gates) @@ -213,7 +217,7 @@ def __init__(self, reg=3, seed=42, density=0.7, *args, **kwargs): for j in range(i + 1, self.reg_size): if random.random() < density * 0.5: # Lower density for 2-qubit # Random ZZ-type interaction with basis rotation - basis_rot = random.choice(['rx', 'ry', 'rz']) + basis_rot = random.choice(["rx", "ry", "rz"]) angle = random.uniform(0.1, 1.5) # Apply random basis rotations @@ -232,21 +236,23 @@ def __init__(self, reg=3, seed=42, density=0.7, *args, **kwargs): # Add some controlled single-qubit operations for extra complexity for i in range(self.reg_size - 1): if random.random() < density * 0.3: - ctrl_gate = random.choice(['cry', 'crx', 'crz']) + ctrl_gate = random.choice(["cry", "crx", "crz"]) angle = random.uniform(0.1, 1.0) - std.call_gate(ctrl_gate, qargs[i], qargs[i + 1], phases=[f"{angle} * time"]) + std.call_gate( + ctrl_gate, qargs[i], qargs[i + 1], phases=[f"{angle} * time"] + ) std.end_gate() # self.call_space = " {}" - self.merge(*sys.build(),self.name) + self.merge(*sys.build(), self.name) def apply(self, time, qubits): """Apply Heisenberg XYZ evolution for given time.""" - self.call_gate(self.name, qubits[-1],qubits[:-1], phases=[time]) + self.call_gate(self.name, qubits[-1], qubits[:-1], phases=[time]) def controlled(self, time, qubits, control): """Apply controlled Heisenberg evolution.""" - self.controlled_op(self.name, (qubits[-1],[control]+qubits[:-1], time), n=1) + self.controlled_op(self.name, (qubits[-1], [control] + qubits[:-1], time), n=1) class FermionicHubbard(GateLibrary): @@ -257,6 +263,7 @@ class FermionicHubbard(GateLibrary): transformation. Creates complex non-local interactions through string of Pauli operations. """ + name = "FermionicHubbard" def __init__(self, reg=3, t=1.0, U=2.0, *args, **kwargs): @@ -267,8 +274,9 @@ def __init__(self, reg=3, t=1.0, U=2.0, *args, **kwargs): self.name = f"FermionicHubbard_{self.reg_size}q_t{int(100*t)}_U{int(100*U)}" names = string.ascii_letters - qargs = [names[i // len(names)] + names[i % len(names)] - for i in range(self.reg_size)] + qargs = [ + names[i // len(names)] + names[i % len(names)] for i in range(self.reg_size) + ] sys = GateBuilder() std = sys.import_library(std_gates) @@ -334,15 +342,15 @@ def __init__(self, reg=3, t=1.0, U=2.0, *args, **kwargs): std.end_gate() # self.call_space = " {}" - self.merge(*sys.build(),self.name) + self.merge(*sys.build(), self.name) def apply(self, time, qubits): """Apply Heisenberg XYZ evolution for given time.""" - self.call_gate(self.name, qubits[-1],qubits[:-1], phases=[time]) + self.call_gate(self.name, qubits[-1], qubits[:-1], phases=[time]) def controlled(self, time, qubits, control): """Apply controlled Heisenberg evolution.""" - self.controlled_op(self.name, (qubits[-1],[control]+qubits[:-1], time), n=1) + self.controlled_op(self.name, (qubits[-1], [control] + qubits[:-1], time), n=1) # Test suite factory function @@ -356,6 +364,7 @@ def create_test_hamiltonians(reg_size=4): Returns: Dictionary of Hamiltonian instances for testing """ + # test_reg = list(range(reg_size)) def anonymize(lib, aparams): """ @@ -369,18 +378,27 @@ def anonymize(lib, aparams): Returns: An anonymous subclass of the library """ + class anon(lib): "You don't get to know ;)" - def __init__(self,*args,**kwargs): - super().__init__(*aparams,*args,**kwargs) + + def __init__(self, *args, **kwargs): + super().__init__(*aparams, *args, **kwargs) + return anon hamiltonians = { - 'tfim': (TransverseFieldIsing,(reg_size, 1.0, 0.7)), #reg, j , h - 'heisenberg': (HeisenbergXYZ,(reg_size, 1.0, 1.2, 0.8)), # reg, jx , jy, jz - 'random_dense': (RandomizedHamiltonian,(reg_size, 42, 0.8)), #reg, seed, density - 'random_sparse': (RandomizedHamiltonian,(reg_size, 123, 0.4)), #reg, seed, density - 'hubbard': (FermionicHubbard,(reg_size, 1.0, 2.0)) # reg, t, U + "tfim": (TransverseFieldIsing, (reg_size, 1.0, 0.7)), # reg, j , h + "heisenberg": (HeisenbergXYZ, (reg_size, 1.0, 1.2, 0.8)), # reg, jx , jy, jz + "random_dense": ( + RandomizedHamiltonian, + (reg_size, 42, 0.8), + ), # reg, seed, density + "random_sparse": ( + RandomizedHamiltonian, + (reg_size, 123, 0.4), + ), # reg, seed, density + "hubbard": (FermionicHubbard, (reg_size, 1.0, 2.0)), # reg, t, U } - return {k : anonymize(v[0],v[1]) for k, v in hamiltonians.items()} + return {k: anonymize(v[0], v[1]) for k, v in hamiltonians.items()} diff --git a/qbraid_algorithms/evolution/trotter.py b/qbraid_algorithms/evolution/trotter.py index 50f2939..951daff 100644 --- a/qbraid_algorithms/evolution/trotter.py +++ b/qbraid_algorithms/evolution/trotter.py @@ -64,7 +64,6 @@ def trot_suz(self, qubits, t, Hp, Hq, depth): # Generate unique subroutine name name = f"trot_suz_{len(qubits)}_{Hp.name}_{Hq.name}_{depth}" # BUG FIX: Include depth in name - qubit_list = "{" + ",".join([str(q) for q in qubits]) + "}" # Use existing subroutine if available if name in self.gate_ref: @@ -80,7 +79,9 @@ def trot_suz(self, qubits, t, Hp, Hq, depth): # Define subroutine signature qubit_array_param = f"qubit[{len(qubits)}] qubits" - std.begin_subroutine(name, [qubit_array_param, "float time", "int recursion_depth"]) + std.begin_subroutine( + name, [qubit_array_param, "float time", "int recursion_depth"] + ) # Register subroutine to prevent infinite recursion self.gate_ref.append(name) # BUG FIX: Should use set or dict for O(1) lookup @@ -104,9 +105,11 @@ def trot_suz(self, qubits, t, Hp, Hq, depth): # Recursive case: Suzuki's symmetric decomposition # Calculate Suzuki coefficient: Uk = 1/(4 - 4^(1/(2k-1))) # BUG FIX: More robust variable naming and type specification - uk_var = std.add_var("suzuki_coeff", - assignment="1.0/(4.0 - pow(4.0, 1.0/(2.0*recursion_depth - 1.0)))", - qtype="float") + uk_var = std.add_var( + "suzuki_coeff", + assignment="1.0/(4.0 - pow(4.0, 1.0/(2.0*recursion_depth - 1.0)))", + qtype="float", + ) # Suzuki's 5-step symmetric decomposition: # S_k = U_k * S_{k-1} * U_k * S_{k-1} * (1-4*U_k) * S_{k-1} * U_k * S_{k-1} * U_k * S_{k-1} @@ -119,7 +122,9 @@ def trot_suz(self, qubits, t, Hp, Hq, depth): std.call_subroutine(name, ["qubits", f"{uk_var}*time", "recursion_depth-1"]) # Middle (1-4*U_k) * S_{k-1} step (this is the negative weight step) - std.call_subroutine(name, ["qubits", f"(1.0-4.0*{uk_var})*time", "recursion_depth-1"]) + std.call_subroutine( + name, ["qubits", f"(1.0-4.0*{uk_var})*time", "recursion_depth-1"] + ) # Fourth U_k * S_{k-1} step std.call_subroutine(name, ["qubits", f"{uk_var}*time", "recursion_depth-1"]) @@ -153,12 +158,16 @@ def multi_trot_suz(self, qubits, t, hamiltonians, depth): if len(hamiltonians) == 2: self.trot_suz(qubits, t, hamiltonians[0], hamiltonians[1], depth) + class Ha(Trotter): - '''casting class to abstract hamiltonian interface operation''' + """casting class to abstract hamiltonian interface operation""" + name = f"M_trot_suz_{abs(hash(hamiltonians[0].name))}_{abs(hash(hamiltonians[1].name))}" - def apply(self,t,qubits): + + def apply(self, t, qubits): """abstract hamiltonian apply""" self.trot_suz(qubits, t, hamiltonians[0], hamiltonians[1], depth) + return Ha # For multiple Hamiltonians, use binary tree approach @@ -168,18 +177,30 @@ def apply(self,t,qubits): right_hams = hamiltonians[mid:] # Create composite Hamiltonian subroutines - left = self.multi_trot_suz(qubits, t, left_hams, depth) if len(left_hams) > 1 else left_hams[0] - right = self.multi_trot_suz(qubits, t, right_hams, depth) if len(right_hams) > 1 else right_hams[0] + left = ( + self.multi_trot_suz(qubits, t, left_hams, depth) + if len(left_hams) > 1 + else left_hams[0] + ) + right = ( + self.multi_trot_suz(qubits, t, right_hams, depth) + if len(right_hams) > 1 + else right_hams[0] + ) # Apply Trotter to the two composite groups self.trot_suz(qubits, t, left, right, depth) m_name = f"M_trot_suz_{abs(hash(left.name))}_{abs(hash(right.name))}" + class Hb(Trotter): - '''casting class to abstract hamiltonian interface operation''' + """casting class to abstract hamiltonian interface operation""" + name = m_name - def apply(self,t,qubits): + + def apply(self, t, qubits): """abstract hamiltonian apply""" self.trot_suz(qubits, t, left, right, depth) + return Hb def trot_linear(self, qubits, t, hamiltonians, steps=1): diff --git a/qbraid_algorithms/iqft/__init__.py b/qbraid_algorithms/iqft/__init__.py index 10571dc..2d771c2 100644 --- a/qbraid_algorithms/iqft/__init__.py +++ b/qbraid_algorithms/iqft/__init__.py @@ -63,11 +63,11 @@ .. autosummary:: :toctree: ../stubs/ - load_program - generate_subroutine + generate_program + save_to_qasm """ -from .iqft import generate_subroutine, load_program +from .iqft import generate_program, save_to_qasm -__all__ = ["load_program", "generate_subroutine"] +__all__ = ["generate_program", "save_to_qasm"] diff --git a/qbraid_algorithms/iqft/iqft.py b/qbraid_algorithms/iqft/iqft.py index 3f4e55d..fb50cb4 100644 --- a/qbraid_algorithms/iqft/iqft.py +++ b/qbraid_algorithms/iqft/iqft.py @@ -26,7 +26,7 @@ from qbraid_algorithms.utils import _prep_qasm_file -def load_program(num_qubits: int) -> QasmModule: +def generate_program(num_qubits: int) -> QasmModule: """ Load the Inverse Quantum Fourier Transform circuit as a pyqasm module. @@ -38,8 +38,8 @@ def load_program(num_qubits: int) -> QasmModule: """ # Load the IQFT QASM files into a staging directory temp_dir = tempfile.mkdtemp() - iqft_src = Path(__file__).parent / "iqft.qasm" - iqft_sub_src = Path(__file__).parent / "iqft_subroutine.qasm" + iqft_src = Path(__file__).parent.parent / "qasm_resources/iqft.qasm" + iqft_sub_src = Path(__file__).parent.parent / "qasm_resources/iqft_subroutine.qasm" iqft_dst = os.path.join(temp_dir, "iqft.qasm") iqft_sub_dst = os.path.join(temp_dir, "iqft_subroutine.qasm") shutil.copy(iqft_src, iqft_dst) @@ -59,9 +59,7 @@ def load_program(num_qubits: int) -> QasmModule: return module -def generate_subroutine( - num_qubits: int, quiet: bool = False, path: str | None = None -) -> None: +def save_to_qasm(num_qubits: int, quiet: bool = False, path: str | None = None) -> None: """ Creates an IQFT subroutine module with user-defined number of qubits. @@ -75,7 +73,7 @@ def generate_subroutine( None """ # Copy the IQFT subroutine QASM file to the specified or current working directory - iqft_src = Path(__file__).parent / "iqft_subroutine.qasm" + iqft_src = Path(__file__).parent.parent / "qasm_resources/iqft_subroutine.qasm" if path is None: iqft_dst = os.path.join(os.getcwd(), "iqft.qasm") else: diff --git a/qbraid_algorithms/bells_inequality/bells_inequality.qasm b/qbraid_algorithms/qasm_resources/bells_inequality.qasm similarity index 100% rename from qbraid_algorithms/bells_inequality/bells_inequality.qasm rename to qbraid_algorithms/qasm_resources/bells_inequality.qasm diff --git a/qbraid_algorithms/bernstein_vazirani/bernvaz.qasm b/qbraid_algorithms/qasm_resources/bernvaz.qasm similarity index 100% rename from qbraid_algorithms/bernstein_vazirani/bernvaz.qasm rename to qbraid_algorithms/qasm_resources/bernvaz.qasm diff --git a/qbraid_algorithms/bernstein_vazirani/bernvaz_subroutine.qasm b/qbraid_algorithms/qasm_resources/bernvaz_subroutine.qasm similarity index 100% rename from qbraid_algorithms/bernstein_vazirani/bernvaz_subroutine.qasm rename to qbraid_algorithms/qasm_resources/bernvaz_subroutine.qasm diff --git a/qbraid_algorithms/iqft/iqft.qasm b/qbraid_algorithms/qasm_resources/iqft.qasm similarity index 100% rename from qbraid_algorithms/iqft/iqft.qasm rename to qbraid_algorithms/qasm_resources/iqft.qasm diff --git a/qbraid_algorithms/iqft/iqft_subroutine.qasm b/qbraid_algorithms/qasm_resources/iqft_subroutine.qasm similarity index 100% rename from qbraid_algorithms/iqft/iqft_subroutine.qasm rename to qbraid_algorithms/qasm_resources/iqft_subroutine.qasm diff --git a/qbraid_algorithms/bernstein_vazirani/oracle.qasm b/qbraid_algorithms/qasm_resources/oracle.qasm similarity index 100% rename from qbraid_algorithms/bernstein_vazirani/oracle.qasm rename to qbraid_algorithms/qasm_resources/oracle.qasm diff --git a/qbraid_algorithms/qft/qft.qasm b/qbraid_algorithms/qasm_resources/qft.qasm similarity index 100% rename from qbraid_algorithms/qft/qft.qasm rename to qbraid_algorithms/qasm_resources/qft.qasm diff --git a/qbraid_algorithms/qft/qft_subroutine.qasm b/qbraid_algorithms/qasm_resources/qft_subroutine.qasm similarity index 100% rename from qbraid_algorithms/qft/qft_subroutine.qasm rename to qbraid_algorithms/qasm_resources/qft_subroutine.qasm diff --git a/qbraid_algorithms/qpe/qpe.qasm b/qbraid_algorithms/qasm_resources/qpe.qasm similarity index 100% rename from qbraid_algorithms/qpe/qpe.qasm rename to qbraid_algorithms/qasm_resources/qpe.qasm diff --git a/qbraid_algorithms/qpe/qpe_subroutine.qasm b/qbraid_algorithms/qasm_resources/qpe_subroutine.qasm similarity index 100% rename from qbraid_algorithms/qpe/qpe_subroutine.qasm rename to qbraid_algorithms/qasm_resources/qpe_subroutine.qasm diff --git a/qbraid_algorithms/qft/__init__.py b/qbraid_algorithms/qft/__init__.py index 5488cac..1716c8a 100644 --- a/qbraid_algorithms/qft/__init__.py +++ b/qbraid_algorithms/qft/__init__.py @@ -81,12 +81,12 @@ .. autosummary:: :toctree: ../stubs/ - load_program - generate_subroutine + generate_program + save_to_qasm """ -from .qft import generate_subroutine, load_program +from .qft import generate_program, save_to_qasm from .qft_lib import QFTLibrary -__all__ = ["load_program", "generate_subroutine", "QFTLibrary"] +__all__ = ["generate_program", "save_to_qasm", "QFTLibrary"] diff --git a/qbraid_algorithms/qft/qft.py b/qbraid_algorithms/qft/qft.py index d3742fd..57a2573 100644 --- a/qbraid_algorithms/qft/qft.py +++ b/qbraid_algorithms/qft/qft.py @@ -42,7 +42,7 @@ from .qft_lib import QFTLibrary -def load_program(num_qubits: int) -> QasmModule: +def generate_program(num_qubits: int) -> QasmModule: """ Load the Quantum Fourier Transform circuit as a pyqasm module. Args: @@ -58,12 +58,10 @@ def load_program(num_qubits: int) -> QasmModule: qft.QFT([*range(num_qubits)]) module = pyqasm.loads(sys.build()) - return module -def generate_subroutine( - num_qubits: int, quiet: bool = False, path: str | None = None -) -> None: + +def save_to_qasm(num_qubits: int, quiet: bool = False, path: str | None = None) -> None: """ Creates a QFT subroutine module with user-defined number of qubits. @@ -77,7 +75,7 @@ def generate_subroutine( None """ # Copy the QFT subroutine QASM file to the specified or current working directory - qft_src = Path(__file__).parent / "qft_subroutine.qasm" + qft_src = Path(__file__).parent.parent / "qasm_resources/qft_subroutine.qasm" if path is None: qft_dst = os.path.join(os.getcwd(), "qft.qasm") else: diff --git a/qbraid_algorithms/qpe/__init__.py b/qbraid_algorithms/qpe/__init__.py index 918cade..b0f963a 100644 --- a/qbraid_algorithms/qpe/__init__.py +++ b/qbraid_algorithms/qpe/__init__.py @@ -75,18 +75,18 @@ .. autosummary:: :toctree: ../stubs/ - load_program - generate_subroutine + generate_program + save_to_qasm get_result """ from .phase_est import PhaseEstimationLibrary -from .qpe import generate_subroutine, get_result, load_program +from .qpe import generate_program, get_result, save_to_qasm __all__ = [ - "load_program", - "generate_subroutine", + "generate_program", + "save_to_qasm", "get_result", "PhaseEstimationLibrary", ] diff --git a/qbraid_algorithms/qpe/qpe.py b/qbraid_algorithms/qpe/qpe.py index 1048d1f..fca154f 100644 --- a/qbraid_algorithms/qpe/qpe.py +++ b/qbraid_algorithms/qpe/qpe.py @@ -43,7 +43,7 @@ from qbraid_algorithms.utils import _prep_qasm_file -def load_program( +def generate_program( unitary_filepath: str, psi_filepath: str, num_qubits: int = 4, @@ -67,13 +67,13 @@ def load_program( """ # Load the QPE QASM files into a staging directory temp_dir = tempfile.mkdtemp() - qpe_src = Path(__file__).parent / "qpe.qasm" - qpe_sub_src = Path(__file__).parent / "qpe_subroutine.qasm" + qpe_src = Path(__file__).parent.parent / "qasm_resources/qpe.qasm" + qpe_sub_src = Path(__file__).parent.parent / "qasm_resources/qpe_subroutine.qasm" qpe_dst = os.path.join(temp_dir, "qpe.qasm") qpe_sub_dst = os.path.join(temp_dir, "qpe_subroutine.qasm") shutil.copy(qpe_src, qpe_dst) shutil.copy(qpe_sub_src, qpe_sub_dst) - iqft.generate_subroutine(num_qubits, quiet=True, path=temp_dir) + iqft.save_to_qasm(num_qubits, quiet=True, path=temp_dir) # Get the string defining the custom gate and its controlled version custom_gate_str = _get_unitary(unitary_filepath) # Get the string defining the eigenstate preparation gate @@ -95,7 +95,7 @@ def load_program( return module -def generate_subroutine( +def save_to_qasm( unitary_filepath: str, num_qubits: int = 4, quiet: bool = False, @@ -117,13 +117,13 @@ def generate_subroutine( None """ # Copy the QPE subroutine QASM file to the specified or current working directory - qpe_src = Path(__file__).parent / "qpe_subroutine.qasm" + qpe_src = Path(__file__).parent.parent / "qasm_resources/qpe_subroutine.qasm" if path is None: qpe_dst = os.path.join(os.getcwd(), "qpe.qasm") else: qpe_dst = os.path.join(path, "qpe.qasm") shutil.copy(qpe_src, qpe_dst) - iqft.generate_subroutine(num_qubits, quiet=True, path=path) + iqft.save_to_qasm(num_qubits, quiet=True, path=path) # Get the string defining the custom gate and its controlled version custom_gate_str = _get_unitary(unitary_filepath) # Replace variable placeholders with user-defined parameters diff --git a/qbraid_algorithms/qtran/gate_library.py b/qbraid_algorithms/qtran/gate_library.py index 799717b..53629e7 100644 --- a/qbraid_algorithms/qtran/gate_library.py +++ b/qbraid_algorithms/qtran/gate_library.py @@ -31,6 +31,8 @@ Class Extensions: - std_gates """ + + # pylint: disable=too-many-positional-arguments,invalid-name # im sticking to std_gates as it needs to be viewed as a default name and matches the qasm name class GateLibrary: @@ -47,7 +49,9 @@ class GateLibrary: """ - def __init__(self, gate_import, gate_ref, gate_defs, program_append, builder, annotated=False): + def __init__( + self, gate_import, gate_ref, gate_defs, program_append, builder, annotated=False + ): """ Initialize the gate library with necessary components. @@ -59,15 +63,17 @@ def __init__(self, gate_import, gate_ref, gate_defs, program_append, builder, an builder: Reference to the circuit builder annotated: Whether to use annotated syntax """ - self.gate_import = gate_import # Libraries to import - self.gate_ref = gate_ref # Available gate names - self.gate_defs = gate_defs # Gate definitions dictionary - self.program = program_append # Function to append code - self.builder = builder # Circuit builder reference - self.annotated = annotated # Annotation flag - self.prefix = "" # Gate modifier (e.g., "ctrl @") - self.call_space = "qb[{}]" # for namespace (e.g. global qubit register vs gate aliases) - self.name = "GATE_LIB" # Library identifier + self.gate_import = gate_import # Libraries to import + self.gate_ref = gate_ref # Available gate names + self.gate_defs = gate_defs # Gate definitions dictionary + self.program = program_append # Function to append code + self.builder = builder # Circuit builder reference + self.annotated = annotated # Annotation flag + self.prefix = "" # Gate modifier (e.g., "ctrl @") + self.call_space = ( + "qb[{}]" # for namespace (e.g. global qubit register vs gate aliases) + ) + self.name = "GATE_LIB" # Library identifier def call_gate(self, gate, target, controls=None, phases=None, prefix=""): """ @@ -87,23 +93,25 @@ def call_gate(self, gate, target, controls=None, phases=None, prefix=""): """ # Validate gate exists in current scope if gate not in self.gate_ref: - print(f"stdgates: gate {gate} is not part of visible scope, " - f"make sure that this isn't a floating reference / malformed statement, " - f"or is at least previously defined within untracked environment definitions") + print( + f"stdgates: gate {gate} is not part of visible scope, " + f"make sure that this isn't a floating reference / malformed statement, " + f"or is at least previously defined within untracked environment definitions" + ) # Build gate call string call = prefix + str(gate) # Add phase parameters if provided if phases is not None: - call += '(' + call += "(" if isinstance(phases, list): call += str(phases[0]) # Fixed: was phase[0] for phase in phases[1:]: call += f",{phase}" else: call += str(phases) - call += ')' + call += ")" call += " " # Add control qubits if provided if controls is not None: @@ -112,13 +120,13 @@ def call_gate(self, gate, target, controls=None, phases=None, prefix=""): call += self.call_space.format(control) + "," else: - call += self.call_space.format(controls) + ',' + call += self.call_space.format(controls) + "," # Add target qubit and complete the statement call += self.call_space.format(target) + ";" self.program(self.prefix + call) - def call_subroutine(self,subroutine,parameters,capture=None): + def call_subroutine(self, subroutine, parameters, capture=None): """ SUBROUTINE APPLICATION @@ -133,14 +141,15 @@ def call_subroutine(self,subroutine,parameters,capture=None): parameters: list of all parameters to apply """ if subroutine not in self.gate_ref: - print(f"stdgates: subroutine {subroutine} is not part of visible scope, " - f"make sure that this isn't a floating reference / malformed statement, " - f"or is at least previously defined within untracked environment definitions") + print( + f"stdgates: subroutine {subroutine} is not part of visible scope, " + f"make sure that this isn't a floating reference / malformed statement, " + f"or is at least previously defined within untracked environment definitions" + ) call = f"{capture + ' = ' if capture is not None else ''}{subroutine}({', '.join(str(a) for a in parameters)});" self.program(call) - def measure(self, qubits: list, clbits: list): """ MEASUREMENT @@ -234,7 +243,13 @@ def begin_loop(self, iterator, ident: str = "i"): # Float range with explicit values base = "float" r = int(iterator[2]) - dom = "{" + str([iterator[0] + float(i)/(r-1) for i in range(r)])[1:-1] + "}" + dom = ( + "{" + + str([iterator[0] + float(i) / (r - 1) for i in range(r)])[ + 1:-1 + ] + + "}" + ) elif isinstance(iterator, str): # Custom loop syntax call = "for " + iterator + "{" @@ -265,11 +280,13 @@ def begin_gate(self, name, qargs, params=None): """ if name in self.gate_ref: print(f"warning: gate {name} replacing existing namespace") - call = f"gate {name}{'('+','.join(params)+')' if params is not None else ''} {','.join(qargs)}" +"{" + call = ( + f"gate {name}{'('+','.join(params)+')' if params is not None else ''} {','.join(qargs)}" + + "{" + ) self.program(call) self.builder.scope += 1 - def begin_subroutine(self, name, parameters: list[str], return_type=None): """ SUBROUTINE DEFINITION @@ -286,7 +303,10 @@ def begin_subroutine(self, name, parameters: list[str], return_type=None): """ if name in self.gate_ref: print(f"warning: subroutine {name} replacing existing namespace") - call = f"def {name}({','.join(parameters)}) {' -> ' + return_type if return_type is not None else ''}" + "{" + call = ( + f"def {name}({','.join(parameters)}) {' -> ' + return_type if return_type is not None else ''}" + + "{" + ) self.program(call) self.builder.scope += 1 @@ -327,7 +347,9 @@ def controlled_op(self, gate_call, params, n=0): """ if isinstance(gate_call, str): # Direct gate name - call with control prefix - self.call_gate(gate_call, *params, prefix=f"ctrl{'' if n == 0 else f'({n})'} @ ") + self.call_gate( + gate_call, *params, prefix=f"ctrl{'' if n == 0 else f'({n})'} @ " + ) else: # Gate function - set modifier and call self.prefix = f"ctrl{'' if n<2 else f'({n})'} @ " @@ -369,21 +391,21 @@ def add_gate(self, name: str, gate_def: str): self.gate_defs[name] = gate_def self.gate_ref.append(name) - def add_var(self,name,assignment = None,qtype= None): - ''' + def add_var(self, name, assignment=None, qtype=None): + """ simple stub for programatically adding a variable Args: name: variable name Assignment: whatever definition you want as long as it resolves to a string - ''' + """ if name in self.gate_ref: print(f"warning: gate {name} replacing existing namespace") call = f"{qtype if qtype is not None else 'let'} {name} {f'= {assignment}' if assignment is not None else ''};" self.program(call) return name - def merge(self,program,imports,definitions,name): + def merge(self, program, imports, definitions, name): """ Merges data from a built library/GateBuilder into the current library bases scope Args: @@ -416,12 +438,33 @@ class std_gates(GateLibrary): """ # Standard gate set from OpenQASM 3.0 specification - gates = ["phase", "x", "y", "z", "h", "s", "sdg", "sx", - 'rx','ry','rz', 'p', - 'cx', 'cy', 'cz', 'cp', 'crx', 'cry', 'crz', 'cnot', - 'swap', 'ccx', 'cswap'] - - name = 'stdgates.inc' # Standard library file name + gates = [ + "phase", + "x", + "y", + "z", + "h", + "s", + "sdg", + "sx", + "rx", + "ry", + "rz", + "p", + "cx", + "cy", + "cz", + "cp", + "crx", + "cry", + "crz", + "cnot", + "swap", + "ccx", + "cswap", + ] + + name = "stdgates.inc" # Standard library file name def __init__(self, *args, **kwargs): """Initialize standard gates library and register all gates.""" @@ -445,52 +488,51 @@ def phase(self, theta, targ): def x(self, targ): """Apply Pauli-X gate (bit flip): !0⟩> !1⟩, !1⟩> !0⟩""" - self.call_gate('x', targ) + self.call_gate("x", targ) def y(self, targ): """Apply Pauli-Y gate: !0⟩>i!1⟩, !1⟩>-i!0⟩""" - self.call_gate('y', targ) + self.call_gate("y", targ) def z(self, targ): """Apply Pauli-Z gate (phase flip): !0⟩> !0⟩, !1⟩>-!1⟩""" - self.call_gate('z', targ) + self.call_gate("z", targ) def h(self, targ): """Apply Hadamard gate: creates superposition""" - self.call_gate('h', targ) + self.call_gate("h", targ) def s(self, targ): """Apply S gate (phase): !1⟩>i!1⟩""" - self.call_gate('s', targ) + self.call_gate("s", targ) def sdg(self, targ): """Apply S-dagger gate (inverse phase): !1⟩>-i!1⟩""" - self.call_gate('sdg', targ) + self.call_gate("sdg", targ) def sx(self, targ): """Apply square root of X gate""" - self.call_gate('sx', targ) + self.call_gate("sx", targ) - def rx(self,theta,targ): + def rx(self, theta, targ): """Apply rx gate""" self.call_gate("rx", targ, phases=theta) - def ry(self,theta,targ): + def ry(self, theta, targ): """Apply ry gate""" self.call_gate("ry", targ, phases=theta) - def rz(self,theta,targ): + def rz(self, theta, targ): """Apply rz gate""" self.call_gate("rz", targ, phases=theta) - # ═══════════════════════════════════════════════════════════════════════════ # Two-QUBIT GATES # ═══════════════════════════════════════════════════════════════════════════ - def cnot(self,control,targ): - '''Apply CNOT gate''' - self.call_gate("cnot",targ,controls=control) + def cnot(self, control, targ): + """Apply CNOT gate""" + self.call_gate("cnot", targ, controls=control) - def cry(self,theta,control,targ): + def cry(self, theta, control, targ): """Apply controlled ry gate""" - self.call_gate("cry", targ,controls=control, phases=theta) + self.call_gate("cry", targ, controls=control, phases=theta) diff --git a/qbraid_algorithms/qtran/module_loader.py b/qbraid_algorithms/qtran/module_loader.py index d6e7d26..1cfcde8 100644 --- a/qbraid_algorithms/qtran/module_loader.py +++ b/qbraid_algorithms/qtran/module_loader.py @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -''' +""" This module provides a decorator, `qasm_pipe`, for functions that generate QASM program strings. The decorator captures 'path' and 'quiet' keyword arguments, writes the returned QASM string to a file, and optionally prints the file location. @@ -26,7 +26,7 @@ @qasm_pipe def generate_qasm(..., path=None, quiet=False): ... - return file_name, program_string''' + return file_name, program_string""" import os from functools import wraps from typing import Callable @@ -43,11 +43,12 @@ def qasm_pipe(func: Callable) -> Callable: The decorator will create a file named "{file_name}.qasm" and write the program_string to it. """ + @wraps(func) def wrapper(*args, **kwargs): # Extract path and quiet from kwargs, with defaults - path = kwargs.pop('path', None) - quiet = kwargs.pop('quiet', False) + path = kwargs.pop("path", None) + quiet = kwargs.pop("quiet", False) # Call the decorated function to get the tuple output file_name, program_string = func(*args, **kwargs) @@ -61,7 +62,7 @@ def wrapper(*args, **kwargs): output_path = os.path.join(path, f"{file_name}.qasm") # Write the program string to the file - with open(output_path, 'w', encoding='utf-8') as file: + with open(output_path, "w", encoding="utf-8") as file: file.write(program_string) if not quiet: diff --git a/qbraid_algorithms/qtran/qasm_builder.py b/qbraid_algorithms/qtran/qasm_builder.py index 28c9e6f..a609a0c 100644 --- a/qbraid_algorithms/qtran/qasm_builder.py +++ b/qbraid_algorithms/qtran/qasm_builder.py @@ -62,11 +62,11 @@ def __init__(self): - Empty program string for accumulating generated code - Zero scope level for proper indentation tracking """ - self.imports = [] # List of library names to import (e.g., "std_gates.inc") - self.gate_defs = {} # Dictionary mapping gate names to definition strings - self.gate_refs = [] # List of available gate names for validation - self.program = "" # Accumulated OpenQASM program code - self.scope = 0 # Current indentation/nesting level + self.imports = [] # List of library names to import (e.g., "std_gates.inc") + self.gate_defs = {} # Dictionary mapping gate names to definition strings + self.gate_refs = [] # List of available gate names for validation + self.program = "" # Accumulated OpenQASM program code + self.scope = 0 # Current indentation/nesting level def import_library(self, lib_class, annotated=False): """ @@ -89,12 +89,12 @@ def import_library(self, lib_class, annotated=False): program.x(0) # Apply X gate to qubit 0 """ return lib_class( - gate_import=self.imports, # Share import list with library - gate_ref=self.gate_refs, # Share gate references for validation - gate_defs=self.gate_defs, # Share gate definitions dictionary + gate_import=self.imports, # Share import list with library + gate_ref=self.gate_refs, # Share gate references for validation + gate_defs=self.gate_defs, # Share gate definitions dictionary program_append=self.program_append, # Provide code appending function - builder=self, # Pass reference to this builder - annotated=annotated # Set annotation mode + builder=self, # Pass reference to this builder + annotated=annotated, # Set annotation mode ) def program_append(self, line): @@ -112,7 +112,7 @@ def program_append(self, line): Indentation is automatically applied based on self.scope. Each scope level contributes one tab character. """ - self.program += self.scope * '\t' + line + "\n" + self.program += self.scope * "\t" + line + "\n" class GateBuilder(FileBuilder): @@ -133,7 +133,7 @@ class GateBuilder(FileBuilder): def import_library(self, lib_class, annotated=False): ret = super().import_library(lib_class, annotated) ret.call_space = " {}" - return ret + return ret def build(self): """ @@ -151,8 +151,10 @@ def build(self): Prints warning if scope is not zero (unclosed blocks) """ if self.scope != 0: - print("Warning (GateBuilder): built qasm has unclosed scope, " - "string will fail compile in native") + print( + "Warning (GateBuilder): built qasm has unclosed scope, " + "string will fail compile in native" + ) return self.program, self.imports, self.gate_defs @@ -277,14 +279,18 @@ def build(self): Prints warning if scope is not zero (unclosed blocks) """ if self.scope != 0: - print("Warning (QasmBuilder): built qasm has unclosed scope, " - "string will fail compile in native") + print( + "Warning (QasmBuilder): built qasm has unclosed scope, " + "string will fail compile in native" + ) # Start with version header qasm_code = self.qasm_header # Add all library includes - qasm_code += "\n".join(f"include \"{import_line}\";" for import_line in self.imports) + qasm_code += "\n".join( + f'include "{import_line}";' for import_line in self.imports + ) # Add qubit declaration circuit_def = f"qubit[{int(self.qubits)}] qb;\n" @@ -342,14 +348,18 @@ def build(self): Prints warning if scope is not zero (unclosed blocks) """ if self.scope != 0: - print("Warning (IncludeBuilder): built include has unclosed scope, " - "string will fail compile in native") + print( + "Warning (IncludeBuilder): built include has unclosed scope, " + "string will fail compile in native" + ) # Initialize with empty string (note: original code had bug with undefined qasm_code) qasm_code = "" # Add all library includes - qasm_code += "\n".join(f"include \"{import_line}\";" for import_line in self.imports) + qasm_code += "\n".join( + f'include "{import_line}";' for import_line in self.imports + ) # Add all gate definitions for gate_def in self.gate_defs.values(): diff --git a/tests/test_bells_inequality.py b/tests/test_bells_inequality.py index c279afd..3024a07 100644 --- a/tests/test_bells_inequality.py +++ b/tests/test_bells_inequality.py @@ -21,8 +21,8 @@ from qbraid_algorithms import bells_inequality -def test_load_program_returns_correct_type(): - """Test that load_program returns a pyqasm module object.""" - circuit = bells_inequality.load_program() +def test_generate_program_returns_correct_type(): + """Test that generate_program returns a pyqasm module object.""" + circuit = bells_inequality.generate_program() # Check that it returns a valid Qasm# module module assert isinstance(circuit, QasmModule), f"Expected QasmModule, got {type(circuit)}" diff --git a/tests/test_bernvaz.py b/tests/test_bernvaz.py index 71c2388..a98633b 100644 --- a/tests/test_bernvaz.py +++ b/tests/test_bernvaz.py @@ -29,32 +29,32 @@ from .local_device import LocalDevice -def test_load_program(): - """Test that load_program correctly returns a pyqasm module object.""" - bv_module = bv.load_program("101") +def test_generate_program(): + """Test that generate_program correctly returns a pyqasm module object.""" + bv_module = bv.generate_program("101") assert isinstance(bv_module, QasmModule) assert bv_module.num_qubits == 4 # 3 data qubits + 1 ancilla qubit -def test_generate_subroutine(): - """Test that generate_subroutine correctly generates the subroutine QASM.""" +def test_save_to_qasm(): + """Test that save_to_qasm correctly generates the subroutine QASM.""" s = "101" with tempfile.TemporaryDirectory() as test_dir: - bv.generate_subroutine(s, quiet=True, path=test_dir) + bv.save_to_qasm(s, quiet=True, path=test_dir) # Ensure the file was created subroutine_qasm = Path(test_dir) / "bernvaz.qasm" assert subroutine_qasm.exists() -def test_generate_subroutine_default_path(): - """Test generate_subroutine with default path (current working directory).""" +def test_save_to_qasm_default_path(): + """Test save_to_qasm with default path (current working directory).""" s = "101" original_cwd = os.getcwd() # Create temporary directory and change to it with tempfile.TemporaryDirectory() as test_dir: os.chdir(test_dir) try: - bv.generate_subroutine(s, quiet=True, path=None) + bv.save_to_qasm(s, quiet=True, path=None) # Ensure the file was created in current directory subroutine_qasm = Path(test_dir) / "bernvaz.qasm" assert subroutine_qasm.exists() @@ -63,15 +63,15 @@ def test_generate_subroutine_default_path(): os.chdir(original_cwd) -def test_generate_subroutine_verbose(): - """Test generate_subroutine with verbose output (quiet=False).""" +def test_save_to_qasm_verbose(): + """Test save_to_qasm with verbose output (quiet=False).""" s = "101" with tempfile.TemporaryDirectory() as test_dir: # Capture stdout captured_output = io.StringIO() sys.stdout = captured_output try: - bv.generate_subroutine(s, quiet=False, path=test_dir) + bv.save_to_qasm(s, quiet=False, path=test_dir) # Get the captured output output = captured_output.getvalue() # Ensure the verbose message was printed @@ -138,7 +138,7 @@ def test_algorithm_101(): """Test the Bernstein-Vazirani algorithm implementation for the input '101'.""" s = "101" device = LocalDevice() - module = bv.load_program(s) + module = bv.generate_program(s) # Unrolling is necessary for proper execution module.unroll() program_str = pyqasm.dumps(module) @@ -152,7 +152,7 @@ def test_algorithm_10101(): """Test the Bernstein-Vazirani algorithm implementation for the input '10101'.""" s = "10101" device = LocalDevice() - module = bv.load_program(s) + module = bv.generate_program(s) # Unrolling is necessary for proper execution module.unroll() program_str = pyqasm.dumps(module) diff --git a/tests/test_builder_algorithms.py b/tests/test_builder_algorithms.py index fc1d37a..242a115 100644 --- a/tests/test_builder_algorithms.py +++ b/tests/test_builder_algorithms.py @@ -27,7 +27,7 @@ """ import string -#lotta disabled linting cases cause of general stability testing +# lotta disabled linting cases cause of general stability testing # ruff: noqa: F841 # pylint: disable=C0303,broad-exception-caught,missing-class-docstring, unused-variable # pylint: disable=missing-function-docstring,too-many-locals,duplicate-code,attribute-defined-outside-init @@ -46,11 +46,13 @@ try: import pyqasm as pq + PYQASM_AVAILABLE = True except ImportError: PYQASM_AVAILABLE = False pytest.skip("pyqasm not available", allow_module_level=True) + class TestPhaseEstimationAlgorithm: """Test Phase Estimation algorithm.""" @@ -66,6 +68,7 @@ def test_phase_estimation_basic_functionality(self): anc_c = builder.claim_clbits(3) std = builder.import_library(std_gates) pe = builder.import_library(PhaseEstimationLibrary) + class Ham(hamiltonian): def apply(self, *args, **kwargs): super().apply(0.1, *args, **kwargs) @@ -84,14 +87,16 @@ def controlled(self, *args, **kwargs): assert len(program) > 0 # Should contain Phase Estimation-specific elements - assert 'P_EST' in program or 'p_est' in program.lower() + assert "P_EST" in program or "p_est" in program.lower() # Validate with pyqasm # is_valid, error_msg = self._validate_qasm_with_pyqasm(program) # assert is_valid, f"Phase Estimation failed for {ham_name}: {error_msg}\nQASM:\n{program}" except Exception as e: - pytest.fail(f"Phase Estimation basic test failed for {ham_name}: {str(e)}") + pytest.fail( + f"Phase Estimation basic test failed for {ham_name}: {str(e)}" + ) def test_phase_estimation_evolution(self): """Test Phase Estimation with circuit evolution.""" @@ -113,14 +118,17 @@ def test_phase_estimation_evolution(self): assert len(program) > 0 # Should contain Phase Estimation-specific elements - assert 'P_EST' in program or 'p_est' in program.lower() + assert "P_EST" in program or "p_est" in program.lower() # Validate with pyqasm # is_valid, error_msg = self._validate_qasm_with_pyqasm(program) # assert is_valid, f"Phase Estimation failed for {ham_name}: {error_msg}\nQASM:\n{program}" except Exception as e: - pytest.fail(f"Phase Estimation evolution test failed for {ham_name}: {str(e)}") + pytest.fail( + f"Phase Estimation evolution test failed for {ham_name}: {str(e)}" + ) + class TestGQSPAlgorithm: """Test Generalized Quantum Signal Processing algorithm.""" @@ -137,17 +145,18 @@ def test_gqsp_basic_functionality(self): builder = QasmBuilder(3) std = builder.import_library(std_gates) gqsp = builder.import_library(GQSP) + class Ham(hamiltonian): - def apply(self,*args,**kwargs): - super().apply(.1,*args,**kwargs) + def apply(self, *args, **kwargs): + super().apply(0.1, *args, **kwargs) - def controlled(self,*args,**kwargs): - super().controlled(.1,*args,**kwargs) + def controlled(self, *args, **kwargs): + super().controlled(0.1, *args, **kwargs) try: # Test GQSP with depth 3 gqsp.GQSP(self.test_qubits, self.test_phases, Ham, depth=3) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) program = builder.build() @@ -156,7 +165,7 @@ def controlled(self,*args,**kwargs): assert len(program) > 0 # Should contain GQSP-specific elements - assert 'GQSP' in program or 'gqsp' in program.lower() + assert "GQSP" in program or "gqsp" in program.lower() # Validate with pyqasm # is_valid, error_msg = self._validate_qasm_with_pyqasm(program) @@ -169,12 +178,14 @@ def test_gqsp_different_depths(self): """Test GQSP with various circuit depths.""" depths = [1, 2, 3, 5] hamiltonian = list(self.test_hamiltonians.values())[0] # Use first Hamiltonian + class Ham(hamiltonian): - def apply(self,*args,**kwargs): - super().apply(.1,*args,**kwargs) + def apply(self, *args, **kwargs): + super().apply(0.1, *args, **kwargs) + + def controlled(self, *args, **kwargs): + super().controlled(0.1, *args, **kwargs) - def controlled(self,*args,**kwargs): - super().controlled(.1,*args,**kwargs) for depth in depths: builder = QasmBuilder(3) std = builder.import_library(std_gates) @@ -185,7 +196,7 @@ def controlled(self,*args,**kwargs): try: gqsp.GQSP(self.test_qubits, phases, Ham, depth=depth) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) full_qasm = builder.build() @@ -194,7 +205,9 @@ def controlled(self,*args,**kwargs): # assert is_valid, f"GQSP depth {depth} invalid: {error_msg}" # Check depth appears in gate name - assert f"_{depth}_" in full_qasm or f"depth={depth}" in full_qasm.lower() + assert ( + f"_{depth}_" in full_qasm or f"depth={depth}" in full_qasm.lower() + ) except Exception as e: pytest.fail(f"GQSP depth {depth} test failed: {str(e)}") @@ -264,6 +277,7 @@ def _validate_qasm_with_pyqasm(self, qasm_string): except Exception as e: return False, str(e) + class TestTrotterAlgorithm: """Test Trotter decomposition algorithm.""" @@ -274,7 +288,9 @@ def setup_method(self): def test_trotter_basic_functionality(self): """Test basic Trotter decomposition between Hamiltonian pairs.""" - ham_pairs = list(combinations(self.test_hamiltonians.items(), 2))[:3] # Test 3 pairs + ham_pairs = list(combinations(self.test_hamiltonians.items(), 2))[ + :3 + ] # Test 3 pairs for (name1, ham1), (name2, ham2) in ham_pairs: builder = QasmBuilder(3) @@ -284,12 +300,12 @@ def test_trotter_basic_functionality(self): try: # Test Suzuki-Trotter decomposition trotter.trot_suz(self.test_qubits, "0.5", ham1, ham2, depth=2) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) full_qasm = builder.build() # print(program) # Validate structure - assert 'trot_suz' in full_qasm or 'trotter' in full_qasm.lower() + assert "trot_suz" in full_qasm or "trotter" in full_qasm.lower() # Validate with pyqasm # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) @@ -310,7 +326,7 @@ def test_trotter_different_depths(self): try: trotter.trot_suz(self.test_qubits, "0.3", ham1, ham2, depth=depth) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) # full_qasm = builder.build() @@ -323,7 +339,9 @@ def test_trotter_different_depths(self): def test_trotter_multi_hamiltonian(self): """Test Trotter with multiple Hamiltonians.""" - hamiltonians = list(self.test_hamiltonians.values())[:3] # Test with 3 Hamiltonians + hamiltonians = list(self.test_hamiltonians.values())[ + :3 + ] # Test with 3 Hamiltonians builder = QasmBuilder(3) std = builder.import_library(std_gates) @@ -332,7 +350,7 @@ def test_trotter_multi_hamiltonian(self): try: # Test multi-Hamiltonian Trotter trotter.multi_trot_suz(self.test_qubits, "0.4", hamiltonians, depth=2) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) # full_qasm = builder.build() @@ -354,7 +372,7 @@ def test_trotter_linear_decomposition(self): try: # Test linear Trotter trotter.trot_linear(self.test_qubits, "0.2", hamiltonians, steps=4) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) # full_qasm = builder.build() @@ -377,7 +395,7 @@ def test_trotter_time_parameters(self): try: trotter.trot_suz(self.test_qubits, time_param, ham1, ham2, depth=1) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) # full_qasm = builder.build() @@ -386,7 +404,9 @@ def test_trotter_time_parameters(self): # assert is_valid, f"Trotter with time {time_param} invalid: {error_msg}" except Exception as e: - pytest.fail(f"Trotter time parameter {time_param} test failed: {str(e)}") + pytest.fail( + f"Trotter time parameter {time_param} test failed: {str(e)}" + ) def _validate_qasm_with_pyqasm(self, qasm_string): """Helper method to validate QASM using pyqasm.""" @@ -401,20 +421,21 @@ def _validate_qasm_with_pyqasm(self, qasm_string): except Exception as e: return False, str(e) + class TestPrepSelAlgorithm: """Test Preparation-Selection library algorithms.""" def setup_method(self): """Set up test environment.""" - self.test_qubits = [f'q[{i}]' for i in range(4)] + self.test_qubits = [f"q[{i}]" for i in range(4)] def test_prep_select_with_matrix(self): """Test prep-select with matrix input.""" # Create test matrices of different sizes test_matrices = [ np.array([[1, 0], [0, -1]]), # Pauli-Z - np.array([[0, 1], [1, 0]]), # Pauli-X - np.random.random((4, 4)) + 1j * np.random.random((4, 4)) # Random 4x4 + np.array([[0, 1], [1, 0]]), # Pauli-X + np.random.random((4, 4)) + 1j * np.random.random((4, 4)), # Random 4x4 ] for i, matrix in enumerate(test_matrices): @@ -428,12 +449,12 @@ def test_prep_select_with_matrix(self): try: # Test prep-select with matrix prep_sel.prep_select(self.test_qubits, matrix, approximate=0.1) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) full_qasm = builder.build() # Should contain prep-select elements - assert 'PS_' in full_qasm or 'prep' in full_qasm.lower() + assert "PS_" in full_qasm or "prep" in full_qasm.lower() # Validate QASM # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) @@ -448,7 +469,7 @@ def test_prep_select_with_operator_chain(self): test_chains = [ [("X", 0.5), ("Z", 0.3), ("Y", 0.2)], [("XX", 0.7), ("ZZ", 0.4), ("XY", 0.1)], - [("XXXX", 0.8), ("ZZZZ", 0.2)] + [("XXXX", 0.8), ("ZZZZ", 0.2)], ] for i, chain in enumerate(test_chains): @@ -461,7 +482,7 @@ def test_prep_select_with_operator_chain(self): try: prep_sel.prep_select(self.test_qubits, chain) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) # full_qasm = builder.build() @@ -479,7 +500,7 @@ def test_preparation_library(self): [0.5, 0.3, 0.2], [0.25, 0.25, 0.25, 0.25], [0.1, 0.2, 0.3, 0.4], - [0.8, 0.1, 0.05, 0.05] + [0.8, 0.1, 0.05, 0.05], ] for i, dist in enumerate(test_distributions): @@ -487,19 +508,19 @@ def test_preparation_library(self): std = builder.import_library(std_gates) prep = builder.import_library(Prep) - qubits = [f'q[{j}]' for j in range(int(np.ceil(np.log2(len(dist)))))] + qubits = [f"q[{j}]" for j in range(int(np.ceil(np.log2(len(dist)))))] # std.qubit(len(qubits) + 1) # std.bit(len(qubits) + 1) try: prep.prep(qubits, dist) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) full_qasm = builder.build() # Should contain preparation elements - assert 'PREP_' in full_qasm or 'prep' in full_qasm.lower() + assert "PREP_" in full_qasm or "prep" in full_qasm.lower() # Validate QASM # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) @@ -521,16 +542,16 @@ def test_selection_library(self): # std.bit(6) try: - target_qubits = ['q[0]', 'q[1]'] - ancilla_qubits = ['q[2]', 'q[3]'] + target_qubits = ["q[0]", "q[1]"] + ancilla_qubits = ["q[2]", "q[3]"] select.select(target_qubits, ancilla_qubits, operators, mapping) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) - full_qasm = builder.build() + full_qasm = builder.build() # Should contain selection elements - assert 'SEL_' in full_qasm or 'select' in full_qasm.lower() + assert "SEL_" in full_qasm or "select" in full_qasm.lower() # Validate QASM # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) @@ -549,19 +570,21 @@ def test_pauli_operator_library(self): std = builder.import_library(std_gates) pauli = builder.import_library(PauliOperator) - qubits = [f'q[{i}]' for i in range(len(pauli_str))] + qubits = [f"q[{i}]" for i in range(len(pauli_str))] # std.qubit(len(qubits)) # std.bit(len(qubits)) try: pauli.pauli_operator(qubits, pauli_str) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) full_qasm = builder.build() # Should contain the Pauli string name or operations - assert pauli_str in full_qasm or any(p in full_qasm.lower() for p in ['x', 'y', 'z']) + assert pauli_str in full_qasm or any( + p in full_qasm.lower() for p in ["x", "y", "z"] + ) # Validate QASM # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) @@ -581,13 +604,14 @@ def _validate_qasm_with_pyqasm(self, qasm_string): except Exception as e: return False, str(e) + class TestAlgorithmIntegration: """Test algorithm interactions and edge cases.""" def setup_method(self): """Set up test environment.""" self.test_hamiltonians = create_test_hamiltonians(reg_size=3) - self.test_qubits = [f'q[{i}]' for i in range(3)] + self.test_qubits = [f"q[{i}]" for i in range(3)] def test_gqsp_with_all_hamiltonians(self): """Test GQSP works with all Hamiltonian types.""" @@ -599,15 +623,15 @@ def test_gqsp_with_all_hamiltonians(self): gqsp = builder.import_library(GQSP) class Ham(hamiltonian): - def apply(self,*args,**kwargs): - super().apply(.1,*args,**kwargs) + def apply(self, *args, **kwargs): + super().apply(0.1, *args, **kwargs) - def controlled(self,*args,**kwargs): - super().controlled(.1,*args,**kwargs) + def controlled(self, *args, **kwargs): + super().controlled(0.1, *args, **kwargs) try: gqsp.GQSP(self.test_qubits, phases, Ham, depth=1) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) # full_qasm = builder.build() @@ -632,7 +656,7 @@ def test_trotter_with_all_hamiltonian_pairs(self): try: trotter.trot_suz(self.test_qubits, "0.1", ham1, ham2, depth=1) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) # full_qasm = builder.build() @@ -641,7 +665,9 @@ def test_trotter_with_all_hamiltonian_pairs(self): # assert is_valid, f"Trotter+{name1}+{name2} invalid: {error_msg}" except Exception as e: - pytest.fail(f"Trotter integration with {name1}+{name2} failed: {str(e)}") + pytest.fail( + f"Trotter integration with {name1}+{name2} failed: {str(e)}" + ) def test_algorithm_parameter_edge_cases(self): """Test algorithms with edge case parameters.""" @@ -655,8 +681,10 @@ def test_algorithm_parameter_edge_cases(self): try: ham_pair = list(self.test_hamiltonians.values())[:2] - trotter.trot_suz(self.test_qubits, time, ham_pair[0], ham_pair[1], depth=1) - std.measure(self.test_qubits,self.test_qubits) + trotter.trot_suz( + self.test_qubits, time, ham_pair[0], ham_pair[1], depth=1 + ) + std.measure(self.test_qubits, self.test_qubits) full_qasm = builder.build() @@ -676,7 +704,7 @@ def test_algorithm_qubit_scaling(self): for n_qubits in qubit_counts: # Create appropriate Hamiltonians for this qubit count test_hams = create_test_hamiltonians(reg_size=n_qubits) - qubits = [f'q[{i}]' for i in range(n_qubits)] + qubits = [f"q[{i}]" for i in range(n_qubits)] # Test GQSP scaling builder = QasmBuilder(3) @@ -689,14 +717,16 @@ def test_algorithm_qubit_scaling(self): try: phases = [0.1, 0.2, 0.3] # depth=1 hamiltonian = list(test_hams.values())[0] + class Ham(hamiltonian): - def apply(self,*args,**kwargs): - super().apply(.1,*args,**kwargs) + def apply(self, *args, **kwargs): + super().apply(0.1, *args, **kwargs) + + def controlled(self, *args, **kwargs): + super().controlled(0.1, *args, **kwargs) - def controlled(self,*args,**kwargs): - super().controlled(.1,*args,**kwargs) gqsp.GQSP(qubits, phases, Ham, depth=1) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) # full_qasm = builder.build() @@ -720,6 +750,7 @@ def _validate_qasm_with_pyqasm(self, qasm_string): except Exception as e: return False, str(e) + class TestAlgorithmStressTests: """Stress tests for algorithm robustness.""" @@ -736,11 +767,11 @@ def test_complex_algorithm_combinations(self): prep_sel = builder.import_library(PrepSelLibrary) class H1(ham_list[0]): - def apply(self,*args,**kwargs): - super().apply(.1,*args,**kwargs) + def apply(self, *args, **kwargs): + super().apply(0.1, *args, **kwargs) - def controlled(self,*args,**kwargs): - super().controlled(.1,*args,**kwargs) + def controlled(self, *args, **kwargs): + super().controlled(0.1, *args, **kwargs) try: # Apply Trotter decomposition @@ -753,7 +784,7 @@ def controlled(self,*args,**kwargs): test_matrix = np.array([[1, 0], [0, -1]]) prep_sel.prep_select(qubits[6:], test_matrix) - std.measure(qubits,qubits) + std.measure(qubits, qubits) # full_qasm = builder.build() @@ -776,16 +807,16 @@ def test_resource_intensive_algorithms(self): gqsp = builder.import_library(GQSP) class H1(hamiltonian): - def apply(self,*args,**kwargs): - super().apply(.1,*args,**kwargs) + def apply(self, *args, **kwargs): + super().apply(0.1, *args, **kwargs) - def controlled(self,*args,**kwargs): - super().controlled(.1,*args,**kwargs) + def controlled(self, *args, **kwargs): + super().controlled(0.1, *args, **kwargs) try: phases = [0.1 * i for i in range(7)] # depth=3 gqsp.GQSP(reg[:2], phases, H1, depth=3) - std.measure(reg,reg) + std.measure(reg, reg) # full_qasm = builder.build() @@ -811,8 +842,10 @@ def _validate_qasm_with_pyqasm(self, qasm_string): class TestAmplitude: class Za(GateLibrary): """Custom gate: controlled-Z on all qubits except index 2.""" + name = "Z_on_two" reg = [*range(3)] + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -884,6 +917,7 @@ def test_full_algorithm_builds(self): # Validation (commented for now) # res.validate() + if __name__ == "__main__": # Run tests if executed directly pytest.main([__file__, "-v", "--tb=short"]) diff --git a/tests/test_builder_statics.py b/tests/test_builder_statics.py index f81fb85..ce5ec88 100644 --- a/tests/test_builder_statics.py +++ b/tests/test_builder_statics.py @@ -15,7 +15,7 @@ """ Test Algorithms - Semantic Validation -This module tests the implementations of several semi static algorithms which +This module tests the implementations of several semi static algorithms which dont accept a arbitrary oracle/hamiltonian. Tests include: 1. Grovers @@ -35,23 +35,24 @@ from qbraid_algorithms.amplitude_amplification import AALibrary from qbraid_algorithms.embedding import Toeplitz -#package modules +# package modules from qbraid_algorithms.qtran import GateBuilder, GateLibrary, QasmBuilder, std_gates from qbraid_algorithms.rodeo import RodeoLibrary class Za(GateLibrary): """Custom gate: controlled-Z on all qubits except index 2.""" + name = "Z_on_two" reg = [*range(3)] + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.name = f"Z_on_two{len(self.reg)}" names = string.ascii_letters qargs = [ - names[i // len(names)] + names[i % len(names)] - for i in range(len(self.reg)) + names[i // len(names)] + names[i % len(names)] for i in range(len(self.reg)) ] sys = GateBuilder() @@ -89,6 +90,7 @@ def controlled(self, qubits, control): """Controlled version of the custom gate.""" self.controlled_op(self.name, (qubits[-1], [control] + qubits[:-1])) + class TestGrover: def test_full_algorithm_builds(self): """Ensure full algorithm builds and pq.loads() runs.""" @@ -116,11 +118,12 @@ def test_full_algorithm_builds(self): # Validation (commented for now) # res.validate() -class TestToeplitz(): + +class TestToeplitz: def test_full_algorithm_builds(self): """Ensure full algorithm builds and pq.loads() runs.""" - t= np.linspace(0.01, 4*2*np.pi, 8,endpoint=True) - f = np.sin(t)/t + t = np.linspace(0.01, 4 * 2 * np.pi, 8, endpoint=True) + f = np.sin(t) / t # Build algorithm with 3 qubits alg = QasmBuilder(3, 0, version="3") @@ -131,7 +134,7 @@ def test_full_algorithm_builds(self): toeplitz_lib = alg.import_library(Toeplitz) # Add Toeplitz operator - toeplitz_lib.real_toeplitz(reg,f) + toeplitz_lib.real_toeplitz(reg, f) # Build OpenQASM code prog = alg.build() @@ -145,7 +148,8 @@ def test_full_algorithm_builds(self): # Validation (commented for now) # res.validate() -class TestRodeo(): + +class TestRodeo: def test_mcm_builds(self): """Ensure full algorithm builds and pq.loads() runs.""" t = np.linspace(0.01, 4 * 2 * np.pi, 8, endpoint=True) diff --git a/tests/test_cli.py b/tests/test_cli.py index 46eb58f..9d802d6 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -384,7 +384,7 @@ def test_qpe_with_show(runner, temp_dir, temp_path): assert "OPENQASM" in result.stdout -@patch("qbraid_algorithms.cli.generate.qft.generate_subroutine") +@patch("qbraid_algorithms.cli.generate.qft.save_to_qasm") def test_qft_error_handling(mock_generate, runner, temp_dir): """Test QFT error handling.""" mock_generate.side_effect = Exception("Test error") @@ -394,7 +394,7 @@ def test_qft_error_handling(mock_generate, runner, temp_dir): assert result.exit_code == 1 -@patch("qbraid_algorithms.cli.generate.iqft.generate_subroutine") +@patch("qbraid_algorithms.cli.generate.iqft.save_to_qasm") def test_iqft_error_handling(mock_generate, runner, temp_dir): """Test IQFT error handling.""" mock_generate.side_effect = Exception("Test error") @@ -404,7 +404,7 @@ def test_iqft_error_handling(mock_generate, runner, temp_dir): assert result.exit_code == 1 -@patch("qbraid_algorithms.cli.generate.bv.generate_subroutine") +@patch("qbraid_algorithms.cli.generate.bv.save_to_qasm") def test_bernvaz_error_handling(mock_generate, runner, temp_dir): """Test Bernstein-Vazirani error handling.""" mock_generate.side_effect = Exception("Test error") @@ -416,7 +416,7 @@ def test_bernvaz_error_handling(mock_generate, runner, temp_dir): assert "Generating Bernstein-Vazirani circuit" in result.stdout -@patch("qbraid_algorithms.cli.generate.qpe.generate_subroutine") +@patch("qbraid_algorithms.cli.generate.qpe.save_to_qasm") def test_qpe_error_handling(mock_generate, runner, temp_dir, temp_path): """Test QPE error handling.""" mock_generate.side_effect = Exception("Test error") diff --git a/tests/test_iqft.py b/tests/test_iqft.py index 2bea1e9..95b6f51 100644 --- a/tests/test_iqft.py +++ b/tests/test_iqft.py @@ -45,9 +45,9 @@ def _run_circuit_and_check_counts( assert lower <= count <= upper -def test_load_program(): - """Test that load_program correctly returns a pyqasm module object.""" - iqft_module = iqft.load_program(3) +def test_generate_program(): + """Test that generate_program correctly returns a pyqasm module object.""" + iqft_module = iqft.generate_program(3) assert isinstance(iqft_module, QasmModule) assert iqft_module.num_qubits == 3 @@ -63,7 +63,7 @@ def test_valid_circuit_0(): iqft_file.unlink() # generate single qubit QFT circuit - iqft.generate_subroutine(1, path=RESOURCES_DIR, quiet=True) + iqft.save_to_qasm(1, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/iqft_0.qasm") # delete the created subroutine file (RESOURCES_DIR / "iqft.qasm").unlink() @@ -95,7 +95,7 @@ def test_valid_circuit_1(): iqft_file.unlink() # generate single qubit QFT circuit - iqft.generate_subroutine(1, path=RESOURCES_DIR, quiet=True) + iqft.save_to_qasm(1, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/iqft_1.qasm") # delete the created subroutine file (RESOURCES_DIR / "iqft.qasm").unlink() @@ -127,7 +127,7 @@ def test_valid_circuit_00(): iqft_file.unlink() # generate two qubit QFT circuit - iqft.generate_subroutine(2, path=RESOURCES_DIR, quiet=True) + iqft.save_to_qasm(2, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/iqft_00.qasm") # delete the created subroutine file (RESOURCES_DIR / "iqft.qasm").unlink() @@ -159,7 +159,7 @@ def test_valid_circuit_2qubit_superposition(): iqft_file.unlink() # generate two qubit QFT circuit - iqft.generate_subroutine(2, path=RESOURCES_DIR, quiet=True) + iqft.save_to_qasm(2, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/iqft_2qubit_superposn.qasm") # delete the created subroutine file (RESOURCES_DIR / "iqft.qasm").unlink() @@ -190,7 +190,7 @@ def test_valid_circuit_000(): iqft_file.unlink() # generate three qubit QFT circuit - iqft.generate_subroutine(3, path=RESOURCES_DIR, quiet=True) + iqft.save_to_qasm(3, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/iqft_000.qasm") # delete the created subroutine file (RESOURCES_DIR / "iqft.qasm").unlink() @@ -231,7 +231,7 @@ def test_valid_circuit_010(): iqft_file.unlink() # generate three qubit QFT circuit - iqft.generate_subroutine(3, path=RESOURCES_DIR, quiet=True) + iqft.save_to_qasm(3, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/iqft_010.qasm") # delete the create # d subroutine file @@ -273,7 +273,7 @@ def test_valid_circuit_001(): iqft_file.unlink() # generate three qubit QFT circuit - iqft.generate_subroutine(3, path=RESOURCES_DIR, quiet=True) + iqft.save_to_qasm(3, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/iqft_001.qasm") # delete the created subroutine file (RESOURCES_DIR / "iqft.qasm").unlink() diff --git a/tests/test_qasmbuilder.py b/tests/test_qasmbuilder.py index 3320966..9e02c48 100644 --- a/tests/test_qasmbuilder.py +++ b/tests/test_qasmbuilder.py @@ -40,17 +40,20 @@ try: import pyqasm as pq + PYQASM_AVAILABLE = True except ImportError: PYQASM_AVAILABLE = False pytest.skip("pyqasm not available", allow_module_level=True) + class TestQASMBuilderBasic: """Test basic QASM generation with exact string matching.""" + def test_simple_gate_sequence(self): """Test exact QASM output for simple gate sequence.""" n = 3 - builder = QasmBuilder(n,version=3) + builder = QasmBuilder(n, version=3) std = builder.import_library(std_gates) qubits = [*range(n)] @@ -58,33 +61,33 @@ def test_simple_gate_sequence(self): std.h(qubits[0]) std.cnot(qubits[0], qubits[1]) std.x(qubits[2]) - std.measure(qubits,qubits) + std.measure(qubits, qubits) program = builder.build() # Expected QASM output (adjust based on your actual format) expected_lines = [ "OPENQASM 3;", - "include \"stdgates.inc\";", + 'include "stdgates.inc";', f"qubit[{n}] qb;", f"bit[{n}] cb;", "h qb[0];", "cnot qb[0], qb[1];", "x qb[2];", - "cb[{0, 1, 2}] = measure qb[{0, 1, 2}];" + "cb[{0, 1, 2}] = measure qb[{0, 1, 2}];", ] # Validate structure assert isinstance(program, str) # Basic content validation (exact matching would depend on your format) - program_lines = [line.strip() for line in program.split('\n') if line.strip()] + program_lines = [line.strip() for line in program.split("\n") if line.strip()] assert len(program_lines) > 0 # Check for key elements - assert any('h' in line for line in program_lines) - assert any('cnot' in line for line in program_lines) - assert any('measure' in line for line in program_lines) + assert any("h" in line for line in program_lines) + assert any("cnot" in line for line in program_lines) + assert any("measure" in line for line in program_lines) stable = True try: @@ -101,8 +104,8 @@ def test_parameterized_gate_definition(self): std = builder.import_library(std_gates) gate_name = "test_rotation" - qargs = ['a', 'b'] - params = ['theta', 'phi'] + qargs = ["a", "b"] + params = ["theta", "phi"] std.begin_gate(gate_name, qargs, params=params) std.rx(params[0], qargs[0]) @@ -123,9 +126,9 @@ def test_parameterized_gate_definition(self): assert all(qarg in gate_def for qarg in qargs) # Check for gate operations - assert 'rx' in gate_def - assert 'ry' in gate_def - assert 'cnot' in gate_def + assert "rx" in gate_def + assert "ry" in gate_def + assert "cnot" in gate_def def test_subroutine_generation(self): """Test QASM subroutine generation.""" @@ -133,7 +136,7 @@ def test_subroutine_generation(self): std = builder.import_library(std_gates) subroutine_name = "test_subroutine" - params = ['qubit[3] qb', 'float time', 'int depth'] + params = ["qubit[3] qb", "float time", "int depth"] std.begin_subroutine(subroutine_name, params) std.begin_if("depth > 0") @@ -148,8 +151,8 @@ def test_subroutine_generation(self): assert subroutine_name in program subroutine_def = program - assert 'def' in subroutine_def or 'subroutine' in subroutine_def - assert 'if' in subroutine_def + assert "def" in subroutine_def or "subroutine" in subroutine_def + assert "if" in subroutine_def assert all(param.split()[-1] in subroutine_def for param in params) def test_conditional_and_loops(self): @@ -170,9 +173,9 @@ def test_conditional_and_loops(self): program, imports, defs = builder.build() # Check for control flow structures - assert 'if' in program - assert 'for' in program - assert 'h' in program + assert "if" in program + assert "for" in program + assert "h" in program def test_ancilla_claiming(self): sys = QasmBuilder(3) @@ -193,7 +196,9 @@ def validate_qasm_with_pyqasm(self, qasm_string): try: # Create temporary file for pyqasm validation - with tempfile.NamedTemporaryFile(mode='w', suffix='.qasm', delete=False) as f: + with tempfile.NamedTemporaryFile( + mode="w", suffix=".qasm", delete=False + ) as f: f.write(qasm_string) f.flush() temp_path = f.name @@ -216,6 +221,7 @@ def validate_qasm_with_pyqasm(self, qasm_string): except Exception as e: return False, f"Validation setup failed: {str(e)}" + class TestHamiltonianInterface: """Test Hamiltonian interface for correct QASM generation.""" @@ -230,11 +236,11 @@ def test_hamiltonian_initialization(self): # Check required attributes exist sys = GateBuilder() H = sys.import_library(ham) - assert hasattr(H, 'name') - assert hasattr(H, 'apply') - assert hasattr(H, 'controlled') - assert hasattr(H, 'gate_defs') - assert hasattr(H, 'gate_ref') + assert hasattr(H, "name") + assert hasattr(H, "apply") + assert hasattr(H, "controlled") + assert hasattr(H, "gate_defs") + assert hasattr(H, "gate_ref") # Check name is reasonable assert isinstance(H.name, str) @@ -255,9 +261,9 @@ def test_hamiltonian_apply_method(self): # Test apply method try: ham_lib.apply("0.5", self.test_qubits) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) - program= builder.build() + program = builder.build() # Validate basic structure assert isinstance(program, str) @@ -266,7 +272,9 @@ def test_hamiltonian_apply_method(self): # Test QASM validity with pyqasm is_valid, error_msg = self._validate_qasm_with_pyqasm(program) - assert is_valid, f"Invalid QASM for {name}: {error_msg}\nQASM:\n{program}" + assert ( + is_valid + ), f"Invalid QASM for {name}: {error_msg}\nQASM:\n{program}" except Exception as e: pytest.fail(f"Failed to apply Hamiltonian {name}: {str(e)}") @@ -290,7 +298,7 @@ def test_hamiltonian_controlled_method(self): target_qubits = self.test_qubits ham_lib.controlled("0.3", target_qubits, control_qubit) - std.measure(self.test_qubits+anc_q,self.test_qubits+anc_c) + std.measure(self.test_qubits + anc_q, self.test_qubits + anc_c) program = builder.build() # Validate structure @@ -300,7 +308,9 @@ def test_hamiltonian_controlled_method(self): full_qasm = program is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) - assert is_valid, f"Invalid controlled QASM for {name}: {error_msg}\nQASM:\n{full_qasm}" + assert ( + is_valid + ), f"Invalid controlled QASM for {name}: {error_msg}\nQASM:\n{full_qasm}" except Exception as e: pytest.fail(f"Failed to apply controlled Hamiltonian {name}: {str(e)}") @@ -315,7 +325,7 @@ def test_hamiltonian_parameter_types(self): ham_lib = builder.import_library(ham) try: ham_lib.apply(time_param, self.test_qubits) - std.measure(self.test_qubits,self.test_qubits) + std.measure(self.test_qubits, self.test_qubits) program = builder.build() @@ -323,17 +333,21 @@ def test_hamiltonian_parameter_types(self): full_qasm = program # Basic validation - parameter should appear somewhere - if not any(char.isalpha() for char in time_param): # Numeric parameter + if not any( + char.isalpha() for char in time_param + ): # Numeric parameter # For numeric parameters, check they're used assert len(full_qasm) > 0 # else: # Symbolic parameter - # For symbolic parameters, they should appear in gate definitions - # assert any(time_param.replace('*', '').replace('/', '').replace('pi', '') in gate_def - # for gate_def in defs.values() if gate_def) + # For symbolic parameters, they should appear in gate definitions + # assert any(time_param.replace('*', '').replace('/', '').replace('pi', '') in gate_def + # for gate_def in defs.values() if gate_def) except Exception as e: # Some parameter types might not be supported - that's OK if "parameter" not in str(e).lower(): - pytest.fail(f"Unexpected error with {name} and parameter {time_param}: {e}") + pytest.fail( + f"Unexpected error with {name} and parameter {time_param}: {e}" + ) def _validate_qasm_with_pyqasm(self, qasm_string): """Helper method to validate QASM using pyqasm.""" @@ -349,20 +363,24 @@ def _validate_qasm_with_pyqasm(self, qasm_string): except Exception as e: return False, str(e) + class TestQASMStability: """Test QASM output stability across runs.""" + def test_deterministic_output(self): """Test that identical inputs produce identical QASM output.""" + def create_test_program(): builder = GateBuilder() std = builder.import_library(std_gates) - std.h('q[0]') - std.cnot('q[0]', 'q[1]') - std.cnot('q[1]', 'q[2]') + std.h("q[0]") + std.cnot("q[0]", "q[1]") + std.cnot("q[1]", "q[2]") # std.measure([0],[1]) return builder.build() + # Generate the same program multiple times results = [create_test_program() for _ in range(5)] # All results should be identical @@ -375,7 +393,7 @@ def create_test_program(): def test_hamiltonian_stability(self): """Test that Hamiltonian QASM generation is stable.""" hamiltonians = create_test_hamiltonians(reg_size=3) - reg= [*range(3)] + reg = [*range(3)] # Test each Hamiltonian multiple times for name, ham_class in hamiltonians.items(): results = [] @@ -384,21 +402,27 @@ def test_hamiltonian_stability(self): # Create fresh instances class test_ham(ham_class): pass + builder = QasmBuilder(len(reg)) std = builder.import_library(std_gates) ham_lib = builder.import_library(test_ham) - ham_lib.apply("0.1", ['qb[0]', 'qb[1]', 'qb[2]']) - std.measure(reg,reg) + ham_lib.apply("0.1", ["qb[0]", "qb[1]", "qb[2]"]) + std.measure(reg, reg) results.append(builder.build()) # All results for this Hamiltonian should be identical first_result = results[0] for i, result in enumerate(results[1:], 1): - assert result[0] == first_result[0], f"Hamiltonian {name} program differs at run {i}" + assert ( + result[0] == first_result[0] + ), f"Hamiltonian {name} program differs at run {i}" # Gate definitions should be the same - assert result[2] == first_result[2], f"Hamiltonian {name} definitions differ at run {i}" + assert ( + result[2] == first_result[2] + ), f"Hamiltonian {name} definitions differ at run {i}" + if __name__ == "__main__": # Run tests if executed directly diff --git a/tests/test_qft.py b/tests/test_qft.py index f0e49ff..eca3f8c 100644 --- a/tests/test_qft.py +++ b/tests/test_qft.py @@ -45,15 +45,15 @@ def _run_circuit_and_check_counts( assert lower <= count <= upper -def test_load_program(): - """Test that load_program correctly returns a pyqasm module object.""" - qft_module = qft.load_program(3) +def test_generate_program(): + """Test that generate_program correctly returns a pyqasm module object.""" + qft_module = qft.generate_program(3) assert isinstance(qft_module, QasmModule) assert qft_module.num_qubits == 3 -def test_generate_subroutine(): - """Placeholder test for QFT generate_subroutine (to be implemented).""" +def test_save_to_qasm(): + """Placeholder test for QFT save_to_qasm (to be implemented).""" # TODO: Implement this test assert True # Placeholder assertion @@ -68,7 +68,7 @@ def test_valid_circuit_0(): # Single qubit QFT circuit should just be H gate device = LocalDevice() # generate single qubit QFT circuit - qft.generate_subroutine(1, path=RESOURCES_DIR, quiet=True) + qft.save_to_qasm(1, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/qft_0.qasm") # delete the created subroutine file (RESOURCES_DIR / "qft.qasm").unlink() @@ -99,7 +99,7 @@ def test_valid_circuit_1(): # Single qubit QFT circuit should just be H gate device = LocalDevice() # generate single qubit QFT circuit - qft.generate_subroutine(1, path=RESOURCES_DIR, quiet=True) + qft.save_to_qasm(1, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/qft_1.qasm") # delete the created subroutine file (RESOURCES_DIR / "qft.qasm").unlink() @@ -125,7 +125,7 @@ def test_valid_circuit_00(): # we want to take in some binary number as a state - ie |00> device = LocalDevice() # generate two qubit QFT circuit - qft.generate_subroutine(2, path=RESOURCES_DIR, quiet=True) + qft.save_to_qasm(2, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/qft_00.qasm") # delete the created subroutine file (RESOURCES_DIR / "qft.qasm").unlink() @@ -151,7 +151,7 @@ def test_valid_circuit_2qubit_superposition(): # we want to take in some binary number as a state - ie 2 = |10> device = LocalDevice() # generate two qubit QFT circuit - qft.generate_subroutine(2, path=RESOURCES_DIR, quiet=True) + qft.save_to_qasm(2, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/qft_2qubit_superposn.qasm") # delete the created subroutine file (RESOURCES_DIR / "qft.qasm").unlink() @@ -177,7 +177,7 @@ def test_valid_circuit_01(): # we want to take in some binary number as a state - ie |01> device = LocalDevice() # generate two qubit QFT circuit - qft.generate_subroutine(2, path=RESOURCES_DIR, quiet=True) + qft.save_to_qasm(2, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/qft_01.qasm") # delete the created subroutine file (RESOURCES_DIR / "qft.qasm").unlink() @@ -203,7 +203,7 @@ def test_valid_circuit_000(): # we want to take in some binary number as a state - ie |000> device = LocalDevice() # generate three qubit QFT circuit - qft.generate_subroutine(3, path=RESOURCES_DIR, quiet=True) + qft.save_to_qasm(3, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/qft_000.qasm") # delete the created subroutine file (RESOURCES_DIR / "qft.qasm").unlink() @@ -239,7 +239,7 @@ def test_valid_circuit_010(): # we want to take in some binary number as a state - ie |010> device = LocalDevice() # generate three qubit QFT circuit - qft.generate_subroutine(3, path=RESOURCES_DIR, quiet=True) + qft.save_to_qasm(3, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/qft_010.qasm") # delete the created subroutine file (RESOURCES_DIR / "qft.qasm").unlink() @@ -275,7 +275,7 @@ def test_valid_circuit_001(): # we want to take in some binary number as a state - ie 3 = |011> device = LocalDevice() # generate two qubit QFT circuit - qft.generate_subroutine(3, path=RESOURCES_DIR, quiet=True) + qft.save_to_qasm(3, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/qft_001.qasm") # delete the created subroutine file (RESOURCES_DIR / "qft.qasm").unlink() @@ -320,8 +320,8 @@ def test_undo_iqft_00(): iqft_file = RESOURCES_DIR / "iqft.qasm" if iqft_file.exists(): iqft_file.unlink() - qft.generate_subroutine(2, path=RESOURCES_DIR, quiet=True) - iqft.generate_subroutine(2, path=RESOURCES_DIR, quiet=True) + qft.save_to_qasm(2, path=RESOURCES_DIR, quiet=True) + iqft.save_to_qasm(2, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/undo_iqft_00.qasm") (RESOURCES_DIR / "qft.qasm").unlink() (RESOURCES_DIR / "iqft.qasm").unlink() @@ -354,8 +354,8 @@ def test_undo_iqft_superposition(): iqft_file = RESOURCES_DIR / "iqft.qasm" if iqft_file.exists(): iqft_file.unlink() - qft.generate_subroutine(2, path=RESOURCES_DIR, quiet=True) - iqft.generate_subroutine(2, path=RESOURCES_DIR, quiet=True) + qft.save_to_qasm(2, path=RESOURCES_DIR, quiet=True) + iqft.save_to_qasm(2, path=RESOURCES_DIR, quiet=True) program = pyqasm.load(f"{RESOURCES_DIR}/undo_iqft_00_superposition.qasm") (RESOURCES_DIR / "qft.qasm").unlink() (RESOURCES_DIR / "iqft.qasm").unlink() diff --git a/tests/test_qpe.py b/tests/test_qpe.py index 5fe6172..5745ea0 100644 --- a/tests/test_qpe.py +++ b/tests/test_qpe.py @@ -32,9 +32,9 @@ RESOURCE_DIR = Path(__file__).parent / "resources" / "qpe" -def test_load_program(): - """Test that load_program correctly returns a pyqasm module object.""" - qpe_module = qpe.load_program( +def test_generate_program(): + """Test that generate_program correctly returns a pyqasm module object.""" + qpe_module = qpe.generate_program( unitary_filepath=f"{RESOURCE_DIR}/t.qasm", psi_filepath=f"{RESOURCE_DIR}/prepare_state.qasm", num_qubits=3, @@ -49,7 +49,7 @@ def test_valid_circuit_r_3pi4(): if qpe_file.exists(): qpe_file.unlink() device = LocalDevice() - qpe.generate_subroutine( + qpe.save_to_qasm( unitary_filepath=f"{RESOURCE_DIR}/r_3pi4.qasm", num_qubits=3, quiet=True, @@ -78,7 +78,7 @@ def test_valid_circuit_t(): if iqft_file.exists(): iqft_file.unlink() device = LocalDevice() - qpe.generate_subroutine( + qpe.save_to_qasm( unitary_filepath=f"{RESOURCE_DIR}/t.qasm", num_qubits=3, quiet=True, @@ -109,7 +109,7 @@ def test_valid_circuit_z(): if iqft_file.exists(): iqft_file.unlink() device = LocalDevice() - qpe.generate_subroutine( + qpe.save_to_qasm( unitary_filepath=f"{RESOURCE_DIR}/z.qasm", num_qubits=3, quiet=True, @@ -130,9 +130,9 @@ def test_valid_circuit_z(): assert result == expected -def test_load_program_without_measurement(): - """Test load_program with include_measurement=False.""" - qpe_module = qpe.load_program( +def test_generate_program_without_measurement(): + """Test generate_program with include_measurement=False.""" + qpe_module = qpe.generate_program( unitary_filepath=f"{RESOURCE_DIR}/t.qasm", psi_filepath=f"{RESOURCE_DIR}/prepare_state.qasm", num_qubits=3, @@ -141,14 +141,14 @@ def test_load_program_without_measurement(): assert isinstance(qpe_module, QasmModule) -def test_generate_subroutine_default_path(): - """Test generate_subroutine with default path (current working directory).""" +def test_save_to_qasm_default_path(): + """Test save_to_qasm with default path (current working directory).""" original_cwd = os.getcwd() # Create temporary directory and change to it with tempfile.TemporaryDirectory() as test_dir: os.chdir(test_dir) try: - qpe.generate_subroutine( + qpe.save_to_qasm( unitary_filepath=f"{RESOURCE_DIR}/t.qasm", num_qubits=3, quiet=True, @@ -164,14 +164,14 @@ def test_generate_subroutine_default_path(): os.chdir(original_cwd) -def test_generate_subroutine_verbose(): - """Test generate_subroutine with verbose output (quiet=False).""" +def test_save_to_qasm_verbose(): + """Test save_to_qasm with verbose output (quiet=False).""" with tempfile.TemporaryDirectory() as test_dir: # Capture stdout captured_output = io.StringIO() sys.stdout = captured_output try: - qpe.generate_subroutine( + qpe.save_to_qasm( unitary_filepath=f"{RESOURCE_DIR}/t.qasm", num_qubits=3, quiet=False, From b146c8a678986a3d9389b0ad8bcd90aa115f0610 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Fri, 26 Sep 2025 16:45:12 -0500 Subject: [PATCH 4/5] Refactor documentation and improve clarity across multiple modules --- .../bernstein_vazirani/bernvaz.py | 5 +- qbraid_algorithms/iqft/__init__.py | 2 +- qbraid_algorithms/qft/qft.py | 16 +--- qbraid_algorithms/qft/qft_lib.py | 2 +- qbraid_algorithms/qpe/phase_est.py | 4 +- qbraid_algorithms/qtran/gate_library.py | 38 +++++---- qbraid_algorithms/qtran/module_loader.py | 5 +- qbraid_algorithms/qtran/qasm_builder.py | 79 ++++++++++--------- 8 files changed, 75 insertions(+), 76 deletions(-) diff --git a/qbraid_algorithms/bernstein_vazirani/bernvaz.py b/qbraid_algorithms/bernstein_vazirani/bernvaz.py index 45bc73a..a2eb6a6 100644 --- a/qbraid_algorithms/bernstein_vazirani/bernvaz.py +++ b/qbraid_algorithms/bernstein_vazirani/bernvaz.py @@ -33,11 +33,10 @@ def generate_program(bitstring: Union[str, list[int]]) -> QasmModule: Load the Bernstein-Vazirani circuit as a pyqasm module. Args: - bitstring (Union[str, list[int]]): The hidden bitstring `s` as a string of '0's - and '1's + bitstring (Union[str, list[int]]): The hidden bitstring `s` as a string of '0's and '1's Returns: - (PyQasm Module) pyqasm module containing the Bernstein-Vazirani circuit + PyQASM module containing the Bernstein-Vazirani circuit """ # Load the Bernstein-Vazirani QASM files into a staging directory diff --git a/qbraid_algorithms/iqft/__init__.py b/qbraid_algorithms/iqft/__init__.py index 2d771c2..d0c0fe5 100644 --- a/qbraid_algorithms/iqft/__init__.py +++ b/qbraid_algorithms/iqft/__init__.py @@ -53,7 +53,7 @@ 1 & 1 & 1 & \\cdots & 1 \\\\ 1 & \\omega_n^{-1} & \\omega_n^{-2} & \\cdots & \\omega_n^{-(2^n-1)} \\\\ \\vdots & \\vdots & \\vdots & \\ddots & \\vdots \\\\ - 1 & \\omega_n^{-(2^n-1)} & \\omega_n^{-2(2^n-1)} & \\cdots & \\omega_n^{-(2^n-1)^2} + 1 & \\omega_n^{-(2^n-1)} & \\omega_n^{-2(2^n-1)} & \\cdots & \\omega_n^{-(2^n-1)(2^n-1)} \\end{pmatrix}` diff --git a/qbraid_algorithms/qft/qft.py b/qbraid_algorithms/qft/qft.py index 57a2573..8dc2508 100644 --- a/qbraid_algorithms/qft/qft.py +++ b/qbraid_algorithms/qft/qft.py @@ -13,21 +13,8 @@ # limitations under the License. """ -Quantum Fourier Transform (QFT) Algorithm Implementation +Quantum Fourier Transform (QFT) Algorithm Interface -This module provides a complete implementation of the Quantum Fourier Transform, -a fundamental building block for many quantum algorithms. - -The QFT transforms quantum states from the computational basis to the Fourier basis -by applying Hadamard gates and controlled phase rotations. It's essential for -algorithms like Shor's algorithm and quantum phase estimation, providing the -quantum analogue of the classical discrete Fourier transform. - -Formulation: - The QFT transforms a quantum state |j⟩ to |ψ⟩ = 1/√N Σₖ₌₀^(N-1) e^(2πijk/N)|k⟩ - where N = 2ⁿ for n qubits. The circuit implements this using Hadamard gates H - and controlled phase rotations R_k with phase φ = 2π/2^k. The total circuit - depth is O(n²) with O(n²) gates. """ import os import shutil @@ -45,6 +32,7 @@ def generate_program(num_qubits: int) -> QasmModule: """ Load the Quantum Fourier Transform circuit as a pyqasm module. + Args: num_qubits (int): The number of qubits for the QFT. diff --git a/qbraid_algorithms/qft/qft_lib.py b/qbraid_algorithms/qft/qft_lib.py index 3fada27..c01065b 100644 --- a/qbraid_algorithms/qft/qft_lib.py +++ b/qbraid_algorithms/qft/qft_lib.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Quantum Fourier Transform (QFT) Algorithm Implementation +Quantum Fourier Transform (QFT) Library Implementation """ diff --git a/qbraid_algorithms/qpe/phase_est.py b/qbraid_algorithms/qpe/phase_est.py index 0945a4e..8fa2340 100644 --- a/qbraid_algorithms/qpe/phase_est.py +++ b/qbraid_algorithms/qpe/phase_est.py @@ -32,8 +32,8 @@ class PhaseEstimationLibrary(GateLibrary): """ Library to implement phase estimation circuits directly related to classical - phase estimation algorithms. Iterative phase estimation will be supported via - the Rodeo package. This library supports both static and time-dependent Hamiltonians. + phase estimation algorithms. + """ def __init__(self, *args, **kwargs): diff --git a/qbraid_algorithms/qtran/gate_library.py b/qbraid_algorithms/qtran/gate_library.py index 53629e7..ba114b4 100644 --- a/qbraid_algorithms/qtran/gate_library.py +++ b/qbraid_algorithms/qtran/gate_library.py @@ -22,11 +22,12 @@ built to inject definitions into whatever FileBuilder class it is connected to. Key (Base) Features: -- Gate application with controls and phases -- Measurements and classical bit operations -- Control flow (loops, conditionals) -- Gate and subroutine definitions -- Code generation and scope management + + - Gate application with controls and phases + - Measurements and classical bit operations + - Control flow (loops, conditionals) + - Gate and subroutine definitions + - Code generation and scope management Class Extensions: - std_gates @@ -41,11 +42,12 @@ class GateLibrary: Core class for quantum gate operations and circuit building. Provides fundamental operations for: - - Gate application with controls and phases - - Measurements and classical bit operations - - Control flow (loops, conditionals) - - Gate and subroutine definitions - - Code generation and scope management + + - Gate application with controls and phases + - Measurements and classical bit operations + - Control flow (loops, conditionals) + - Gate and subroutine definitions + - Code generation and scope management """ @@ -210,10 +212,11 @@ def begin_loop(self, iterator, ident: str = "i"): LOOPS Start a loop block with various iteration patterns: - - int: for int i in [0:n] - - (start, end): for int i in [start:end] - - (start, step, end): for int i in [start:end:step] - - string: custom loop syntax + + - int: for int i in [0:n] + - (start, end): for int i in [start:end] + - (start, step, end): for int i in [start:end:step] + - string: custom loop syntax Args: iterator: Loop specification (int, tuple, or string) @@ -432,9 +435,10 @@ class std_gates(GateLibrary): Implementation of std_lib quantum gates following OpenQASM 3.0 standards. Available Gates: - - Single-qubit: phase, x, y, z, h, s, sdg, sx - - Two-qubit: cx, cy, cz, cp, crx, cry, crz, swap - - Multi-qubit: ccx (Toffoli), cswap (Fredkin) + + - Single-qubit: phase, x, y, z, h, s, sdg, sx + - Two-qubit: cx, cy, cz, cp, crx, cry, crz, swap + - Multi-qubit: ccx (Toffoli), cswap (Fredkin) """ # Standard gate set from OpenQASM 3.0 specification diff --git a/qbraid_algorithms/qtran/module_loader.py b/qbraid_algorithms/qtran/module_loader.py index 1cfcde8..4346b91 100644 --- a/qbraid_algorithms/qtran/module_loader.py +++ b/qbraid_algorithms/qtran/module_loader.py @@ -38,8 +38,9 @@ def qasm_pipe(func: Callable) -> Callable: then writes the function's (file_name, program_string) output to a .qasm file. The decorated function should: - 1. Accept 'path' and 'quiet' as keyword arguments - 2. Return a tuple of (file_name, program_string) + + 1. Accept 'path' and 'quiet' as keyword arguments + 2. Return a tuple of (file_name, program_string) The decorator will create a file named "{file_name}.qasm" and write the program_string to it. """ diff --git a/qbraid_algorithms/qtran/qasm_builder.py b/qbraid_algorithms/qtran/qasm_builder.py index a609a0c..d4ff403 100644 --- a/qbraid_algorithms/qtran/qasm_builder.py +++ b/qbraid_algorithms/qtran/qasm_builder.py @@ -22,16 +22,17 @@ structure/semantics requirements unique to each file Key Features: -- Automatic scope and indentation management -- Library import and gate definition tracking -- Multiple output formats (QASM circuits, includes, gate definitions) -- Resource allocation for qubits and classical bits -- Extensible design for custom quantum libraries + + - Automatic scope and indentation management + - Library import and gate definition tracking + - Multiple output formats (QASM circuits, includes, gate definitions) + - Resource allocation for qubits and classical bits + - Extensible design for custom quantum libraries Class Extensions: -- GateBuilder -- QasmBuilder -- IncludeBuilder + - GateBuilder + - QasmBuilder + - IncludeBuilder """ @@ -44,11 +45,12 @@ class FileBuilder: for specialized builders that generate different types of OpenQASM output. The FileBuilder maintains several key data structures: - - imports: List of library files to include - - gate_defs: Dictionary mapping gate names to their definitions - - gate_refs: List of available gate names for validation - - program: Accumulated program code with proper indentation - - scope: Current nesting level for proper code formatting + + - imports: List of library files to include + - gate_defs: Dictionary mapping gate names to their definitions + - gate_refs: List of available gate names for validation + - program: Accumulated program code with proper indentation + - scope: Current nesting level for proper code formatting """ def __init__(self): @@ -56,11 +58,12 @@ def __init__(self): Initialize the base file builder with empty data structures. Sets up the foundational components needed for code generation: - - Empty import list for library dependencies - - Empty gate definitions dictionary for custom gates - - Empty gate references list for scope validation - - Empty program string for accumulating generated code - - Zero scope level for proper indentation tracking + + - Empty import list for library dependencies + - Empty gate definitions dictionary for custom gates + - Empty gate references list for scope validation + - Empty program string for accumulating generated code + - Zero scope level for proper indentation tracking """ self.imports = [] # List of library names to import (e.g., "std_gates.inc") self.gate_defs = {} # Dictionary mapping gate names to definition strings @@ -125,9 +128,10 @@ class GateBuilder(FileBuilder): complete circuit structure. Use cases: - - Creating custom gate libraries - - Generating reusable quantum subroutines - - Building modular quantum components + + - Creating custom gate libraries + - Generating reusable quantum subroutines + - Building modular quantum components """ def import_library(self, lib_class, annotated=False): @@ -168,12 +172,13 @@ class QasmBuilder(FileBuilder): resource allocation and generates standards-compliant OpenQASM code. Features: - - Automatic OpenQASM version header generation - - Qubit and classical bit resource management - - Dynamic resource allocation with claim methods - - Complete circuit structure generation - - Library import management - - Gate definition embedding + + - Automatic OpenQASM version header generation + - Qubit and classical bit resource management + - Dynamic resource allocation with claim methods + - Complete circuit structure generation + - Library import management + - Gate definition embedding """ def __init__(self, qubits, clbits=None, version=3): @@ -256,11 +261,12 @@ def build(self): Generate the complete OpenQASM circuit code. Assembles all components into a valid OpenQASM program including: - 1. Version header (OPENQASM 3;) - 2. Include statements for imported libraries - 3. Qubit and classical bit declarations - 4. Custom gate definitions - 5. Main program code + + 1. Version header (OPENQASM 3;) + 2. Include statements for imported libraries + 3. Qubit and classical bit declarations + 4. Custom gate definitions + 5. Main program code Returns: str: Complete OpenQASM program ready for execution @@ -319,10 +325,11 @@ class IncludeBuilder(FileBuilder): subroutines but do not include qubit declarations or main program logic. Include files are useful for: - - Sharing gate definitions across multiple circuits - - Creating domain-specific gate libraries - - Modular quantum program development - - Standardizing common quantum operations + + - Sharing gate definitions across multiple circuits + - Creating domain-specific gate libraries + - Modular quantum program development + - Standardizing common quantum operations """ def build(self): From 5749eb4c93cc14939764892552836522f3d06411 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Wed, 15 Oct 2025 21:43:06 -0500 Subject: [PATCH 5/5] Update Jupyter notebooks and improve HHL algorithm implementation --- examples/QPE/qpe.ipynb | 1190 ++++++++--------- examples/bells_inequality.ipynb | 6 +- examples/bernstein_vazirani.ipynb | 1010 +++++++------- ...mo_hamiltonians.ipynb => evolutions.ipynb} | 173 +-- examples/hhl.ipynb | 568 ++++++++ examples/qft.ipynb | 1013 +++++++------- .../{demo_qasmbuilder.ipynb => qtran.ipynb} | 47 +- qbraid_algorithms/hhl/hhl.py | 61 +- qbraid_algorithms/qpe/phase_est.py | 5 + qbraid_algorithms/qtran/qasm_builder.py | 2 + 10 files changed, 2390 insertions(+), 1685 deletions(-) rename examples/{demo_hamiltonians.ipynb => evolutions.ipynb} (93%) create mode 100644 examples/hhl.ipynb rename examples/{demo_qasmbuilder.ipynb => qtran.ipynb} (96%) diff --git a/examples/QPE/qpe.ipynb b/examples/QPE/qpe.ipynb index 5083079..e1d5706 100644 --- a/examples/QPE/qpe.ipynb +++ b/examples/QPE/qpe.ipynb @@ -1,597 +1,597 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": 69, - "id": "a42257b1", - "metadata": {}, - "outputs": [], - "source": [ - "import sys\n", - "import os\n", - "\n", - "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '../..')))" - ] - }, - { - "cell_type": "markdown", - "id": "405f6563", - "metadata": {}, - "source": [ - "## Quantum Phase Estimation (QPE)\n", - "#### In this tutorial, we will demonstrate the capabilities of qBraid Algorithms Quantum Phase Estimation Module\n", - "Begin by importing the module from qBraid Algorithms library" - ] - }, - { - "cell_type": "code", - "execution_count": 70, - "id": "0a281dc2", - "metadata": {}, - "outputs": [], - "source": [ - "import pyqasm\n", - "from qbraid_algorithms import qpe" - ] - }, - { - "cell_type": "markdown", - "id": "727d132b", - "metadata": {}, - "source": [ - "To load a full QPE algorithm circuit as a PyQASM module, pass a path to the unitary U to the `generate_program()` method - this shoulds be defined as a valid custom QASM gate. Additionally, pass a custom gate that prepares your eigenstate. For example, to set the eigenstate to |1$\\rangle$, simply define and pass the following .qasm file:\n", - "```\n", - "OPENQASM 3.0;\n", - "include \"stdgates.inc\";\n", - "\n", - "gate prep q {\n", - " x q;\n", - "}\n", - "```\n", - "Note, the gate names in both cases can be anything." - ] - }, - { - "cell_type": "code", - "execution_count": 71, - "id": "82d18afa", - "metadata": {}, - "outputs": [], - "source": [ - "module = qpe.generate_program(\"unitary.qasm\", \"prepare_state.qasm\")" - ] - }, - { - "cell_type": "markdown", - "id": "57bc257e", - "metadata": {}, - "source": [ - "We can perform all standard [PyQASM](https://docs.qbraid.com/pyqasm/user-guide/overview) operations on the module, such as unrolling." - ] - }, - { - "cell_type": "code", - "execution_count": 72, - "id": "c294fd7f", - "metadata": {}, - "outputs": [], - "source": [ - "module.unroll()" - ] - }, - { - "cell_type": "markdown", - "id": "01a23645", - "metadata": {}, - "source": [ - "Below, we display the unrolled circuits." - ] - }, - { - "cell_type": "code", - "execution_count": 73, - "id": "046b193b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\n", - "include \"stdgates.inc\";\n", - "qubit[4] q;\n", - "qubit[1] psi;\n", - "bit[4] b;\n", - "x psi[0];\n", - "h q[0];\n", - "h q[1];\n", - "h q[2];\n", - "h q[3];\n", - "cz q[0], psi[0];\n", - "cz q[1], psi[0];\n", - "cz q[1], psi[0];\n", - "cz q[2], psi[0];\n", - "cz q[2], psi[0];\n", - "cz q[2], psi[0];\n", - "cz q[2], psi[0];\n", - "cz q[3], psi[0];\n", - "cz q[3], psi[0];\n", - "cz q[3], psi[0];\n", - "cz q[3], psi[0];\n", - "cz q[3], psi[0];\n", - "cz q[3], psi[0];\n", - "cz q[3], psi[0];\n", - "cz q[3], psi[0];\n", - "h q[3];\n", - "rz(-0.7853981633974483) q[3];\n", - "rx(1.5707963267948966) q[3];\n", - "rz(3.141592653589793) q[3];\n", - "rx(1.5707963267948966) q[3];\n", - "rz(3.141592653589793) q[3];\n", - "cx q[3], q[2];\n", - "rz(0.7853981633974483) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "cx q[3], q[2];\n", - "rz(-0.7853981633974483) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "h q[2];\n", - "rz(-0.39269908169872414) q[3];\n", - "rx(1.5707963267948966) q[3];\n", - "rz(3.141592653589793) q[3];\n", - "rx(1.5707963267948966) q[3];\n", - "rz(3.141592653589793) q[3];\n", - "cx q[3], q[1];\n", - "rz(0.39269908169872414) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "cx q[3], q[1];\n", - "rz(-0.39269908169872414) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rz(-0.7853981633974483) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "cx q[2], q[1];\n", - "rz(0.7853981633974483) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "cx q[2], q[1];\n", - "rz(-0.7853981633974483) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "h q[1];\n", - "rz(-0.19634954084936207) q[3];\n", - "rx(1.5707963267948966) q[3];\n", - "rz(3.141592653589793) q[3];\n", - "rx(1.5707963267948966) q[3];\n", - "rz(3.141592653589793) q[3];\n", - "cx q[3], q[0];\n", - "rz(0.19634954084936207) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "cx q[3], q[0];\n", - "rz(-0.19634954084936207) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rz(-0.39269908169872414) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "cx q[2], q[0];\n", - "rz(0.39269908169872414) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "cx q[2], q[0];\n", - "rz(-0.39269908169872414) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rz(-0.7853981633974483) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "cx q[1], q[0];\n", - "rz(0.7853981633974483) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "cx q[1], q[0];\n", - "rz(-0.7853981633974483) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "h q[0];\n", - "swap q[0], q[3];\n", - "swap q[1], q[2];\n", - "b[0] = measure q[0];\n", - "b[1] = measure q[1];\n", - "b[2] = measure q[2];\n", - "b[3] = measure q[3];\n", - "\n" - ] - } - ], - "source": [ - "module_str = pyqasm.dumps(module)\n", - "print(module_str)" - ] - }, - { - "cell_type": "markdown", - "id": "858d7bf8", - "metadata": {}, - "source": [ - "## Using QPE in your own OpenQASM3 program\n", - "#### qBraid algorithms makes it easy to incorporate QPE into your own OpenQASM3 circuit.\n", - "To use the QPE algorithm within your own circuit, simply pass the path to your pre-defined unitary operation to the `save_to_qasm` function. The function will generate a subroutine containing QPE for your unitary within a QASM file located in your current working directory, or any path of your choice." - ] - }, - { - "cell_type": "code", - "execution_count": 74, - "id": "1efed244", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Subroutine 'qpe' has been added to /Users/lukeandreesen/qbraid_algos/examples/QPE/qpe.qasm\n" - ] - } - ], - "source": [ - "qpe.save_to_qasm(\"unitary.qasm\")" - ] - }, - { - "cell_type": "markdown", - "id": "8738adb4", - "metadata": {}, - "source": [ - "To use the subroutine in your own circuit, add `include \"qpe.qasm\";` to your OpenQASM file, and call the `qpe` method by passing an appropriately sized register of qubits, as well as an ancilla qubit." - ] - }, - { - "cell_type": "code", - "execution_count": 75, - "id": "72a9dbe9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\r\n", - "include \"stdgates.inc\";\r\n", - "include \"iqft.qasm\";\r\n", - "\r\n", - "gate custom_t q {\r\n", - " z q;\r\n", - "}\r\n", - "\r\n", - "gate CU a, b {\r\n", - " ctrl @ custom_t a, b;\r\n", - "}\r\n", - " \r\n", - "\r\n", - "def qpe(qubit[4] q, qubit[1] psi) {\r\n", - " int n = 4;\r\n", - " for int i in [0:n-1] {\r\n", - " h q[i];\r\n", - " }\r\n", - " for int j in [0:n-1] {\r\n", - " int[16] k = 1 << j;\r\n", - " for int m in [0:k-1] {\r\n", - " CU q[j], psi[0];\r\n", - " }\r\n", - " }\r\n", - " iqft(q);\r\n", - "\r\n", - "}" - ] - } - ], - "source": [ - "%cat qpe.qasm" - ] - }, - { - "cell_type": "markdown", - "id": "7d939af7", - "metadata": {}, - "source": [ - "## Running Algorithms on qBraid\n", - "Running algorithms on qBraid is simple. First, import QbraidProvider from qBraid Runtime. Visit [here](https://docs.qbraid.com/sdk/user-guide/providers/native#qbraidprovider) for more information." - ] - }, - { - "cell_type": "markdown", - "id": "6401942b", - "metadata": {}, - "source": [ - "### Sample Problem: QPE with |1⟩ and the Z Gate\n", - "\n", - "The **Pauli-Z** gate is defined as:\n", - "\n", - "$$\n", - "Z =\n", - "\\begin{bmatrix}\n", - "1 & 0 \\\\\n", - "0 & -1\n", - "\\end{bmatrix}\n", - "$$\n", - "\n", - "#### 1. Eigenvalues and Eigenstates\n", - "- $|0\\rangle$ → eigenvalue $+1 = e^{2\\pi i \\cdot 0}$ → phase $\\phi = 0$ \n", - "- $|1\\rangle$ → eigenvalue $-1 = e^{i\\pi} = e^{2\\pi i \\cdot \\frac12}$ → phase $\\phi = 0.5$\n", - "\n", - "Since our input state is $|1\\rangle$, we are in an **eigenstate** of $Z$ with eigenvalue $-1$.\n", - "\n", - "#### 2. Phase Interpretation\n", - "In QPE, the unitary’s eigenvalue is written as:\n", - "\n", - "$$\n", - "\\lambda = e^{2\\pi i \\phi}\n", - "$$\n", - "\n", - "For $\\lambda = -1$:\n", - "\n", - "$$\n", - "-1 = e^{i\\pi} = e^{2\\pi i \\cdot \\frac12}\n", - "$$\n", - "$$\n", - "\\Rightarrow \\phi = \\frac12\n", - "$$\n", - "\n", - "#### 3. QPE Output with 3 Counting Qubits\n", - "- $n = 3$ → precision is $2^3 = 8$ possible values \n", - "- $\\phi = 0.5$ in binary with 3 bits is:\n", - "$$\n", - "0.5 = 0.100_2\n", - "$$\n", - "- QPE measurement register will give **`100`** with probability $1$.\n", - "\n", - "---\n", - "\n", - "**Final result:** \n", - "For $|1\\rangle$ and $Z$, QPE perfectly returns `100` for 3 counting qubits, corresponding to a phase of **0.5**.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 76, - "id": "74c3cd37", - "metadata": {}, - "outputs": [], - "source": [ - "from qbraid.runtime import QbraidProvider" - ] - }, - { - "cell_type": "markdown", - "id": "14200703", - "metadata": {}, - "source": [ - "If you have not yet configured QbraidProvider, provide your API key." - ] - }, - { - "cell_type": "code", - "execution_count": 77, - "id": "9d35aeaa", - "metadata": {}, - "outputs": [], - "source": [ - "# provider = QbraidProvider(api_key='API_KEY')\n", - "provider = QbraidProvider()" - ] - }, - { - "cell_type": "markdown", - "id": "011126c3", - "metadata": {}, - "source": [ - "We'll run our program on qBraid's QIR simulator." - ] - }, - { - "cell_type": "code", - "execution_count": 78, - "id": "9023abed", - "metadata": {}, - "outputs": [], - "source": [ - "device = provider.get_device('qbraid_qir_simulator')" - ] - }, - { - "cell_type": "code", - "execution_count": 79, - "id": "3fd289f7", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[, , , , , , , , ]\n" - ] - } - ], - "source": [ - "print(provider.get_devices())" - ] - }, - { - "cell_type": "markdown", - "id": "76f7ad3a", - "metadata": {}, - "source": [ - "To run the job, simply pass a QASM string to the device. If you have a PyQASM module, use `dumps` to generate a QASM string" - ] - }, - { - "cell_type": "code", - "execution_count": 80, - "id": "4532cf30", - "metadata": {}, - "outputs": [], - "source": [ - "module = qpe.generate_program(\"unitary.qasm\", \"prepare_state.qasm\", num_qubits=3)\n", - "module.unroll()\n", - "qasm_str = pyqasm.dumps(module)" - ] - }, - { - "cell_type": "code", - "execution_count": 81, - "id": "434bf090", - "metadata": {}, - "outputs": [], - "source": [ - "job = device.run(qasm_str, shots=500)" - ] - }, - { - "cell_type": "markdown", - "id": "713d7e0d", - "metadata": {}, - "source": [ - "Endianess is flipped for this machine - so we'll reverse the Qubit order; we also receive measurement results for the Ancilla qubit, which we'll drop here." - ] - }, - { - "cell_type": "code", - "execution_count": 82, - "id": "4dffb435", - "metadata": {}, - "outputs": [], - "source": [ - "results = job.result()\n", - "counts = results.data.get_counts()\n", - "# Drop the ancilla qubit\n", - "counts = {bitstr[:-1]: count for bitstr, count in counts.items()}\n", - "# Reverse the qubit order\n", - "counts = {bitstr[::-1]: count for bitstr, count in counts.items()}\n", - "\n", - "result = qpe.get_eigenvalue(counts)" - ] - }, - { - "cell_type": "markdown", - "id": "8ee6aa91", - "metadata": {}, - "source": [ - "Below, we show our succesfuly result of 0.5" - ] - }, - { - "cell_type": "code", - "execution_count": 83, - "id": "25c8c8fb", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.5\n" - ] - } - ], - "source": [ - "print(result)" - ] - }, - { - "cell_type": "markdown", - "id": "75dfbb27", - "metadata": {}, - "source": [ - "Finally, we can plot the results using qBraid Visualization." - ] - }, - { - "cell_type": "code", - "execution_count": 84, - "id": "6ea15fac", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from qbraid.visualization import plot_histogram\n", - "\n", - "plot_histogram(counts)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "14285905", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.11.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 + "cells": [ + { + "cell_type": "code", + "execution_count": 69, + "id": "a42257b1", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "\n", + "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), \"../..\")))" + ] + }, + { + "cell_type": "markdown", + "id": "405f6563", + "metadata": {}, + "source": [ + "## Quantum Phase Estimation (QPE)\n", + "#### In this tutorial, we will demonstrate the capabilities of qBraid Algorithms Quantum Phase Estimation Module\n", + "Begin by importing the module from qBraid Algorithms library" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "0a281dc2", + "metadata": {}, + "outputs": [], + "source": [ + "import pyqasm\n", + "from qbraid_algorithms import qpe" + ] + }, + { + "cell_type": "markdown", + "id": "727d132b", + "metadata": {}, + "source": [ + "To load a full QPE algorithm circuit as a PyQASM module, pass a path to the unitary U to the `generate_program()` method - this shoulds be defined as a valid custom QASM gate. Additionally, pass a custom gate that prepares your eigenstate. For example, to set the eigenstate to |1$\\rangle$, simply define and pass the following .qasm file:\n", + "```\n", + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "\n", + "gate prep q {\n", + " x q;\n", + "}\n", + "```\n", + "Note, the gate names in both cases can be anything." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "82d18afa", + "metadata": {}, + "outputs": [], + "source": [ + "module = qpe.generate_program(\"unitary.qasm\", \"prepare_state.qasm\")" + ] + }, + { + "cell_type": "markdown", + "id": "57bc257e", + "metadata": {}, + "source": [ + "We can perform all standard [PyQASM](https://docs.qbraid.com/pyqasm/user-guide/overview) operations on the module, such as unrolling." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "c294fd7f", + "metadata": {}, + "outputs": [], + "source": [ + "module.unroll()" + ] + }, + { + "cell_type": "markdown", + "id": "01a23645", + "metadata": {}, + "source": [ + "Below, we display the unrolled circuits." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "046b193b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "qubit[4] q;\n", + "qubit[1] psi;\n", + "bit[4] b;\n", + "x psi[0];\n", + "h q[0];\n", + "h q[1];\n", + "h q[2];\n", + "h q[3];\n", + "cz q[0], psi[0];\n", + "cz q[1], psi[0];\n", + "cz q[1], psi[0];\n", + "cz q[2], psi[0];\n", + "cz q[2], psi[0];\n", + "cz q[2], psi[0];\n", + "cz q[2], psi[0];\n", + "cz q[3], psi[0];\n", + "cz q[3], psi[0];\n", + "cz q[3], psi[0];\n", + "cz q[3], psi[0];\n", + "cz q[3], psi[0];\n", + "cz q[3], psi[0];\n", + "cz q[3], psi[0];\n", + "cz q[3], psi[0];\n", + "h q[3];\n", + "rz(-0.7853981633974483) q[3];\n", + "rx(1.5707963267948966) q[3];\n", + "rz(3.141592653589793) q[3];\n", + "rx(1.5707963267948966) q[3];\n", + "rz(3.141592653589793) q[3];\n", + "cx q[3], q[2];\n", + "rz(0.7853981633974483) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "cx q[3], q[2];\n", + "rz(-0.7853981633974483) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "h q[2];\n", + "rz(-0.39269908169872414) q[3];\n", + "rx(1.5707963267948966) q[3];\n", + "rz(3.141592653589793) q[3];\n", + "rx(1.5707963267948966) q[3];\n", + "rz(3.141592653589793) q[3];\n", + "cx q[3], q[1];\n", + "rz(0.39269908169872414) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "cx q[3], q[1];\n", + "rz(-0.39269908169872414) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "rz(-0.7853981633974483) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "cx q[2], q[1];\n", + "rz(0.7853981633974483) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "cx q[2], q[1];\n", + "rz(-0.7853981633974483) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "h q[1];\n", + "rz(-0.19634954084936207) q[3];\n", + "rx(1.5707963267948966) q[3];\n", + "rz(3.141592653589793) q[3];\n", + "rx(1.5707963267948966) q[3];\n", + "rz(3.141592653589793) q[3];\n", + "cx q[3], q[0];\n", + "rz(0.19634954084936207) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "cx q[3], q[0];\n", + "rz(-0.19634954084936207) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rz(-0.39269908169872414) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "cx q[2], q[0];\n", + "rz(0.39269908169872414) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "cx q[2], q[0];\n", + "rz(-0.39269908169872414) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rz(-0.7853981633974483) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "rx(1.5707963267948966) q[1];\n", + "rz(3.141592653589793) q[1];\n", + "cx q[1], q[0];\n", + "rz(0.7853981633974483) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "cx q[1], q[0];\n", + "rz(-0.7853981633974483) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "h q[0];\n", + "swap q[0], q[3];\n", + "swap q[1], q[2];\n", + "b[0] = measure q[0];\n", + "b[1] = measure q[1];\n", + "b[2] = measure q[2];\n", + "b[3] = measure q[3];\n", + "\n" + ] + } + ], + "source": [ + "module_str = pyqasm.dumps(module)\n", + "print(module_str)" + ] + }, + { + "cell_type": "markdown", + "id": "858d7bf8", + "metadata": {}, + "source": [ + "## Using QPE in your own OpenQASM3 program\n", + "#### qBraid algorithms makes it easy to incorporate QPE into your own OpenQASM3 circuit.\n", + "To use the QPE algorithm within your own circuit, simply pass the path to your pre-defined unitary operation to the `save_to_qasm` function. The function will generate a subroutine containing QPE for your unitary within a QASM file located in your current working directory, or any path of your choice." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "1efed244", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Subroutine 'qpe' has been added to /Users/lukeandreesen/qbraid_algos/examples/QPE/qpe.qasm\n" + ] + } + ], + "source": [ + "qpe.save_to_qasm(\"unitary.qasm\")" + ] + }, + { + "cell_type": "markdown", + "id": "8738adb4", + "metadata": {}, + "source": [ + "To use the subroutine in your own circuit, add `include \"qpe.qasm\";` to your OpenQASM file, and call the `qpe` method by passing an appropriately sized register of qubits, as well as an ancilla qubit." + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "72a9dbe9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\r\n", + "include \"stdgates.inc\";\r\n", + "include \"iqft.qasm\";\r\n", + "\r\n", + "gate custom_t q {\r\n", + " z q;\r\n", + "}\r\n", + "\r\n", + "gate CU a, b {\r\n", + " ctrl @ custom_t a, b;\r\n", + "}\r\n", + " \r\n", + "\r\n", + "def qpe(qubit[4] q, qubit[1] psi) {\r\n", + " int n = 4;\r\n", + " for int i in [0:n-1] {\r\n", + " h q[i];\r\n", + " }\r\n", + " for int j in [0:n-1] {\r\n", + " int[16] k = 1 << j;\r\n", + " for int m in [0:k-1] {\r\n", + " CU q[j], psi[0];\r\n", + " }\r\n", + " }\r\n", + " iqft(q);\r\n", + "\r\n", + "}" + ] + } + ], + "source": [ + "%cat qpe.qasm" + ] + }, + { + "cell_type": "markdown", + "id": "7d939af7", + "metadata": {}, + "source": [ + "## Running Algorithms on qBraid\n", + "Running algorithms on qBraid is simple. First, import QbraidProvider from qBraid Runtime. Visit [here](https://docs.qbraid.com/sdk/user-guide/providers/native#qbraidprovider) for more information." + ] + }, + { + "cell_type": "markdown", + "id": "6401942b", + "metadata": {}, + "source": [ + "### Sample Problem: QPE with |1⟩ and the Z Gate\n", + "\n", + "The **Pauli-Z** gate is defined as:\n", + "\n", + "$$\n", + "Z =\n", + "\\begin{bmatrix}\n", + "1 & 0 \\\\\n", + "0 & -1\n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "#### 1. Eigenvalues and Eigenstates\n", + "- $|0\\rangle$ → eigenvalue $+1 = e^{2\\pi i \\cdot 0}$ → phase $\\phi = 0$ \n", + "- $|1\\rangle$ → eigenvalue $-1 = e^{i\\pi} = e^{2\\pi i \\cdot \\frac12}$ → phase $\\phi = 0.5$\n", + "\n", + "Since our input state is $|1\\rangle$, we are in an **eigenstate** of $Z$ with eigenvalue $-1$.\n", + "\n", + "#### 2. Phase Interpretation\n", + "In QPE, the unitary’s eigenvalue is written as:\n", + "\n", + "$$\n", + "\\lambda = e^{2\\pi i \\phi}\n", + "$$\n", + "\n", + "For $\\lambda = -1$:\n", + "\n", + "$$\n", + "-1 = e^{i\\pi} = e^{2\\pi i \\cdot \\frac12}\n", + "$$\n", + "$$\n", + "\\Rightarrow \\phi = \\frac12\n", + "$$\n", + "\n", + "#### 3. QPE Output with 3 Counting Qubits\n", + "- $n = 3$ → precision is $2^3 = 8$ possible values \n", + "- $\\phi = 0.5$ in binary with 3 bits is:\n", + "$$\n", + "0.5 = 0.100_2\n", + "$$\n", + "- QPE measurement register will give **`100`** with probability $1$.\n", + "\n", + "---\n", + "\n", + "**Final result:** \n", + "For $|1\\rangle$ and $Z$, QPE perfectly returns `100` for 3 counting qubits, corresponding to a phase of **0.5**.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "74c3cd37", + "metadata": {}, + "outputs": [], + "source": [ + "from qbraid.runtime import QbraidProvider" + ] + }, + { + "cell_type": "markdown", + "id": "14200703", + "metadata": {}, + "source": [ + "If you have not yet configured QbraidProvider, provide your API key." + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "9d35aeaa", + "metadata": {}, + "outputs": [], + "source": [ + "# provider = QbraidProvider(api_key='API_KEY')\n", + "provider = QbraidProvider()" + ] + }, + { + "cell_type": "markdown", + "id": "011126c3", + "metadata": {}, + "source": [ + "We'll run our program on qBraid's QIR simulator." + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "9023abed", + "metadata": {}, + "outputs": [], + "source": [ + "device = provider.get_device(\"qbraid_qir_simulator\")" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "3fd289f7", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[, , , , , , , , ]\n" + ] + } + ], + "source": [ + "print(provider.get_devices())" + ] + }, + { + "cell_type": "markdown", + "id": "76f7ad3a", + "metadata": {}, + "source": [ + "To run the job, simply pass a QASM string to the device. If you have a PyQASM module, use `dumps` to generate a QASM string" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "4532cf30", + "metadata": {}, + "outputs": [], + "source": [ + "module = qpe.generate_program(\"unitary.qasm\", \"prepare_state.qasm\", num_qubits=3)\n", + "module.unroll()\n", + "qasm_str = pyqasm.dumps(module)" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "434bf090", + "metadata": {}, + "outputs": [], + "source": [ + "job = device.run(qasm_str, shots=500)" + ] + }, + { + "cell_type": "markdown", + "id": "713d7e0d", + "metadata": {}, + "source": [ + "Endianess is flipped for this machine - so we'll reverse the Qubit order; we also receive measurement results for the Ancilla qubit, which we'll drop here." + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "4dffb435", + "metadata": {}, + "outputs": [], + "source": [ + "results = job.result()\n", + "counts = results.data.get_counts()\n", + "# Drop the ancilla qubit\n", + "counts = {bitstr[:-1]: count for bitstr, count in counts.items()}\n", + "# Reverse the qubit order\n", + "counts = {bitstr[::-1]: count for bitstr, count in counts.items()}\n", + "\n", + "result = qpe.get_eigenvalue(counts)" + ] + }, + { + "cell_type": "markdown", + "id": "8ee6aa91", + "metadata": {}, + "source": [ + "Below, we show our succesfuly result of 0.5" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "25c8c8fb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.5\n" + ] + } + ], + "source": [ + "print(result)" + ] + }, + { + "cell_type": "markdown", + "id": "75dfbb27", + "metadata": {}, + "source": [ + "Finally, we can plot the results using qBraid Visualization." + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "6ea15fac", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from qbraid.visualization import plot_histogram\n", + "\n", + "plot_histogram(counts)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14285905", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 } \ No newline at end of file diff --git a/examples/bells_inequality.ipynb b/examples/bells_inequality.ipynb index 1f7ad1b..2eb3741 100644 --- a/examples/bells_inequality.ipynb +++ b/examples/bells_inequality.ipynb @@ -12,7 +12,7 @@ "import sys\n", "import os\n", "\n", - "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '..')))" + "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), \"..\")))" ] }, { @@ -116,7 +116,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -130,7 +130,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.11" + "version": "3.13.2" } }, "nbformat": 4, diff --git a/examples/bernstein_vazirani.ipynb b/examples/bernstein_vazirani.ipynb index c0fc967..459083f 100644 --- a/examples/bernstein_vazirani.ipynb +++ b/examples/bernstein_vazirani.ipynb @@ -1,506 +1,506 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "405809fb", - "metadata": { - "tags": [ - "hide_cell" - ] - }, - "outputs": [], - "source": [ - "import sys\n", - "import os\n", - "\n", - "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '..')))" - ] - }, - { - "cell_type": "markdown", - "id": "d3b8876c", - "metadata": {}, - "source": [ - "## Bernstein Vazirani Algorithm\n", - "#### In this tutorial, we will demonstrate the capabilities of qBraid Algorithms Bernstein Vazirani Module\n", - "Begin by importing the module from qBraid Algorithms library" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e939fb23", - "metadata": {}, - "outputs": [], - "source": [ - "import pyqasm\n", - "from qbraid_algorithms import bernstein_vazirani as bv" - ] - }, - { - "cell_type": "markdown", - "id": "3545fac9", - "metadata": {}, - "source": [ - "To load a full Bernstein Vazirani algorithm circuit as a PyQASM module, simply pass the secret string to be encoded in the oracle to the `load_program()` method." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "97649823", - "metadata": {}, - "outputs": [], - "source": [ - "module = bv.generate_program('1011')" - ] - }, - { - "cell_type": "markdown", - "id": "7e2ac473", - "metadata": {}, - "source": [ - "We can perform all standard [PyQASM](https://docs.qbraid.com/pyqasm/user-guide/overview) operations on the module, such as unrolling." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "0fb825a7", - "metadata": {}, - "outputs": [], - "source": [ - "module.unroll()" - ] - }, - { - "cell_type": "markdown", - "id": "287b4aa8", - "metadata": {}, - "source": [ - "Below, we display the unrolled circuit, which includes preparing the input and ancilla qubits, applying the '1011' oracle, applying Hadamard gates after the oracle, then measuring the results." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "31842ddc", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\n", - "include \"stdgates.inc\";\n", - "qubit[1] ancilla;\n", - "qubit[4] q;\n", - "bit[4] b;\n", - "h q[0];\n", - "h q[1];\n", - "h q[2];\n", - "h q[3];\n", - "x ancilla[0];\n", - "h ancilla[0];\n", - "cx q[0], ancilla[0];\n", - "cx q[2], ancilla[0];\n", - "cx q[3], ancilla[0];\n", - "h q[0];\n", - "h q[1];\n", - "h q[2];\n", - "h q[3];\n", - "b[0] = measure q[0];\n", - "b[1] = measure q[1];\n", - "b[2] = measure q[2];\n", - "b[3] = measure q[3];\n", - "\n" - ] - } - ], - "source": [ - "module_str = pyqasm.dumps(module)\n", - "print(module_str)" - ] - }, - { - "cell_type": "markdown", - "id": "21906ab0", - "metadata": {}, - "source": [ - "## Using B-V in your own OpenQASM3 program\n", - "#### qBraid algorithms makes it easy to incorporate either the full Bernstein Vazirani algorithm - or just the encoded oracle - into your own OpenQASM3 circuit.\n", - "To use a secret string-encoded oracle in your circuit, first generate the oracle submodule using the `generate_oracle` method, which takes a secret string. The method will create a QASM3 file containing your oracle as a subroutine within your current working directory." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "6f3e9784", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Oracle 'oracle' has been added to /Users/lukeandreesen/qbraid_algos/examples/oracle.qasm\n" - ] - } - ], - "source": [ - "bv.generate_oracle('111')" - ] - }, - { - "cell_type": "markdown", - "id": "61817da7", - "metadata": {}, - "source": [ - "Below, we can see the custom oracle subroutine that you now have access to. To use this in your own circuit, simply add `include \"oracle.qasm\";` to your OpenQASM file, and call the `oracle` subroutine by passing an appropriately sized register of qubits and an ancilla qubit." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "8833f4b9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\r\n", - "include \"stdgates.inc\";\r\n", - "\r\n", - "def oracle(qubit[3] q, qubit[1] ancilla) {\r\n", - " int[32] s = 7;\r\n", - " int[16] n = 3;\r\n", - " for int i in [0:n - 1] {\r\n", - " if ((s >> i) & 1) {\r\n", - " cx q[i], ancilla[0];\r\n", - " }\r\n", - " }\r\n", - "}\r\n" - ] - } - ], - "source": [ - "%cat oracle.qasm" - ] - }, - { - "cell_type": "markdown", - "id": "3672d119", - "metadata": {}, - "source": [ - "Similarly, you can generate an entire Bernstein Vazirani circuit as a submodule using the `save_to_qasm` method, again passing your desired secret string." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "5e37f60d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Subroutine 'bernvaz' has been added to /Users/lukeandreesen/qbraid_algos/examples/bernvaz.qasm\n" - ] - } - ], - "source": [ - "subroutine = bv.save_to_qasm('011')" - ] - }, - { - "cell_type": "markdown", - "id": "05cfcdf4", - "metadata": {}, - "source": [ - "To use the subroutine in your own circuit, add `include \"bernvaz.qasm\";` to your OpenQASM file, and call the `bernvaz` method by passing an appropriately sized register of qubits, as well as an ancilla qubit." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "38b7beb3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\r\n", - "include \"stdgates.inc\";\r\n", - "\r\n", - "def bernvaz(qubit[3] q, qubit[1] ancilla) {\r\n", - " int[32] s = 6;\r\n", - " int[16] n = 3;\r\n", - " for int i in [0:n - 1] {\r\n", - " h q[i];\r\n", - " }\r\n", - " x ancilla[0];\r\n", - " h ancilla[0];\r\n", - " // Note: Nested subroutine calls not yet supported by QASM, so manually insert\r\n", - " for int i in [0:n - 1] {\r\n", - " if ((s >> i) & 1) {\r\n", - " cx q[i], ancilla[0];\r\n", - " }\r\n", - " }\r\n", - " for int i in [0:n - 1] {\r\n", - " h q[i];\r\n", - " }\r\n", - "\r\n", - "}\r\n" - ] - } - ], - "source": [ - "%cat bernvaz.qasm" - ] - }, - { - "cell_type": "markdown", - "id": "e7d3dd7e", - "metadata": {}, - "source": [ - "## Running Algorithms on qBraid\n", - "Running algorithms on qBraid is simple. First, import QbraidProvider from qBraid Runtime. Visit [here](https://docs.qbraid.com/sdk/user-guide/providers/native#qbraidprovider) for more information." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "96870043", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/opt/homebrew/lib/python3.11/site-packages/pennylane/capture/capture_operators.py:33: RuntimeWarning: PennyLane is not yet compatible with JAX versions > 0.4.28. You have version 0.4.30 installed. Please downgrade JAX to <=0.4.28 to avoid runtime errors.\n", - " warnings.warn(\n" - ] - } - ], - "source": [ - "from qbraid.runtime import QbraidProvider" - ] - }, - { - "cell_type": "markdown", - "id": "f3ffffdf", - "metadata": {}, - "source": [ - "If you have not yet configured QbraidProvider, provide your API key." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "b82efa4b", - "metadata": {}, - "outputs": [], - "source": [ - "# provider = QbraidProvider(api_key='API_KEY')\n", - "provider = QbraidProvider()" - ] - }, - { - "cell_type": "markdown", - "id": "539d7239", - "metadata": {}, - "source": [ - "We'll run our program on qBraid's QIR simulator." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "a273c97e", - "metadata": {}, - "outputs": [], - "source": [ - "device = provider.get_device('qbraid_qir_simulator')" - ] - }, - { - "cell_type": "markdown", - "id": "8495dbad", - "metadata": {}, - "source": [ - "To run the job, simply pass a QASM string to the device. If you have a PyQASM module, use `dumps` to generate a QASM string" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "51206d57", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\n", - "include \"stdgates.inc\";\n", - "qubit[1] ancilla;\n", - "qubit[3] q;\n", - "bit[3] b;\n", - "h q[0];\n", - "h q[1];\n", - "h q[2];\n", - "x ancilla[0];\n", - "h ancilla[0];\n", - "cx q[0], ancilla[0];\n", - "cx q[2], ancilla[0];\n", - "h q[0];\n", - "h q[1];\n", - "h q[2];\n", - "b[0] = measure q[0];\n", - "b[1] = measure q[1];\n", - "b[2] = measure q[2];\n", - "\n" - ] - } - ], - "source": [ - "secret_string = '101'\n", - "module = bv.generate_program(secret_string)\n", - "module.unroll()\n", - "qasm_str = pyqasm.dumps(module)\n", - "print(qasm_str)" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "358fcbc7", - "metadata": {}, - "outputs": [], - "source": [ - "job = device.run(qasm_str, shots=500)" - ] - }, - { - "cell_type": "markdown", - "id": "53858d48", - "metadata": {}, - "source": [ - "We can now get the counts from the job results." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "9a8f91c5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'1010': 500}\n" - ] - } - ], - "source": [ - "results = job.result()\n", - "counts = results.data.get_counts()" - ] - }, - { - "cell_type": "markdown", - "id": "b00c560b", - "metadata": {}, - "source": [ - "We can now check the counts to see if the most frequent value is our secret string. This particular backend includes the ancilla qubit as the least significant bit, so we'll remove that." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "b14a37ce", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Secret String: 101. B-V result string: 101\n" - ] - } - ], - "source": [ - "# Remove ancilla qubit from bitstring\n", - "processed_counts = {bitstr[:-1]: count for bitstr, count in counts.items()}\n", - "# Find most frequent count\n", - "max_str = max(processed_counts, key=counts.get)\n", - "\n", - "print(f\"Secret String: {secret_string}. B-V result string: {result_string}\")" - ] - }, - { - "cell_type": "markdown", - "id": "29cbdab1", - "metadata": {}, - "source": [ - "We see that the algorithm successfully identified the secret string. Finally, we can plot the results using qBraid Visualization." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "e84ea61e", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAG3CAYAAABSTJRlAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA8nElEQVR4nO3de3QU9f3/8dfMJoSQkIQk5Aa5AUGIBoNcI9YLpEREqwULKAWkVnr4Av6Ear3US7E9otiqtdXytbWgViripSqICKFi1cglGLmohHsCuQEh2RDMJtnZ3x8Ju5uvaDUEEsbn45yc475ndj6feUtmX5mZ3TU8Ho9HAAAANmW29wQAAADOJMIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwtXYPO4cOHdJPf/pTRUVFKTg4WBkZGdq8ebN3ucfj0f3336/4+HgFBwcrOztbu3btarGNyspKTZ48WWFhYYqIiNDNN9+s48ePn+1dAQAAHVC7hp1jx45pxIgRCgwM1KpVq/TZZ5/pD3/4g7p16+ZdZ+HChXryySe1aNEibdiwQSEhIcrJyVFdXZ13ncmTJ2vHjh1as2aNVqxYoffff18zZsxoj10CAAAdjNGeXwR611136cMPP9R//vOfUy73eDxKSEjQL3/5S91+++2SpOrqasXGxmrJkiWaNGmSPv/8c6Wnp2vTpk0aPHiwJOmdd97RVVddpYMHDyohIeGs7Q8AAOh4Atpz8DfffFM5OTn6yU9+ovXr16tHjx76n//5H91yyy2SpH379qmsrEzZ2dne54SHh2vYsGHKy8vTpEmTlJeXp4iICG/QkaTs7GyZpqkNGzboxz/+8VfGdblccrlc3seWZamyslJRUVEyDOMM7jEAAGgrHo9HNTU1SkhIkGl+/cWqdg07e/fu1V/+8hfNmzdP99xzjzZt2qRbb71VnTp10rRp01RWViZJio2NbfG82NhY77KysjLFxMS0WB4QEKDIyEjvOv/XggULNH/+/DOwRwAA4GwrLi5Wz549v3Z5u4Ydy7I0ePBgPfTQQ5KkgQMHavv27Vq0aJGmTZt2xsa9++67NW/ePO/j6upqJSUlqbi4WGFhYWdsXAAA0HacTqcSExPVtWvXb1yvXcNOfHy80tPTW9T69++vV199VZIUFxcnSSovL1d8fLx3nfLycmVmZnrXqaioaLGNxsZGVVZWep//fwUFBSkoKOgr9bCwMMIOAADnmP92C0q7vhtrxIgR2rlzZ4taYWGhkpOTJUmpqamKi4tTbm6ud7nT6dSGDRuUlZUlScrKylJVVZXy8/O966xbt06WZWnYsGFnYS8AAEBH1q5ndubOnauLL75YDz30kCZMmKCNGzfqmWee0TPPPCOpKanddttt+t3vfqe0tDSlpqbqvvvuU0JCgq677jpJTWeCrrzySt1yyy1atGiRGhoaNHv2bE2aNIl3YgEAgPZ967kkrVixQnfffbd27dql1NRUzZs3z/tuLKnpTusHHnhAzzzzjKqqqnTJJZfo6aefVt++fb3rVFZWavbs2XrrrbdkmqbGjx+vJ598UqGhod9qDk6nU+Hh4aquruYyFgAA54hv+/rd7mGnIyDsAABw7vm2r9/t/nURAAAAZxJhBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphB8A5LSUlReedd54yMzOVmZmpZcuWSZJ27dqliy++WH379tWQIUO0Y8cO73O+aRkA+yHsADjnLVu2TAUFBSooKNDEiRMlSb/4xS80Y8YMFRYW6s4779RNN93kXf+blgGwH8IOANupqKjQ5s2b9dOf/lSSNH78eBUXF2v37t3fuAyAPRF2AJzzpk6dqoyMDN188806fPiwiouLFR8fr4CAAEmSYRhKSkpSUVHRNy4DYE+EHQDntPfff19bt27Vli1bFB0drWnTprX3lAB0MAHtPQEAOB1JSUmSpMDAQN12223q27evEhMTVVpaqsbGRgUEBMjj8aioqEhJSUkKCwv72mUA7IkzOwDOWbW1taqqqvI+/uc//6mBAwcqJiZGF110kf7xj39Ikl599VX17NlTffr0+cZlAOzJ8Hg8nvaeRHtzOp0K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NX36dLlcrhbPGzp0qK644go98sgjmjFjhg4cOKDVq1d7l584cUIhISF6++23NWbMmFOO7XK5WmzX6XQqMTFR1dXVCgsLa9P9TLlrZZtuDwCAc83+h8eeke06nU6Fh4f/19fvdruMlZ+fr4qKCl100UUKCAhQQECA1q9fryeffFIBAQGKjY1VfX29qqqqWjyvvLxccXFxkqS4uLivvDvr5OOT65xKUFCQwsLCWvwAAAB7arewM2rUKG3btk0FBQXen8GDB2vy5Mne/w4MDFRubq73OTt37lRRUZGysrIkSVlZWdq2bZsqKiq866xZs0ZhYWFKT08/6/sEAAA6nna7Z6dr16664IILWtRCQkIUFRXlrd98882aN2+eIiMjFRYWpjlz5igrK0vDhw+XJI0ePVrp6emaMmWKFi5cqLKyMt17772aNWuWgoKCzvo+AQCAjqddb1D+bx5//HGZpqnx48fL5XIpJydHTz/9tHe5w+HQihUrNHPmTGVlZSkkJETTpk3Tgw8+2I6zBgAAHUm73aDckXzbG5xagxuUAQDfd9/bG5QBAADOBsIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwtVaFnS1btmjbtm3ex2+88Yauu+463XPPPaqvr2+zyQEAAJyuVoWdX/ziFyosLJQk7d27V5MmTVKXLl20fPly/epXv2rTCQIAAJyOVoWdwsJCZWZmSpKWL1+uSy+9VEuXLtWSJUv06quvtuX8AAAATkurwo7H45FlWZKktWvX6qqrrpIkJSYm6siRI203OwAAgNPUqrAzePBg/e53v9MLL7yg9evXa+zYsZKkffv2KTY2tk0nCAAAcDpaFXYef/xxbdmyRbNnz9avf/1r9enTR5L0yiuv6OKLL27TCQIAAJyOgNY86cILL2zxbqyTHn30UQUEtGqTAAAAZ0Srzuz06tVLR48e/Uq9rq5Offv2Pe1JAQAAtJVWhZ39+/fL7XZ/pe5yuXTw4MHTnhQAAEBb+U7XnN58803vf69evVrh4eHex263W7m5uUpNTW272QEAAJym7xR2rrvuOkmSYRiaNm1ai2WBgYFKSUnRH/7whzabHAAAwOn6TmHn5GfrpKamatOmTYqOjj4jkwIAAGgrrXrr1L59+9p6HgAAAGdEq98nnpubq9zcXFVUVHjP+Jz097///bQnBgAA0BZaFXbmz5+vBx98UIMHD1Z8fLwMw2jreQEAALSJVoWdRYsWacmSJZoyZUpbzwcAAKBNtepzdurr6/laCAAAcE5oVdj5+c9/rqVLl7b1XAAAANpcq8JOXV2dHnvsMV122WWaM2eO5s2b1+Ln2/rLX/6iAQMGKCwsTGFhYcrKytKqVatajDNr1ixFRUUpNDRU48ePV3l5eYttFBUVaezYserSpYtiYmJ0xx13qLGxsTW7BQAAbKhV9+xs3bpVmZmZkqTt27e3WPZdblbu2bOnHn74YaWlpcnj8ei5557Ttddeq08++UTnn3++5s6dq5UrV2r58uUKDw/X7NmzNW7cOH344YeSmj61eezYsYqLi9NHH32k0tJSTZ06VYGBgXrooYdas2sAAMBmDI/H42nvSfiLjIzUo48+quuvv17du3fX0qVLdf3110uSvvjiC/Xv3195eXkaPny4Vq1apauvvlolJSWKjY2V1HTz9J133qnDhw+rU6dO32pMp9Op8PBwVVdXKywsrE33J+WulW26PQAAzjX7Hx57Rrb7bV+/W3UZ60xwu9166aWXVFtbq6ysLOXn56uhoUHZ2dnedfr166ekpCTl5eVJkvLy8pSRkeENOpKUk5Mjp9OpHTt2fO1YLpdLTqezxQ8AALCnVl3GuuKKK77xctW6deu+9ba2bdumrKws1dXVKTQ0VK+//rrS09NVUFCgTp06KSIiosX6sbGxKisrkySVlZW1CDonl59c9nUWLFig+fPnf6W+efNmhYaGSpIyMzNVU1OjPXv2eJf369dPDoejRZBKSUlRVFSU8vPzW8whOTlZBQUF+lnfpm+HP1hr6N1Dpq7s6VZCl6b1ahqk5fscyoqx1D/Cd4JtcaGp/hEeDY/x1V7bbyokQMrp6fsAx9wSU0frpAm9fLX8I4Y+rTR1U5pbZvP/ol1OQ/8pM/XjZLe6BTXVKuqkFUUOjUywlBLaNI7Lkl7c7dCgaEsXRvrGXrrHVI8Q6bI43zgri01ZHumaJF/tg3JD+2oMTenjq207ZmjTYVM39HYr2NFUKzpuaG2JqasS3YoLbqpVN0iv7nNoRKyl88J9Y/+90KELulka2t1Xe3W/qa6B0ugevnHWHjJVVS9dn+qrbTpsaNsxU9P7unXyX2thtaEPyk2NS3ErovnEX/mX0spih0YlWEpu7kWdW1q6x6HB0ZYG+PXixd2mEkM9ujTOV1tR1PQ3w9V+vXi/zFDxcUOT/XqxtdLQ5iOmbuztVufmXhw4bii3xNTYRLdim3tRVS+9tt+hS2It9W3uhUfS4kKHMrpZGuLXi1f2mYroJGX79eLdQ6ZqGqTxKb7axsOGth8zvf8eJWlntaEPy02NT3UrPLCpVval9HaxQ9kJlpKae/GlW/rnHoeGdLeU0c039gu7TaV29eiSWF/trSJTpiGNTfSNvb7M1KFa6cbevtqnlYbyj5ia3MetoOY/ufYfN7SuxNTVSW7FdG6qHXNJrx9w6AdxltLCmsaxPNKSXQ5dGGlpULRv7Jf3morqLI1K8I2z+qCp2kZpnF8vPq4w9HmVoel9fbXPqwzlVZj6SapbXZt7UXJCeuegQ6N7WOoZ0jRObaO0bK9Dw7pbOt+vF8/vMtU7zKMRfr1444CpTqY0xq8X75WaKvtSmuT3O/vJUUOfHDU1pY9bgc292Ftj6L1SUz9Kciu6uRdHXdIbBxy6LM5S7+ZeNHqk53c5lBlp6SK/Xizba6p7Z2mkXy/eOWiqzi1dl+yr5VUY2llt6KY0X+2zKkMfV5iakOpWaHMvDp0wtPqgqZyelnp0aRrneIP08j6HhsdYSvc7fi3ZZeq8cI+y/I5f/zpgqrNDutLv+LWuxNThOmmiXy+2HDFUUGlqappbAc2/tHuchtaXmbo22a2o5uPXkTrpzSKHLo+31Ktr0zgNlvTCbocGRlkaGOUb+6W9puKCpcvjfeOsKjZVb0nX+vXiw3JDe5yGpvr1YscxQxsOm5rYy62Q5ldLjuWtO5ZLUmlpqYqLi72PMzIy5HK5VFhY6K2lpaUpODhYW7du9dYSExMVHx+vjRs3emvR0dHq1avXN57Y8Neqy1hz585t8bihoUEFBQXavn27pk2bpj/+8Y/felv19fUqKipSdXW1XnnlFf3tb3/T+vXrVVBQoOnTp8vlcrVYf+jQobriiiv0yCOPaMaMGTpw4IBWr17tXX7ixAmFhITo7bff1pgxY045psvlarFdp9OpxMRELmMBAHAGtPdlrFad2Xn88cdPWf/Nb36j48ePf6dtderUSX369JEkDRo0SJs2bdIf//hHTZw4UfX19aqqqmpxdqe8vFxxcXGSpLi4uBZJ7+Tyk8u+TlBQkIKCgr7TPAEAwLmpTe/Z+elPf3ra34tlWZZcLpcGDRqkwMBA5ebmepft3LlTRUVFysrKkiRlZWVp27Ztqqio8K6zZs0ahYWFKT09/bTmAQAA7KHVXwR6Knl5eercufO3Xv/uu+/WmDFjlJSUpJqaGi1dulTvvfeeVq9erfDwcN18882aN2+eIiMjFRYWpjlz5igrK0vDhw+XJI0ePVrp6emaMmWKFi5cqLKyMt17772aNWsWZ24AAICkVoadcePGtXjs8XhUWlqqzZs367777vvW26moqNDUqVNVWlqq8PBwDRgwQKtXr9YPf/hDSU2Xy0zT1Pjx4+VyuZSTk6Onn37a+3yHw6EVK1Zo5syZysrKUkhIiKZNm6YHH3ywNbsFAABsqFU3KE+fPr3FY9M01b17d40cOVKjR49us8mdLXzODgAAZ845eYPy4sWLWz0xAACAs+m07tnJz8/X559/Lkk6//zzNXDgwDaZFAAAQFtpVdipqKjQpEmT9N5773nfFl5VVaUrrrhCL730krp3796WcwQAAGi1Vr31fM6cOaqpqdGOHTtUWVmpyspKbd++XU6nU7feemtbzxEAAKDVWnVm55133tHatWvVv39/by09PV1PPfXUOXmDMgAAsK9WndmxLEuBgYFfqQcGBsqyrFM8AwAAoH20KuyMHDlS/+///T+VlJR4a4cOHdLcuXM1atSoNpscAADA6WpV2Pnzn/8sp9OplJQU9e7dW71791ZqaqqcTqf+9Kc/tfUcAQAAWq1V9+wkJiZqy5YtWrt2rb744gtJUv/+/ZWdnd2mkwMAADhd3+nMzrp165Seni6n0ynDMPTDH/5Qc+bM0Zw5czRkyBCdf/75+s9//nOm5goAAPCdfaew88QTT+iWW2455Ucyh4eH6xe/+IUee+yxNpscAADA6fpOYefTTz/VlVde+bXLR48erfz8/NOeFAAAQFv5TmGnvLz8lG85PykgIECHDx8+7UkBAAC0le8Udnr06KHt27d/7fKtW7cqPj7+tCcFAADQVr5T2Lnqqqt03333qa6u7ivLvvzySz3wwAO6+uqr22xyAAAAp+s7vfX83nvv1Wuvvaa+fftq9uzZOu+88yRJX3zxhZ566im53W79+te/PiMTBQAAaI3vFHZiY2P10UcfaebMmbr77rvl8XgkSYZhKCcnR0899ZRiY2PPyEQBAABa4zt/qGBycrLefvttHTt2TLt375bH41FaWpq6det2JuYHAABwWlr1CcqS1K1bNw0ZMqQt5wIAANDmWvXdWAAAAOcKwg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALC1dg07CxYs0JAhQ9S1a1fFxMTouuuu086dO1usU1dXp1mzZikqKkqhoaEaP368ysvLW6xTVFSksWPHqkuXLoqJidEdd9yhxsbGs7krAACgg2rXsLN+/XrNmjVLH3/8sdasWaOGhgaNHj1atbW13nXmzp2rt956S8uXL9f69etVUlKicePGeZe73W6NHTtW9fX1+uijj/Tcc89pyZIluv/++9tjlwAAQAdjeDweT3tP4qTDhw8rJiZG69ev16WXXqrq6mp1795dS5cu1fXXXy9J+uKLL9S/f3/l5eVp+PDhWrVqla6++mqVlJQoNjZWkrRo0SLdeeedOnz4sDp16vRfx3U6nQoPD1d1dbXCwsLadJ9S7lrZptsDAOBcs//hsWdku9/29btD3bNTXV0tSYqMjJQk5efnq6GhQdnZ2d51+vXrp6SkJOXl5UmS8vLylJGR4Q06kpSTkyOn06kdO3acchyXyyWn09niBwAA2FNAe0/gJMuydNttt2nEiBG64IILJEllZWXq1KmTIiIiWqwbGxursrIy7zr+Qefk8pPLTmXBggWaP3/+V+qbN29WaGioJCkzM1M1NTXas2ePd3m/fv3kcDhahKiUlBRFRUUpPz+/xfjJyckqKCjQz/q6JUkHaw29e8jUlT3dSujStF5Ng7R8n0NZMZb6R/hOsC0uNNU/wqPhMb7aa/tNhQRIOT0tby23xNTROmlCL18t/4ihTytN3ZTmlmk01XY5Df2nzNSPk93qFtRUq6iTVhQ5NDLBUkpo0zguS3pxt0ODoi1dGOkbe+keUz1CpMvifOOsLDZleaRrkny1D8oN7asxNKWPr7btmKFNh03d0NutYEdTrei4obUlpq5KdCsuuKlW3SC9us+hEbGWzgv3jf33Qocu6GZpaHdf7dX9proGSqN7+MZZe8hUVb10faqvtumwoW3HTE3v61ZzK1RYbeiDclPjUtyKaD7pV/6ltLLYoVEJlpKbe1HnlpbucWhwtKUBfr14cbepxFCPLo3z1VYUNf3NcLVfL94vM1R83NBkv15srTS0+YipG3u71bm5FweOG8otMTU20a3Y5l5U1Uuv7XfoklhLfZt74ZG0uNChjG6Whvj14pV9piI6Sdl+vXj3kKmaBml8iq+28bCh7cdM779HSdpZbejDclPjU90KD2yqlX0pvV3sUHaCpaTmXnzplv65x6Eh3S1ldPON/cJuU6ldPbok1ld7q8iUaUhjE31jry8zdahWurG3r/ZppaH8I6Ym93ErqPlPrv3HDa0rMXV1klsxnZtqx1zS6wcc+kGcpbSwpnEsj7Rkl0MXRloaFO0b++W9pqI6S6MSfOOsPmiqtlEa59eLjysMfV5laHpfX+3zKkN5FaZ+kupW1+ZelJyQ3jno0OgelnqGNI1T2ygt2+vQsO6WzvfrxfO7TPUO82iEXy/eOGCqkymN8evFe6Wmyr6UJvn9zn5y1NAnR01N6eNWYHMv9tYYeq/U1I+S3Ipu7sVRl/TGAYcui7PUu7kXjR7p+V0OZUZausivF8v2mureWRrp14t3Dpqqc0vXJftqeRWGdlYbuinNV/usytDHFaYmpLoV2tyLQycMrT5oKqenpR5dmsY53iC9vM+h4TGW0v2OX0t2mTov3KMsv+PXvw6Y6uyQrvQ7fq0rMXW4Tpro14stRwwVVJqamuZWQPMv7R6nofVlpq5Ndiuq+fh1pE56s8ihy+Mt9eraNE6DJb2w26GBUZYGRvnGfmmvqbhg6fJ43zirik3VW9K1fr34sNzQHqehqX692HHM0IbDpib2ciuk+dWSY3nrjuWSVFpaquLiYu/jjIwMuVwuFRYWemtpaWkKDg7W1q1bvbXExETFx8dr48aN3lp0dLR69er1tSc1/q8Ocxlr5syZWrVqlT744AP17NlTkrR06VJNnz5dLperxbpDhw7VFVdcoUceeUQzZszQgQMHtHr1au/yEydOKCQkRG+//bbGjBnzlbFcLleLbTqdTiUmJnIZCwCAM6C9L2N1iDM7s2fP1ooVK/T+++97g44kxcXFqb6+XlVVVS3O7pSXlysuLs67jn/aO7n85LJTCQoKUlBQUBvvBQAA6Ija9Z4dj8ej2bNn6/XXX9e6deuUmpraYvmgQYMUGBio3Nxcb23nzp0qKipSVlaWJCkrK0vbtm1TRUWFd501a9YoLCxM6enpZ2dHAABAh9WuZ3ZmzZqlpUuX6o033lDXrl2999iEh4crODhY4eHhuvnmmzVv3jxFRkYqLCxMc+bMUVZWloYPHy5JGj16tNLT0zVlyhQtXLhQZWVluvfeezVr1izO3gAAgPYNO3/5y18kSZdffnmL+uLFi3XTTTdJkh5//HGZpqnx48fL5XIpJydHTz/9tHddh8OhFStWaObMmcrKylJISIimTZumBx988GztBgAA6MA6zA3K7YnP2QEA4Mxp7xuUO9Tn7AAAALQ1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALC1dg0777//vq655holJCTIMAz961//arHc4/Ho/vvvV3x8vIKDg5Wdna1du3a1WKeyslKTJ09WWFiYIiIidPPNN+v48eNncS8AAEBH1q5hp7a2VhdeeKGeeuqpUy5fuHChnnzySS1atEgbNmxQSEiIcnJyVFdX511n8uTJ2rFjh9asWaMVK1bo/fff14wZM87WLgAAgA4uoD0HHzNmjMaMGXPKZR6PR0888YTuvfdeXXvttZKk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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from qbraid.visualization import plot_histogram\n", - "\n", - "plot_histogram(counts)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6d95cc0b", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.11.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "405809fb", + "metadata": { + "tags": [ + "hide_cell" + ] + }, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "\n", + "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), \"..\")))" + ] + }, + { + "cell_type": "markdown", + "id": "d3b8876c", + "metadata": {}, + "source": [ + "## Bernstein Vazirani Algorithm\n", + "#### In this tutorial, we will demonstrate the capabilities of qBraid Algorithms Bernstein Vazirani Module\n", + "Begin by importing the module from qBraid Algorithms library" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e939fb23", + "metadata": {}, + "outputs": [], + "source": [ + "import pyqasm\n", + "from qbraid_algorithms import bernstein_vazirani as bv" + ] + }, + { + "cell_type": "markdown", + "id": "3545fac9", + "metadata": {}, + "source": [ + "To load a full Bernstein Vazirani algorithm circuit as a PyQASM module, simply pass the secret string to be encoded in the oracle to the `load_program()` method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97649823", + "metadata": {}, + "outputs": [], + "source": [ + "module = bv.generate_program(\"1011\")" + ] + }, + { + "cell_type": "markdown", + "id": "7e2ac473", + "metadata": {}, + "source": [ + "We can perform all standard [PyQASM](https://docs.qbraid.com/pyqasm/user-guide/overview) operations on the module, such as unrolling." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0fb825a7", + "metadata": {}, + "outputs": [], + "source": [ + "module.unroll()" + ] + }, + { + "cell_type": "markdown", + "id": "287b4aa8", + "metadata": {}, + "source": [ + "Below, we display the unrolled circuit, which includes preparing the input and ancilla qubits, applying the '1011' oracle, applying Hadamard gates after the oracle, then measuring the results." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "31842ddc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "qubit[1] ancilla;\n", + "qubit[4] q;\n", + "bit[4] b;\n", + "h q[0];\n", + "h q[1];\n", + "h q[2];\n", + "h q[3];\n", + "x ancilla[0];\n", + "h ancilla[0];\n", + "cx q[0], ancilla[0];\n", + "cx q[2], ancilla[0];\n", + "cx q[3], ancilla[0];\n", + "h q[0];\n", + "h q[1];\n", + "h q[2];\n", + "h q[3];\n", + "b[0] = measure q[0];\n", + "b[1] = measure q[1];\n", + "b[2] = measure q[2];\n", + "b[3] = measure q[3];\n", + "\n" + ] + } + ], + "source": [ + "module_str = pyqasm.dumps(module)\n", + "print(module_str)" + ] + }, + { + "cell_type": "markdown", + "id": "21906ab0", + "metadata": {}, + "source": [ + "## Using B-V in your own OpenQASM3 program\n", + "#### qBraid algorithms makes it easy to incorporate either the full Bernstein Vazirani algorithm - or just the encoded oracle - into your own OpenQASM3 circuit.\n", + "To use a secret string-encoded oracle in your circuit, first generate the oracle submodule using the `generate_oracle` method, which takes a secret string. The method will create a QASM3 file containing your oracle as a subroutine within your current working directory." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "6f3e9784", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Oracle 'oracle' has been added to /Users/lukeandreesen/qbraid_algos/examples/oracle.qasm\n" + ] + } + ], + "source": [ + "bv.generate_oracle(\"111\")" + ] + }, + { + "cell_type": "markdown", + "id": "61817da7", + "metadata": {}, + "source": [ + "Below, we can see the custom oracle subroutine that you now have access to. To use this in your own circuit, simply add `include \"oracle.qasm\";` to your OpenQASM file, and call the `oracle` subroutine by passing an appropriately sized register of qubits and an ancilla qubit." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "8833f4b9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\r\n", + "include \"stdgates.inc\";\r\n", + "\r\n", + "def oracle(qubit[3] q, qubit[1] ancilla) {\r\n", + " int[32] s = 7;\r\n", + " int[16] n = 3;\r\n", + " for int i in [0:n - 1] {\r\n", + " if ((s >> i) & 1) {\r\n", + " cx q[i], ancilla[0];\r\n", + " }\r\n", + " }\r\n", + "}\r\n" + ] + } + ], + "source": [ + "%cat oracle.qasm" + ] + }, + { + "cell_type": "markdown", + "id": "3672d119", + "metadata": {}, + "source": [ + "Similarly, you can generate an entire Bernstein Vazirani circuit as a submodule using the `save_to_qasm` method, again passing your desired secret string." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "5e37f60d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Subroutine 'bernvaz' has been added to /Users/lukeandreesen/qbraid_algos/examples/bernvaz.qasm\n" + ] + } + ], + "source": [ + "subroutine = bv.save_to_qasm(\"011\")" + ] + }, + { + "cell_type": "markdown", + "id": "05cfcdf4", + "metadata": {}, + "source": [ + "To use the subroutine in your own circuit, add `include \"bernvaz.qasm\";` to your OpenQASM file, and call the `bernvaz` method by passing an appropriately sized register of qubits, as well as an ancilla qubit." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "38b7beb3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\r\n", + "include \"stdgates.inc\";\r\n", + "\r\n", + "def bernvaz(qubit[3] q, qubit[1] ancilla) {\r\n", + " int[32] s = 6;\r\n", + " int[16] n = 3;\r\n", + " for int i in [0:n - 1] {\r\n", + " h q[i];\r\n", + " }\r\n", + " x ancilla[0];\r\n", + " h ancilla[0];\r\n", + " // Note: Nested subroutine calls not yet supported by QASM, so manually insert\r\n", + " for int i in [0:n - 1] {\r\n", + " if ((s >> i) & 1) {\r\n", + " cx q[i], ancilla[0];\r\n", + " }\r\n", + " }\r\n", + " for int i in [0:n - 1] {\r\n", + " h q[i];\r\n", + " }\r\n", + "\r\n", + "}\r\n" + ] + } + ], + "source": [ + "%cat bernvaz.qasm" + ] + }, + { + "cell_type": "markdown", + "id": "e7d3dd7e", + "metadata": {}, + "source": [ + "## Running Algorithms on qBraid\n", + "Running algorithms on qBraid is simple. First, import QbraidProvider from qBraid Runtime. Visit [here](https://docs.qbraid.com/sdk/user-guide/providers/native#qbraidprovider) for more information." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "96870043", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/homebrew/lib/python3.11/site-packages/pennylane/capture/capture_operators.py:33: RuntimeWarning: PennyLane is not yet compatible with JAX versions > 0.4.28. You have version 0.4.30 installed. Please downgrade JAX to <=0.4.28 to avoid runtime errors.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "from qbraid.runtime import QbraidProvider" + ] + }, + { + "cell_type": "markdown", + "id": "f3ffffdf", + "metadata": {}, + "source": [ + "If you have not yet configured QbraidProvider, provide your API key." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "b82efa4b", + "metadata": {}, + "outputs": [], + "source": [ + "# provider = QbraidProvider(api_key='API_KEY')\n", + "provider = QbraidProvider()" + ] + }, + { + "cell_type": "markdown", + "id": "539d7239", + "metadata": {}, + "source": [ + "We'll run our program on qBraid's QIR simulator." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "a273c97e", + "metadata": {}, + "outputs": [], + "source": [ + "device = provider.get_device(\"qbraid_qir_simulator\")" + ] + }, + { + "cell_type": "markdown", + "id": "8495dbad", + "metadata": {}, + "source": [ + "To run the job, simply pass a QASM string to the device. If you have a PyQASM module, use `dumps` to generate a QASM string" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51206d57", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "qubit[1] ancilla;\n", + "qubit[3] q;\n", + "bit[3] b;\n", + "h q[0];\n", + "h q[1];\n", + "h q[2];\n", + "x ancilla[0];\n", + "h ancilla[0];\n", + "cx q[0], ancilla[0];\n", + "cx q[2], ancilla[0];\n", + "h q[0];\n", + "h q[1];\n", + "h q[2];\n", + "b[0] = measure q[0];\n", + "b[1] = measure q[1];\n", + "b[2] = measure q[2];\n", + "\n" + ] + } + ], + "source": [ + "secret_string = \"101\"\n", + "module = bv.generate_program(secret_string)\n", + "module.unroll()\n", + "qasm_str = pyqasm.dumps(module)\n", + "print(qasm_str)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "358fcbc7", + "metadata": {}, + "outputs": [], + "source": [ + "job = device.run(qasm_str, shots=500)" + ] + }, + { + "cell_type": "markdown", + "id": "53858d48", + "metadata": {}, + "source": [ + "We can now get the counts from the job results." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "9a8f91c5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'1010': 500}\n" + ] + } + ], + "source": [ + "results = job.result()\n", + "counts = results.data.get_counts()" + ] + }, + { + "cell_type": "markdown", + "id": "b00c560b", + "metadata": {}, + "source": [ + "We can now check the counts to see if the most frequent value is our secret string. This particular backend includes the ancilla qubit as the least significant bit, so we'll remove that." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "b14a37ce", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Secret String: 101. B-V result string: 101\n" + ] + } + ], + "source": [ + "# Remove ancilla qubit from bitstring\n", + "processed_counts = {bitstr[:-1]: count for bitstr, count in counts.items()}\n", + "# Find most frequent count\n", + "max_str = max(processed_counts, key=counts.get)\n", + "\n", + "print(f\"Secret String: {secret_string}. B-V result string: {result_string}\")" + ] + }, + { + "cell_type": "markdown", + "id": "29cbdab1", + "metadata": {}, + "source": [ + "We see that the algorithm successfully identified the secret string. Finally, we can plot the results using qBraid Visualization." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "e84ea61e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAG3CAYAAABSTJRlAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA8nElEQVR4nO3de3QU9f3/8dfMJoSQkIQk5Aa5AUGIBoNcI9YLpEREqwULKAWkVnr4Av6Ear3US7E9otiqtdXytbWgViripSqICKFi1cglGLmohHsCuQEh2RDMJtnZ3x8Ju5uvaDUEEsbn45yc475ndj6feUtmX5mZ3TU8Ho9HAAAANmW29wQAAADOJMIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwtXYPO4cOHdJPf/pTRUVFKTg4WBkZGdq8ebN3ucfj0f3336/4+HgFBwcrOztbu3btarGNyspKTZ48WWFhYYqIiNDNN9+s48ePn+1dAQAAHVC7hp1jx45pxIgRCgwM1KpVq/TZZ5/pD3/4g7p16+ZdZ+HChXryySe1aNEibdiwQSEhIcrJyVFdXZ13ncmTJ2vHjh1as2aNVqxYoffff18zZsxoj10CAAAdjNGeXwR611136cMPP9R//vOfUy73eDxKSEjQL3/5S91+++2SpOrqasXGxmrJkiWaNGmSPv/8c6Wnp2vTpk0aPHiwJOmdd97RVVddpYMHDyohIeGs7Q8AAOh4Atpz8DfffFM5OTn6yU9+ovXr16tHjx76n//5H91yyy2SpH379qmsrEzZ2dne54SHh2vYsGHKy8vTpEmTlJeXp4iICG/QkaTs7GyZpqkNGzboxz/+8VfGdblccrlc3seWZamyslJRUVEyDOMM7jEAAGgrHo9HNTU1SkhIkGl+/cWqdg07e/fu1V/+8hfNmzdP99xzjzZt2qRbb71VnTp10rRp01RWViZJio2NbfG82NhY77KysjLFxMS0WB4QEKDIyEjvOv/XggULNH/+/DOwRwAA4GwrLi5Wz549v3Z5u4Ydy7I0ePBgPfTQQ5KkgQMHavv27Vq0aJGmTZt2xsa9++67NW/ePO/j6upqJSUlqbi4WGFhYWdsXAAA0HacTqcSExPVtWvXb1yvXcNOfHy80tPTW9T69++vV199VZIUFxcnSSovL1d8fLx3nfLycmVmZnrXqaioaLGNxsZGVVZWep//fwUFBSkoKOgr9bCwMMIOAADnmP92C0q7vhtrxIgR2rlzZ4taYWGhkpOTJUmpqamKi4tTbm6ud7nT6dSGDRuUlZUlScrKylJVVZXy8/O966xbt06WZWnYsGFnYS8AAEBH1q5ndubOnauLL75YDz30kCZMmKCNGzfqmWee0TPPPCOpKanddttt+t3vfqe0tDSlpqbqvvvuU0JCgq677jpJTWeCrrzySt1yyy1atGiRGhoaNHv2bE2aNIl3YgEAgPZ967kkrVixQnfffbd27dql1NRUzZs3z/tuLKnpTusHHnhAzzzzjKqqqnTJJZfo6aefVt++fb3rVFZWavbs2XrrrbdkmqbGjx+vJ598UqGhod9qDk6nU+Hh4aquruYyFgAA54hv+/rd7mGnIyDsAABw7vm2r9/t/nURAAAAZxJhBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphB8A5LSUlReedd54yMzOVmZmpZcuWSZJ27dqliy++WH379tWQIUO0Y8cO73O+aRkA+yHsADjnLVu2TAUFBSooKNDEiRMlSb/4xS80Y8YMFRYW6s4779RNN93kXf+blgGwH8IOANupqKjQ5s2b9dOf/lSSNH78eBUXF2v37t3fuAyAPRF2AJzzpk6dqoyMDN188806fPiwiouLFR8fr4CAAEmSYRhKSkpSUVHRNy4DYE+EHQDntPfff19bt27Vli1bFB0drWnTprX3lAB0MAHtPQEAOB1JSUmSpMDAQN12223q27evEhMTVVpaqsbGRgUEBMjj8aioqEhJSUkKCwv72mUA7IkzOwDOWbW1taqqqvI+/uc//6mBAwcqJiZGF110kf7xj39Ikl599VX17NlTffr0+cZlAOzJ8Hg8nvaeRHtzOp0K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NX36dLlcrhbPGzp0qK644go98sgjmjFjhg4cOKDVq1d7l584cUIhISF6++23NWbMmFOO7XK5WmzX6XQqMTFR1dXVCgsLa9P9TLlrZZtuDwCAc83+h8eeke06nU6Fh4f/19fvdruMlZ+fr4qKCl100UUKCAhQQECA1q9fryeffFIBAQGKjY1VfX29qqqqWjyvvLxccXFxkqS4uLivvDvr5OOT65xKUFCQwsLCWvwAAAB7arewM2rUKG3btk0FBQXen8GDB2vy5Mne/w4MDFRubq73OTt37lRRUZGysrIkSVlZWdq2bZsqKiq866xZs0ZhYWFKT08/6/sEAAA6nna7Z6dr16664IILWtRCQkIUFRXlrd98882aN2+eIiMjFRYWpjlz5igrK0vDhw+XJI0ePVrp6emaMmWKFi5cqLKyMt17772aNWuWgoKCzvo+AQCAjqddb1D+bx5//HGZpqnx48fL5XIpJydHTz/9tHe5w+HQihUrNHPmTGVlZSkkJETTpk3Tgw8+2I6zBgAAHUm73aDckXzbG5xagxuUAQDfd9/bG5QBAADOBsIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwtVaFnS1btmjbtm3ex2+88Yauu+463XPPPaqvr2+zyQEAAJyuVoWdX/ziFyosLJQk7d27V5MmTVKXLl20fPly/epXv2rTCQIAAJyOVoWdwsJCZWZmSpKWL1+uSy+9VEuXLtWSJUv06quvtuX8AAAATkurwo7H45FlWZKktWvX6qqrrpIkJSYm6siRI203OwAAgNPUqrAzePBg/e53v9MLL7yg9evXa+zYsZKkffv2KTY2tk0nCAAAcDpaFXYef/xxbdmyRbNnz9avf/1r9enTR5L0yiuv6OKLL27TCQIAAJyOgNY86cILL2zxbqyTHn30UQUEtGqTAAAAZ0Srzuz06tVLR48e/Uq9rq5Offv2Pe1JAQAAtJVWhZ39+/fL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Vr5T2Lnqqqt03333qa6u7ivLvvzySz3wwAO6+uqr22xyAAAAp+s7vfX83nvv1Wuvvaa+fftq9uzZOu+88yRJX3zxhZ566im53W79+te/PiMTBQAAaI3vFHZiY2P10UcfaebMmbr77rvl8XgkSYZhKCcnR0899ZRiY2PPyEQBAABa4zt/qGBycrLefvttHTt2TLt375bH41FaWpq6det2JuYHAABwWlr1CcqS1K1bNw0ZMqQt5wIAANDmWvXdWAAAAOcKwg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALC1dg07CxYs0JAhQ9S1a1fFxMTouuuu086dO1usU1dXp1mzZikqKkqhoaEaP368ysvLW6xTVFSksWPHqkuXLoqJidEdd9yhxsbGs7krAACgg2rXsLN+/XrNmjVLH3/8sdasWaOGhgaNHj1atbW13nXmzp2rt956S8uXL9f69etVUlKicePGeZe73W6NHTtW9fX1+uijj/Tcc89pyZIluv/++9tjlwAAQAdjeDweT3tP4qTDhw8rJiZG69ev16WXXqrq6mp1795dS5cu1fXXXy9J+uKLL9S/f3/l5eVp+PDhWrVqla6++mqVlJQoNjZWkrRo0SLdeeedOnz4sDp16vRfx3U6nQoPD1d1dbXCwsLadJ9S7lrZptsDAOBcs//hsWdku9/29btD3bNTXV0tSYqMjJQk5efnq6GhQdnZ2d51+vXrp6SkJOXl5UmS8vLylJGR4Q06kpSTkyOn06kdO3acchyXyyWn09niBwAA2FNAe0/gJMuydNttt2nEiBG64IILJEllZWXq1KmTIiIiWqwbGxursrIy7zr+Qefk8pPLTmXBggWaP3/+V+qbN29WaGioJCkzM1M1NTXas2ePd3m/fv3kcDhahKiUlBRFRUUpPz+/xfjJyckqKCjQz/q6JUkHaw29e8jUlT3dSujStF5Ng7R8n0NZMZb6R/hOsC0uNNU/wqPhMb7aa/tNhQRIOT0tby23xNTROmlCL18t/4ihTytN3ZTmlmk01XY5Df2nzNSPk93qFtRUq6iTVhQ5NDLBUkpo0zguS3pxt0ODoi1dGOkbe+keUz1CpMvifOOsLDZleaRrkny1D8oN7asxNKWPr7btmKFNh03d0NutYEdTrei4obUlpq5KdCsuuKlW3SC9us+hEbGWzgv3jf33Qocu6GZpaHdf7dX9proGSqN7+MZZe8hUVb10faqvtumwoW3HTE3v61ZzK1RYbeiDclPjUtyKaD7pV/6ltLLYoVEJlpKbe1HnlpbucWhwtKUBfr14cbepxFCPLo3z1VYUNf3NcLVfL94vM1R83NBkv15srTS0+YipG3u71bm5FweOG8otMTU20a3Y5l5U1Uuv7XfoklhLfZt74ZG0uNChjG6Whvj14pV9piI6Sdl+vXj3kKmaBml8iq+28bCh7cdM779HSdpZbejDclPjU90KD2yqlX0pvV3sUHaCpaTmXnzplv65x6Eh3S1ldPON/cJuU6ldPbok1ld7q8iUaUhjE31jry8zdahWurG3r/ZppaH8I6Ym93ErqPlPrv3HDa0rMXV1klsxnZtqx1zS6wcc+kGcpbSwpnEsj7Rkl0MXRloaFO0b++W9pqI6S6MSfOOsPmiqtlEa59eLjysMfV5laHpfX+3zKkN5FaZ+kupW1+ZelJyQ3jno0OgelnqGNI1T2ygt2+vQsO6WzvfrxfO7TPUO82iEXy/eOGCqkymN8evFe6Wmyr6UJvn9zn5y1NAnR01N6eNWYHMv9tYYeq/U1I+S3Ipu7sVRl/TGAYcui7PUu7kXjR7p+V0OZUZausivF8v2mureWRrp14t3Dpqqc0vXJftqeRWGdlYbuinNV/usytDHFaYmpLoV2tyLQycMrT5oKqenpR5dmsY53iC9vM+h4TGW0v2OX0t2mTov3KMsv+PXvw6Y6uyQrvQ7fq0rMXW4Tpro14stRwwVVJqamuZWQPMv7R6nofVlpq5Ndiuq+fh1pE56s8ihy+Mt9eraNE6DJb2w26GBUZYGRvnGfmmvqbhg6fJ43zirik3VW9K1fr34sNzQHqehqX692HHM0IbDpib2ciuk+dWSY3nrjuWSVFpaquLiYu/jjIwMuVwuFRYWemtpaWkKDg7W1q1bvbXExETFx8dr48aN3lp0dLR69er1tSc1/q8Ocxlr5syZWrVqlT744AP17NlTkrR06VJNnz5dLperxbpDhw7VFVdcoUceeUQzZszQgQMHtHr1au/yEydOKCQkRG+//bbGjBnzlbFcLleLbTqdTiUmJnIZCwCAM6C9L2N1iDM7s2fP1ooVK/T+++97g44kxcXFqb6+XlVVVS3O7pSXlysuLs67jn/aO7n85LJTCQoKUlBQUBvvBQAA6Ija9Z4dj8ej2bNn6/XXX9e6deuUmpraYvmgQYMUGBio3Nxcb23nzp0qKipSVlaWJCkrK0vbtm1TRUWFd501a9YoLCxM6enpZ2dHAABAh9WuZ3ZmzZqlpUuX6o033lDXrl2999iEh4crODhY4eHhuvnmmzVv3jxFRkYqLCxMc+bMUVZWloYPHy5JGj16tNLT0zVlyhQtXLhQZWVluvfeezVr1izO3gAAgPYNO3/5y18kSZdffnmL+uLFi3XTTTdJkh5//HGZpqnx48fL5XIpJydHTz/9tHddh8OhFStWaObMmcrKylJISIimTZumBx988GztBgAA6MA6zA3K7YnP2QEA4Mxp7xuUO9Tn7AAAALQ1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALA1wg4AALC1dg0777//vq655holJCTIMAz961//arHc4/Ho/vvvV3x8vIKDg5Wdna1du3a1WKeyslKTJ09WWFiYIiIidPPNN+v48eNncS8AAEBH1q5hp7a2VhdeeKGeeuqpUy5fuHChnnzySS1atEgbNmxQSEiIcnJyVFdX511n8uTJ2rFjh9asWaMVK1bo/fff14wZM87WLgAAgA4uoD0HHzNmjMaMGXPKZR6PR0888YTuvfdeXXvttZKk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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from qbraid.visualization import plot_histogram\n", + "\n", + "plot_histogram(counts)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6d95cc0b", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/demo_hamiltonians.ipynb b/examples/evolutions.ipynb similarity index 93% rename from examples/demo_hamiltonians.ipynb rename to examples/evolutions.ipynb index 5387527..9d31ad8 100644 --- a/examples/demo_hamiltonians.ipynb +++ b/examples/evolutions.ipynb @@ -47,7 +47,8 @@ "from qbraid_algorithms.embedding import PauliOperator, Prep, PrepSelLibrary, Select\n", "from qbraid_algorithms.qtran import QasmBuilder, std_gates, GateLibrary, GateBuilder\n", "from qbraid_algorithms.amplitude_amplification import AALibrary\n", - "np.set_printoptions(linewidth=np.inf,precision=2,suppress=True)" + "\n", + "np.set_printoptions(linewidth=np.inf, precision=2, suppress=True)" ] }, { @@ -70,8 +71,8 @@ } ], "source": [ - "print(\"Quantum Algorithms Demo\",'\\n',\"=\" * 40)\n", - "print(\"Testing GQSP, Trotter, and Prep-Select algorithms\",'\\n',\"=\" * 40)\n", + "print(\"Quantum Algorithms Demo\", \"\\n\", \"=\" * 40)\n", + "print(\"Testing GQSP, Trotter, and Prep-Select algorithms\", \"\\n\", \"=\" * 40)\n", "\n", "# Initialize test environment\n", "test_hamiltonians = create_test_hamiltonians(reg_size=3)\n", @@ -189,15 +190,14 @@ } ], "source": [ - "\n", "def demo_gqsp_configurations():\n", " \"\"\"Compare basic GQSP with different depth configurations.\"\"\"\n", " print(\"\\nGQSP Algorithm - Basic vs Multi-Depth\")\n", " print(\"-\" * 42)\n", - " \n", + "\n", " # Get a test Hamiltonian\n", " hamiltonian = list(test_hamiltonians.values())[0]\n", - " \n", + "\n", " # Configuration 1: Basic GQSP (depth=1)\n", " print(\"Configuration 1: Basic GQSP (depth=1)\")\n", " basic_phases = [0.1, 0.2, 0.3] # 2*1 + 1 = 3 phases\n", @@ -211,61 +211,64 @@ " class BasicHam(hamiltonian):\n", " def apply(self, *args, **kwargs):\n", " super().apply(0.1, *args, **kwargs)\n", + "\n", " def controlled(self, *args, **kwargs):\n", " super().controlled(0.1, *args, **kwargs)\n", - " \n", + "\n", " try:\n", " # Apply GQSP with basic Hamiltonian\n", " gqsp1.GQSP(test_qubits, basic_phases, BasicHam, depth=1)\n", " std1.measure(test_qubits, test_qubits)\n", " basic_program = builder1.build()\n", - " \n", + "\n", " print(f\"Basic GQSP: {len(basic_program)} characters\")\n", " print(f\"\\tPhases used: {len(basic_phases)}\")\n", " print(basic_program)\n", - " \n", + "\n", " except Exception as e:\n", " print(f\"Basic GQSP failed: {str(e)}\")\n", " return\n", - " \n", + "\n", " # Configuration 2: Multi-depth GQSP (depth=3)\n", " print(\"\\nConfiguration 2: Multi-depth GQSP (depth=3)\")\n", " multi_phases = [0.1, 0.2, 0.3, 0.15, 0.25, 0.35, 0.05] # 2*3 + 1 = 7 phases\n", - " \n", + "\n", " builder2 = QasmBuilder(3)\n", " std2 = builder2.import_library(std_gates)\n", " gqsp2 = builder2.import_library(GQSP)\n", - " \n", + "\n", " class MultiHam(hamiltonian):\n", " def apply(self, *args, **kwargs):\n", " super().apply(0.1, *args, **kwargs)\n", + "\n", " def controlled(self, *args, **kwargs):\n", " super().controlled(0.1, *args, **kwargs)\n", - " \n", + "\n", " try:\n", " gqsp2.GQSP(test_qubits, multi_phases, MultiHam, depth=3)\n", " std2.measure(test_qubits, test_qubits)\n", " multi_program = builder2.build()\n", - " \n", + "\n", " print(f\"Multi-depth GQSP: {len(multi_program)} characters\")\n", " print(f\"\\tPhases used: {len(multi_phases)}\")\n", " print(multi_program)\n", - " \n", + "\n", " except Exception as e:\n", " print(f\"Multi-depth GQSP failed: {str(e)}\")\n", " return\n", - " \n", + "\n", " # Compare configurations\n", " print(f\"\\nComparison:\")\n", " print(f\" Basic (depth=1): {len(basic_program)} chars, {len(basic_phases)} phases\")\n", " print(f\" Multi (depth=3): {len(multi_program)} chars, {len(multi_phases)} phases\")\n", " print(f\" Size ratio: {len(multi_program) / len(basic_program):.2f}x\")\n", - " \n", + "\n", " return {\n", - " 'basic': {'length': len(basic_program), 'phases': len(basic_phases)},\n", - " 'multi': {'length': len(multi_program), 'phases': len(multi_phases)}\n", + " \"basic\": {\"length\": len(basic_program), \"phases\": len(basic_phases)},\n", + " \"multi\": {\"length\": len(multi_program), \"phases\": len(multi_phases)},\n", " }\n", "\n", + "\n", "# Run GQSP configurations demo\n", "gqsp_results = demo_gqsp_configurations()" ] @@ -392,43 +395,43 @@ " \"\"\"Compare two-Hamiltonian and multi-Hamiltonian Trotter decomposition.\"\"\"\n", " print(\"\\nTrotter Decomposition - Two vs Multi-Hamiltonian\")\n", " print(\"-\" * 52)\n", - " \n", + "\n", " hamiltonians = list(test_hamiltonians.values())\n", - " \n", + "\n", " # Configuration 1: Two-Hamiltonian Trotter\n", " print(\"Configuration 1: Two-Hamiltonian Trotter (Suzuki)\")\n", " ham1, ham2 = hamiltonians[0], hamiltonians[1]\n", - " \n", + "\n", " builder1 = QasmBuilder(3)\n", " std1 = builder1.import_library(std_gates)\n", " trotter1 = builder1.import_library(Trotter)\n", - " \n", + "\n", " try:\n", " trotter1.trot_suz(test_qubits, \"0.5\", ham1, ham2, depth=2)\n", " std1.measure(test_qubits, test_qubits)\n", " two_ham_program = builder1.build()\n", - " \n", + "\n", " print(f\"Two-Hamiltonian: {len(two_ham_program)} characters\")\n", " print(f\"\\tMethod: Suzuki-Trotter, Depth: 2\")\n", " print(two_ham_program)\n", - " \n", + "\n", " except Exception as e:\n", " print(f\"Two-Hamiltonian Trotter failed: {str(e)}\")\n", " return\n", - " \n", + "\n", " # Configuration 2: Multi-Hamiltonian Trotter\n", " print(\"\\nConfiguration 2: Multi-Hamiltonian Trotter\")\n", " multi_hams = hamiltonians[:3] # Use 3 Hamiltonians\n", - " \n", + "\n", " builder2 = QasmBuilder(3)\n", " std2 = builder2.import_library(std_gates)\n", " trotter2 = builder2.import_library(Trotter)\n", - " \n", + "\n", " try:\n", " trotter2.multi_trot_suz(test_qubits, \"0.4\", multi_hams, depth=2)\n", " std2.measure(test_qubits, test_qubits)\n", " multi_ham_program = builder2.build()\n", - " \n", + "\n", " print(f\"Multi-Hamiltonian: {len(multi_ham_program)} characters\")\n", " print(f\"\\tHamiltonians: 3, Depth: 2\")\n", " # print(multi_ham_program)\n", @@ -436,26 +439,26 @@ " except Exception as e:\n", " print(f\"Multi-Hamiltonian Trotter failed: {str(e)}\")\n", " return\n", - " \n", + "\n", " # Configuration 3: Linear Trotter (bonus comparison)\n", " print(\"\\nConfiguration 3: Linear Trotter Decomposition\")\n", - " \n", + "\n", " builder3 = QasmBuilder(3)\n", " std3 = builder3.import_library(std_gates)\n", " trotter3 = builder3.import_library(Trotter)\n", - " \n", + "\n", " try:\n", " trotter3.trot_linear(test_qubits, \"0.2\", hamiltonians[:2], steps=4)\n", " std3.measure(test_qubits, test_qubits)\n", " linear_program = builder3.build()\n", - " \n", + "\n", " print(f\"Linear Trotter: {len(linear_program)} characters\")\n", " print(f\"Method: First-order, Steps: 4\")\n", - " \n", + "\n", " except Exception as e:\n", " print(f\"Linear Trotter failed: {str(e)}\")\n", " linear_program = \"\"\n", - " \n", + "\n", " # Compare all configurations\n", " print(f\"Trotter Configuration Comparison:\")\n", " print(f\"\\tTwo-Hamiltonian (Suzuki): {len(two_ham_program)} chars\")\n", @@ -464,11 +467,12 @@ " print(f\"\\tLinear decomposition: {len(linear_program)} chars\")\n", "\n", " return {\n", - " 'two_ham': len(two_ham_program),\n", - " 'multi_ham': len(multi_ham_program),\n", - " 'linear': len(linear_program) if linear_program else 0\n", + " \"two_ham\": len(two_ham_program),\n", + " \"multi_ham\": len(multi_ham_program),\n", + " \"linear\": len(linear_program) if linear_program else 0,\n", " }\n", "\n", + "\n", "# Run Trotter configurations demo\n", "trotter_results = demo_trotter_configurations()" ] @@ -577,76 +581,75 @@ } ], "source": [ - "\n", "def demo_prep_select_configurations():\n", " \"\"\"Compare prep-select with matrix input vs operator chain input.\"\"\"\n", " print(\"\\nPreparation-Selection - Matrix vs Operator Chain\")\n", " print(\"-\" * 52)\n", - " \n", + "\n", " test_qubits = [*range(4)]\n", - " \n", + "\n", " # Configuration 1: Matrix Input\n", " print(\"Configuration 1: Matrix Input\")\n", " test_matrices = [\n", " (\"Pauli-Z\", np.array([[1, 0], [0, -1]])),\n", - " (\"Random 4x4\", np.random.random((4, 4)) + 1j * np.random.random((4, 4)))\n", + " (\"Random 4x4\", np.random.random((4, 4)) + 1j * np.random.random((4, 4))),\n", " ]\n", - " \n", + "\n", " matrix_results = {}\n", - " \n", + "\n", " for name, matrix in test_matrices:\n", " print(f\"Testing {name} matrix {matrix.shape}...\")\n", - " \n", + "\n", " builder = QasmBuilder(3)\n", " std = builder.import_library(std_gates)\n", " prep_sel = builder.import_library(PrepSelLibrary)\n", - " \n", + "\n", " try:\n", " prep_sel.prep_select(test_qubits, matrix, approximate=0.1)\n", " std.measure(test_qubits, test_qubits)\n", - " \n", + "\n", " program = builder.build()\n", " matrix_results[name] = len(program)\n", - " \n", + "\n", " print(f\"{name}: {len(program)} characters\")\n", - " \n", + "\n", " except Exception as e:\n", " matrix_results[name] = 0\n", " print(f\"{name}: {str(e)}\")\n", - " \n", + "\n", " # Configuration 2: Operator Chain Input\n", " print(\"\\nConfiguration 2: Operator Chain Input\")\n", " test_chains = [\n", " (\"Single Pauli\", [(\"X\", 0.5), (\"Z\", 0.3), (\"Y\", 0.2)]),\n", - " (\"Two-qubit Pauli\", [(\"XX\", 0.7), (\"ZZ\", 0.4), (\"XY\", 0.1)])\n", + " (\"Two-qubit Pauli\", [(\"XX\", 0.7), (\"ZZ\", 0.4), (\"XY\", 0.1)]),\n", " ]\n", - " \n", + "\n", " chain_results = {}\n", - " \n", + "\n", " for name, chain in test_chains:\n", " print(f\"Testing {name} chain ({len(chain)} operators)...\")\n", - " \n", + "\n", " builder = QasmBuilder(3)\n", " std = builder.import_library(std_gates)\n", " prep_sel = builder.import_library(PrepSelLibrary)\n", - " \n", + "\n", " try:\n", " prep_sel.prep_select(test_qubits[:2], chain)\n", " std.measure(test_qubits, test_qubits)\n", - " \n", + "\n", " program = builder.build()\n", " chain_results[name] = len(program)\n", - " \n", + "\n", " operators = [op for op, _ in chain]\n", " print(f\"\\t{name}: {len(program)} characters\")\n", " print(f\"\\tOperators: {operators}\")\n", - " if name== \"Single Pauli\":\n", + " if name == \"Single Pauli\":\n", " print(program)\n", "\n", " except Exception as e:\n", " chain_results[name] = 0\n", " print(f\"{name}: {str(e)}\")\n", - " \n", + "\n", " # Compare configurations\n", " print(f\"\\nPrep-Select Configuration Comparison:\")\n", " print(f\"Matrix inputs:\")\n", @@ -656,7 +659,8 @@ " for name, length in chain_results.items():\n", " print(f\"\\t{name}: {length} chars\")\n", "\n", - " return {'matrix': matrix_results, 'chain': chain_results}\n", + " return {\"matrix\": matrix_results, \"chain\": chain_results}\n", + "\n", "\n", "# Run prep-select configurations demo\n", "prep_select_results = demo_prep_select_configurations()" @@ -702,64 +706,69 @@ } ], "source": [ - "\n", "def demo_algorithm_integration():\n", " \"\"\"Demonstrate combining multiple algorithms in a single quantum program.\"\"\"\n", " print(\"\\n🔗 Algorithm Integration - Combined Pipeline\")\n", " print(\"-\" * 44)\n", - " \n", + "\n", " hamiltonians = list(test_hamiltonians.values())[:2]\n", - " \n", + "\n", " print(\"Building combined algorithm pipeline...\")\n", " print(\"Pipeline: Trotter → GQSP → Prep-Select\")\n", - " \n", + "\n", " builder = QasmBuilder(8)\n", " qubits = [*range(8)]\n", " std = builder.import_library(std_gates)\n", " gqsp = builder.import_library(GQSP)\n", " trotter = builder.import_library(Trotter)\n", " prep_sel = builder.import_library(PrepSelLibrary)\n", - " \n", + "\n", " class IntegratedHam(hamiltonians[0]):\n", " def apply(self, *args, **kwargs):\n", " super().apply(0.1, *args, **kwargs)\n", + "\n", " def controlled(self, *args, **kwargs):\n", " super().controlled(0.1, *args, **kwargs)\n", - " \n", + "\n", " try:\n", " # Step 1: Apply Trotter decomposition\n", " print(\" Step 1: Applying Trotter decomposition...\")\n", " trotter.trot_suz(qubits[:3], \"0.1\", hamiltonians[0], hamiltonians[1], depth=1)\n", - " \n", + "\n", " # Step 2: Apply GQSP\n", " print(\" Step 2: Applying GQSP...\")\n", " gqsp.GQSP(qubits[3:6], [0.1, 0.2, 0.3], IntegratedHam, depth=1)\n", - " \n", + "\n", " # Step 3: Apply prep-select\n", " print(\" Step 3: Applying prep-select...\")\n", " test_matrix = np.array([[1, 0], [0, -1]]) # Pauli-Z\n", " prep_sel.prep_select(qubits[6:], test_matrix)\n", - " \n", + "\n", " # Measure all qubits\n", " std.measure(qubits, qubits)\n", - " \n", + "\n", " # Build complete program\n", " integrated_program = builder.build()\n", - " \n", + "\n", " print(f\"Integrated pipeline: {len(integrated_program)} characters\")\n", " print(f\"\\tContains Trotter: {'trot_suz' in integrated_program}\")\n", - " print(f\"\\tContains GQSP: {'GQSP' in integrated_program or 'gqsp' in integrated_program.lower()}\")\n", - " print(f\"\\tContains PrepSelect: {'PS_' in integrated_program or 'prep' in integrated_program.lower()}\")\n", - " \n", + " print(\n", + " f\"\\tContains GQSP: {'GQSP' in integrated_program or 'gqsp' in integrated_program.lower()}\"\n", + " )\n", + " print(\n", + " f\"\\tContains PrepSelect: {'PS_' in integrated_program or 'prep' in integrated_program.lower()}\"\n", + " )\n", + "\n", " return {\n", - " 'success': True,\n", - " 'total_length': len(integrated_program),\n", - " 'algorithms_used': 3\n", + " \"success\": True,\n", + " \"total_length\": len(integrated_program),\n", + " \"algorithms_used\": 3,\n", " }\n", - " \n", + "\n", " except Exception as e:\n", " print(f\"Algorithm integration failed: {str(e)}\")\n", - " return {'success': False, 'error': str(e)}\n", + " return {\"success\": False, \"error\": str(e)}\n", + "\n", "\n", "# Run integration demo\n", "integration_results = demo_algorithm_integration()" @@ -829,7 +838,9 @@ "\n", "class Za(GateLibrary):\n", " \"\"\"Custom gate: controlled-Z on all qubits except index 2.\"\"\"\n", + "\n", " name = \"Z_on_two\"\n", + "\n", " def __init__(self, *args, **kwargs):\n", " super().__init__(*args, **kwargs)\n", "\n", @@ -854,7 +865,7 @@ " std.end_gate()\n", "\n", " # Collect gate definitions and imports\n", - " self.merge(*sys.build(),self.name)\n", + " self.merge(*sys.build(), self.name)\n", "\n", " def apply(self, qubits):\n", " \"\"\"Apply the custom gate to a set of qubits.\"\"\"\n", @@ -876,7 +887,7 @@ "print(prog)\n", "\n", "# res = pq.loads(prog)\n", - "# print(res)\n" + "# print(res)" ] }, { diff --git a/examples/hhl.ipynb b/examples/hhl.ipynb new file mode 100644 index 0000000..1cce1fe --- /dev/null +++ b/examples/hhl.ipynb @@ -0,0 +1,568 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3675066b", + "metadata": {}, + "source": [ + "# Harrow-Hassidim-Lloyd (HHL) Algorithm Example\n", + "\n", + "## Overview\n", + "\n", + "This notebook demonstrates the implementation of the **Harrow-Hassidim-Lloyd (HHL) algorithm**, a quantum algorithm for solving systems of linear equations of the form $A\\mathbf{x} = \\mathbf{b}$. The HHL algorithm provides exponential speedup over classical methods for certain classes of sparse, well-conditioned matrices.\n", + "\n", + "## What You'll Learn\n", + "\n", + "- How to implement the HHL algorithm using qBraid Algorithms\n", + "- How to create custom Hamiltonians for the algorithm\n", + "- How to generate and analyze QASM circuits for HHL\n", + "- Understanding the quantum circuit structure of the HHL algorithm\n", + "\n", + "## Algorithm Background\n", + "\n", + "The HHL algorithm consists of three main steps:\n", + "\n", + "1. **Quantum Phase Estimation (QPE)**: Estimates the eigenvalues of matrix $A$\n", + "2. **Controlled Rotation**: Applies rotations based on the estimated eigenvalues \n", + "3. **Inverse QPE**: Uncomputes the phase estimation to extract the solution\n", + "\n", + "\n", + "### Mathematical Foundation\n", + "\n", + "The HHL algorithm solves the linear system $A\\mathbf{x} = \\mathbf{b}$ where:\n", + "- $A$ is an $N \\times N$ Hermitian matrix\n", + "- $\\mathbf{b}$ is the input vector\n", + "- $\\mathbf{x}$ is the solution vector\n", + "\n", + "### Quantum State Representation\n", + "\n", + "The algorithm encodes the solution in quantum amplitudes:\n", + "$$|\\mathbf{x}\\rangle = \\frac{1}{\\sqrt{\\sum_{j=1}^N |\\beta_j|^2/\\lambda_j^2}} \\sum_{j=1}^N \\frac{\\beta_j}{\\lambda_j} |u_j\\rangle$$\n", + "\n", + "Where:\n", + "- $\\lambda_j$ are eigenvalues of $A$\n", + "- $|u_j\\rangle$ are eigenvectors of $A$ \n", + "- $\\beta_j = \\langle u_j|\\mathbf{b}\\rangle$ are expansion coefficients\n", + "\n", + "\n", + "The algorithm achieves $\\mathcal{O}(\\log(N) s^2 \\kappa^2 / \\epsilon)$ runtime complexity, where:\n", + "- $N$ is the matrix dimension\n", + "- $s$ is the sparsity of the matrix\n", + "- $\\kappa$ is the condition number\n", + "- $\\epsilon$ is the desired precision\n" + ] + }, + { + "cell_type": "markdown", + "id": "7b3c4385", + "metadata": {}, + "source": [ + "## 1. Import Required Libraries\n", + "\n", + "First, we import the necessary modules from qBraid Algorithms and other dependencies:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65306502", + "metadata": {}, + "outputs": [], + "source": [ + "# Core qBraid Algorithms imports\n", + "from qbraid_algorithms.hhl import HHLLibrary # HHL algorithm implementation\n", + "from qbraid_algorithms.qtran import QasmBuilder # Quantum circuit builder\n", + "from qbraid_algorithms.evolution import (\n", + " RandomizedHamiltonian,\n", + ") # Random Hamiltonian generator\n", + "\n", + "# Additional utilities\n", + "import pyqasm # QASM parsing and manipulation" + ] + }, + { + "cell_type": "markdown", + "id": "f653b099", + "metadata": {}, + "source": [ + "## 2. Define Custom Hamiltonian\n", + "\n", + "For this example, we'll create a custom Hamiltonian class that inherits from `RandomizedHamiltonian`. This Hamiltonian represents the matrix $A$ in our linear system $A\\mathbf{x} = \\mathbf{b}$.\n", + "\n", + "The `RandomizedHamiltonian` class generates random Hamiltonians with controlled sparsity and spectral properties, making it ideal for demonstrating the HHL algorithm.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "384a90db", + "metadata": {}, + "outputs": [], + "source": [ + "class RandomHamiltonian(RandomizedHamiltonian):\n", + " \"\"\"\n", + " Custom Hamiltonian for HHL Algorithm Demonstration\n", + " The Hamiltonian represents matrix A in the linear system A|x⟩ = |b⟩\n", + " \"\"\"\n", + "\n", + " def __init__(self, *args, **kwargs):\n", + " super().__init__(reg=2, seed=42, density=0.7, *args, **kwargs)\n", + "\n", + " def apply(self, qubits):\n", + " \"\"\"Apply the Hamiltonian evolution for time t=1.0\"\"\"\n", + " super().apply(1.0, qubits)\n", + "\n", + " def controlled(self, qubits, control):\n", + " \"\"\"Apply controlled Hamiltonian evolution for time t=1.0\"\"\"\n", + " super().controlled(1.0, qubits, control)" + ] + }, + { + "cell_type": "markdown", + "id": "29c4b1d3", + "metadata": {}, + "source": [ + "## 3. Implement the HHL Algorithm\n", + "\n", + "Now we'll implement the HHL algorithm using the qBraid Algorithms framework. Here's what we'll do:\n", + "\n", + "1. **Create a QASM Builder**: Initialize a quantum circuit builder with 4 qubits\n", + "2. **Import HHL Library**: Load the HHL algorithm implementation\n", + "3. **Apply HHL**: Run the algorithm with our custom Hamiltonian\n", + "4. **Generate QASM**: Convert the quantum circuit to OpenQASM format\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19132783", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3;\n", + "include \"stdgates.inc\";\n", + "qubit[5] qb;\n", + "bit[5] cb;\n", + "gate RandomHam_2q_s42_d70(time) aa,ab{\n", + "\trx(1.5089459495436826 * time) aa;\n", + "\trx(1.4992953069116235 * time) ab;\n", + "}\n", + "\n", + "gate QFT2S aa,ab{\n", + "\th aa;\n", + "\tcp(pi/2) aa,ab;\n", + "\th ab;\n", + "\tswap ab,aa;\n", + "}\n", + "\n", + "gate P_EST_2_RandomHam aa,ab,ac,ad{\n", + "\tctrl(1) @ RandomHam_2q_s42_d70(1.0) ac, aa, ab;\n", + "\tctrl(1) @ RandomHam_2q_s42_d70(1.0) ad, aa, ab;\n", + "\tctrl(1) @ RandomHam_2q_s42_d70(1.0) ad, aa, ab;\n", + "\tQFT2S ac, ad;\n", + "}\n", + "\n", + "gate Pest_INV_2_RandomHam aa,ab,ac,ad{\n", + "\tinv @ QFT2S ac, ad;\n", + "\tctrl(1) @ RandomHam_2q_s42_d70(1.0) ad, aa, ab;\n", + "\tctrl(1) @ RandomHam_2q_s42_d70(1.0) ad, aa, ab;\n", + "\tctrl(1) @ RandomHam_2q_s42_d70(1.0) ac, aa, ab;\n", + "}\n", + "\n", + "P_EST_2_RandomHam qb[0],qb[1],qb[2],qb[3];\n", + "ctrl @ ry(pi/(2**1)) qb[2],qb[4];\n", + "ctrl @ ry(pi/(2**2)) qb[3],qb[4];\n", + "Pest_INV_2_RandomHam qb[0],qb[1],qb[2],qb[3];\n", + "cb[{4}] = measure qb[{4}];\n", + "\n" + ] + } + ], + "source": [ + "# Create a quantum circuit builder with 4 qubits total, ancilla qubit automatically allocated by algorithm\n", + "builder = QasmBuilder(qubits=4)\n", + "\n", + "# Import the HHL algorithm library\n", + "hhl_lib = builder.import_library(HHLLibrary)\n", + "\n", + "# Apply the HHL algorithm\n", + "# Parameters:\n", + "# - RandomHamiltonian: The matrix A in A|x⟩ = |b⟩\n", + "# - [0, 1]: Input state qubits (vector |b⟩)\n", + "# - [2, 3]: Clock register qubits for phase estimation\n", + "hhl_lib.HHL(RandomHamiltonian, [0, 1], [2, 3])\n", + "\n", + "# Generate the OpenQASM 3.0 circuit\n", + "qasm_str = builder.build()\n", + "\n", + "\n", + "print(qasm_str)" + ] + }, + { + "cell_type": "markdown", + "id": "7ca25e0b", + "metadata": {}, + "source": [ + "### Circuit Components Analysis of above example\n", + "\n", + "The generated circuit contains several key components:\n", + "\n", + "#### 1. **Custom Hamiltonian Gates**\n", + "- `RandomHam_2q_s42_d70(time)`: Custom 2-qubit Hamiltonian\n", + "- `ctrl(1) @ RandomHam_2q_s42_d70(1.0)`: Controlled Hamiltonian evolution\n", + "\n", + "#### 2. **Quantum Fourier Transform (QFT)**\n", + "- `QFT2S`: 2-qubit quantum Fourier transform gate\n", + "- `inv @ QFT2S`: Inverse QFT for uncomputation\n", + "\n", + "#### 3. **Phase Estimation Gates**\n", + "- `P_EST_2_RandomHam`: Phase estimation subroutine\n", + "- `Pest_INV_2_RandomHam`: Inverse phase estimation\n", + "\n", + "#### 4. **Controlled Rotations**\n", + "- `ctrl @ ry(pi/(2**1))` and `ctrl @ ry(pi/(2**2))`: Controlled Y-rotations for eigenvalue encoding\n", + "\n", + "#### 5. **Measurement**\n", + "- `cb[{4}] = measure qb[{4}]`: Measurement of the ancilla qubit\n" + ] + }, + { + "cell_type": "markdown", + "id": "575b1fec", + "metadata": {}, + "source": [ + "## 4. Circuit Unrolling\n", + "\n", + "Let's analyze the generated circuit by unrolling it to see all the individual gates. This helps us understand the complete quantum circuit structure of the HHL algorithm.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "d345326f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "qubit[5] qb;\n", + "bit[5] cb;\n", + "rz(1.5707963267948966) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "cx qb[2], qb[0];\n", + "rz(0) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(2.387119678817952) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "cx qb[2], qb[0];\n", + "rz(0) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.8960656283616344) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(1.5707963267948966) qb[0];\n", + "rz(1.5707963267948966) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "cx qb[2], qb[1];\n", + "rz(0) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(2.3919450001339815) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "cx qb[2], qb[1];\n", + "rz(0) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.8912403070456048) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(1.5707963267948966) qb[1];\n", + "rz(1.5707963267948966) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "cx qb[3], qb[0];\n", + "rz(0) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(2.387119678817952) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "cx qb[3], qb[0];\n", + "rz(0) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.8960656283616344) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(1.5707963267948966) qb[0];\n", + "rz(1.5707963267948966) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "cx qb[3], qb[1];\n", + "rz(0) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(2.3919450001339815) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "cx qb[3], qb[1];\n", + "rz(0) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.8912403070456048) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(1.5707963267948966) qb[1];\n", + "rz(1.5707963267948966) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "cx qb[3], qb[0];\n", + "rz(0) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(2.387119678817952) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "cx qb[3], qb[0];\n", + "rz(0) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.8960656283616344) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(1.5707963267948966) qb[0];\n", + "rz(1.5707963267948966) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "cx qb[3], qb[1];\n", + "rz(0) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(2.3919450001339815) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "cx qb[3], qb[1];\n", + "rz(0) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.8912403070456048) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(1.5707963267948966) qb[1];\n", + "h qb[2];\n", + "rz(0.7853981633974483) qb[2];\n", + "rx(1.5707963267948966) qb[2];\n", + "rz(3.141592653589793) qb[2];\n", + "rx(1.5707963267948966) qb[2];\n", + "rz(3.141592653589793) qb[2];\n", + "cx qb[2], qb[3];\n", + "rz(-0.7853981633974483) qb[3];\n", + "rx(1.5707963267948966) qb[3];\n", + "rz(3.141592653589793) qb[3];\n", + "rx(1.5707963267948966) qb[3];\n", + "rz(3.141592653589793) qb[3];\n", + "cx qb[2], qb[3];\n", + "rz(0.7853981633974483) qb[3];\n", + "rx(1.5707963267948966) qb[3];\n", + "rz(3.141592653589793) qb[3];\n", + "rx(1.5707963267948966) qb[3];\n", + "rz(3.141592653589793) qb[3];\n", + "h qb[3];\n", + "swap qb[3], qb[2];\n", + "rz(0) qb[4];\n", + "rx(1.5707963267948966) qb[4];\n", + "rz(3.9269908169872414) qb[4];\n", + "rx(1.5707963267948966) qb[4];\n", + "rz(3.141592653589793) qb[4];\n", + "cx qb[2], qb[4];\n", + "rz(0) qb[4];\n", + "rx(1.5707963267948966) qb[4];\n", + "rz(2.356194490192345) qb[4];\n", + "rx(1.5707963267948966) qb[4];\n", + "rz(3.141592653589793) qb[4];\n", + "cx qb[2], qb[4];\n", + "rz(0) qb[4];\n", + "rx(1.5707963267948966) qb[4];\n", + "rz(3.5342917352885173) qb[4];\n", + "rx(1.5707963267948966) qb[4];\n", + "rz(3.141592653589793) qb[4];\n", + "cx qb[3], qb[4];\n", + "rz(0) qb[4];\n", + "rx(1.5707963267948966) qb[4];\n", + "rz(2.748893571891069) qb[4];\n", + "rx(1.5707963267948966) qb[4];\n", + "rz(3.141592653589793) qb[4];\n", + "cx qb[3], qb[4];\n", + "swap qb[3], qb[2];\n", + "h qb[3];\n", + "rz(0.7853981633974483) qb[2];\n", + "rx(1.5707963267948966) qb[2];\n", + "rz(3.141592653589793) qb[2];\n", + "rx(1.5707963267948966) qb[2];\n", + "rz(3.141592653589793) qb[2];\n", + "cx qb[2], qb[3];\n", + "rz(-0.7853981633974483) qb[3];\n", + "rx(1.5707963267948966) qb[3];\n", + "rz(3.141592653589793) qb[3];\n", + "rx(1.5707963267948966) qb[3];\n", + "rz(3.141592653589793) qb[3];\n", + "cx qb[2], qb[3];\n", + "rz(0.7853981633974483) qb[3];\n", + "rx(1.5707963267948966) qb[3];\n", + "rz(3.141592653589793) qb[3];\n", + "rx(1.5707963267948966) qb[3];\n", + "rz(3.141592653589793) qb[3];\n", + "h qb[2];\n", + "rz(1.5707963267948966) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "cx qb[3], qb[0];\n", + "rz(0) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(2.387119678817952) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "cx qb[3], qb[0];\n", + "rz(0) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.8960656283616344) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(1.5707963267948966) qb[0];\n", + "rz(1.5707963267948966) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "cx qb[3], qb[1];\n", + "rz(0) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(2.3919450001339815) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "cx qb[3], qb[1];\n", + "rz(0) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.8912403070456048) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(1.5707963267948966) qb[1];\n", + "rz(1.5707963267948966) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "cx qb[3], qb[0];\n", + "rz(0) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(2.387119678817952) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "cx qb[3], qb[0];\n", + "rz(0) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.8960656283616344) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(1.5707963267948966) qb[0];\n", + "rz(1.5707963267948966) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "cx qb[3], qb[1];\n", + "rz(0) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(2.3919450001339815) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "cx qb[3], qb[1];\n", + "rz(0) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.8912403070456048) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(1.5707963267948966) qb[1];\n", + "rz(1.5707963267948966) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "cx qb[2], qb[0];\n", + "rz(0) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(2.387119678817952) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "cx qb[2], qb[0];\n", + "rz(0) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.8960656283616344) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(1.5707963267948966) qb[0];\n", + "rz(1.5707963267948966) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "cx qb[2], qb[1];\n", + "rz(0) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(2.3919450001339815) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.141592653589793) qb[1];\n", + "cx qb[2], qb[1];\n", + "rz(0) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(3.8912403070456048) qb[1];\n", + "rx(1.5707963267948966) qb[1];\n", + "rz(1.5707963267948966) qb[1];\n", + "cb[4] = measure qb[4];\n", + "\n" + ] + } + ], + "source": [ + "# Parse the QASM string and unroll all gate definitions\n", + "qasm_circuit = pyqasm.loads(qasm_str)\n", + "qasm_circuit.unroll()\n", + "\n", + "print(qasm_circuit)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e99cc3b2", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/qft.ipynb b/examples/qft.ipynb index cdf1361..9d42bbb 100644 --- a/examples/qft.ipynb +++ b/examples/qft.ipynb @@ -1,473 +1,542 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "23827387", - "metadata": {}, - "outputs": [], - "source": [ - "import sys\n", - "import os\n", - "\n", - "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '..')))" - ] - }, - { - "cell_type": "markdown", - "id": "338ad3ca", - "metadata": {}, - "source": [ - "## Quantum Fourier Transform\n", - "#### In this tutorial, we will demonstrate the capabilities of qBraid Algorithms QFT and Inverse QFT (IQFT) Modules\n", - "Begin by importing the modules from qBraid Algorithms library" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "ccfc911a", - "metadata": {}, - "outputs": [], - "source": [ - "import pyqasm\n", - "from qbraid_algorithms import qft\n", - "from qbraid_algorithms import iqft" - ] - }, - { - "cell_type": "markdown", - "id": "87d3980f", - "metadata": {}, - "source": [ - "We can load both QFT and IQFT as PyQASM modules by calling the `generate_program` method, passing the number of qubits to apply the operations to." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "ff7dc849", - "metadata": {}, - "outputs": [], - "source": [ - "num_qubits = 3\n", - "qft_module = qft.generate_program(num_qubits)\n", - "iqft_module = iqft.generate_program(num_qubits)" - ] - }, - { - "cell_type": "markdown", - "id": "b68eeb17", - "metadata": {}, - "source": [ - "We can perform all standard [PyQASM](https://docs.qbraid.com/pyqasm/user-guide/overview) operations on the module, such as unrolling." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "e8c1a74d", - "metadata": {}, - "outputs": [], - "source": [ - "qft_module.unroll()\n", - "iqft_module.unroll()" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "4ff9ef62", - "metadata": {}, - "outputs": [], - "source": [ - "qft_str = pyqasm.dumps(qft_module)\n", - "iqft_str = pyqasm.dumps(iqft_module)" - ] - }, - { - "cell_type": "markdown", - "id": "d626c8d5", - "metadata": {}, - "source": [ - "Below, we display a preview of the the unrolled QFT circuit, followed by the IQFT circuit." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "b2795398", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\n", - "include \"stdgates.inc\";\n", - "qubit[3] q;\n", - "bit[3] b;\n", - "h q[0];\n", - "rz(0.7853981633974483) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "...\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "rx(1.5707963267948966) q[1];\n", - "rz(3.141592653589793) q[1];\n", - "h q[2];\n", - "swap q[0], q[2];\n", - "b[0] = measure q[0];\n", - "b[1] = measure q[1];\n", - "b[2] = measure q[2];\n", - "\n" - ] - } - ], - "source": [ - "print(\"\\n\".join(qft_str.split(\"\\n\")[:10]) + \"\\n...\\n\" + \"\\n\".join(qft_str.split(\"\\n\")[-10:]))" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "385c7c14", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\n", - "include \"stdgates.inc\";\n", - "qubit[3] q;\n", - "bit[3] b;\n", - "h q[2];\n", - "rz(-0.7853981633974483) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "rx(1.5707963267948966) q[2];\n", - "rz(3.141592653589793) q[2];\n", - "...\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "rx(1.5707963267948966) q[0];\n", - "rz(3.141592653589793) q[0];\n", - "h q[0];\n", - "swap q[0], q[2];\n", - "b[0] = measure q[0];\n", - "b[1] = measure q[1];\n", - "b[2] = measure q[2];\n", - "\n" - ] - } - ], - "source": [ - "print(\"\\n\".join(iqft_str.split(\"\\n\")[:10]) + \"\\n...\\n\" + \"\\n\".join(iqft_str.split(\"\\n\")[-10:]))" - ] - }, - { - "cell_type": "markdown", - "id": "35ada7c7", - "metadata": {}, - "source": [ - "## Using Quantum Fourier Transform in your own OpenQASM3 program\n", - "#### qBraid algorithms makes it easy to incorporate QFT and IQFT into your own OpenQASM3 circuit.\n", - "To use a QFT/IQFT in your circuit, first generate the subroutine using the `save_to_qasm` method, which takes the number of qubits to use. The method will create a QASM3 file containing the algorithm as a subroutine within your current working directory." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "3da3ed9d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Subroutine 'qft' has been added to /Users/lukeandreesen/qbraid_algos/examples/qft.qasm\n", - "Subroutine 'iqft' has been added to /Users/lukeandreesen/qbraid_algos/examples/iqft.qasm\n" - ] - } - ], - "source": [ - "qft.save_to_qasm(3)\n", - "iqft.save_to_qasm(3)" - ] - }, - { - "cell_type": "markdown", - "id": "9fb21cfd", - "metadata": {}, - "source": [ - "To use the QFT subroutine in your own circuit, add `include \"qft.qasm\";` to your OpenQASM file, and call the `qft` method by passing an appropriately sized register of qubits." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "59fb5dec", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\r\n", - "include \"stdgates.inc\";\r\n", - "\r\n", - "\r\n", - "def qft(qubit[3] q) {\r\n", - " int n = 3;\r\n", - " for int[16] i in [0:n - 1] {\r\n", - " h q[i];\r\n", - " for int[16] j in [i + 1:n - 1] {\r\n", - " int[16] k = j - i;\r\n", - " cp(2 * pi / (1 << (k + 1))) q[j], q[i];\r\n", - " }\r\n", - " }\r\n", - "\r\n", - " for int[16] i in [0:(n >> 1) - 1] {\r\n", - " swap q[i], q[n - i - 1];\r\n", - " }\r\n", - "}" - ] - } - ], - "source": [ - "%cat qft.qasm" - ] - }, - { - "cell_type": "markdown", - "id": "ab150803", - "metadata": {}, - "source": [ - "To use the IQFT subroutine in your own circuit, add `include \"qft.qasm\";` to your OpenQASM file, and call the `qft` method by passing an appropriately sized register of qubits." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "66248d06", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "OPENQASM 3.0;\r\n", - "include \"stdgates.inc\";\r\n", - "\r\n", - "def iqft(qubit[3] q) {\r\n", - " int n = 3;\r\n", - " \r\n", - " for int[16] i in [0:n-1] {\r\n", - " int[16] target = n - i - 1;\r\n", - " for int[16] j in [0:(n - target - 2)] {\r\n", - " int[16] control = n - j - 1;\r\n", - " int[16] k = control - target;\r\n", - " cp(-2 * pi / (1 << (k + 1))) q[control], q[target];\r\n", - " }\r\n", - " h q[target];\r\n", - " }\r\n", - "\r\n", - " for int[16] i in [0:(n >> 1) - 1] {\r\n", - " swap q[i], q[n - i - 1];\r\n", - " }\r\n", - "}" - ] - } - ], - "source": [ - "%cat iqft.qasm" - ] - }, - { - "cell_type": "markdown", - "id": "d94efa88", - "metadata": {}, - "source": [ - "## Running Algorithms on qBraid\n", - "Running algorithms on qBraid is simple. First, import QbraidProvider from qBraid Runtime. Visit [here](https://docs.qbraid.com/sdk/user-guide/providers/native#qbraidprovider) for more information." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "f2b39f1a", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/opt/homebrew/lib/python3.11/site-packages/pennylane/capture/capture_operators.py:33: RuntimeWarning: PennyLane is not yet compatible with JAX versions > 0.4.28. You have version 0.4.30 installed. Please downgrade JAX to <=0.4.28 to avoid runtime errors.\n", - " warnings.warn(\n" - ] - } - ], - "source": [ - "from qbraid.runtime import QbraidProvider" - ] - }, - { - "cell_type": "markdown", - "id": "e12616de", - "metadata": {}, - "source": [ - "If you have not yet configured QbraidProvider, provide your API key." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "4bb1e7e5", - "metadata": {}, - "outputs": [], - "source": [ - "# provider = QbraidProvider(api_key='API_KEY')\n", - "provider = QbraidProvider()" - ] - }, - { - "cell_type": "markdown", - "id": "629d7b9e", - "metadata": {}, - "source": [ - "We'll run our program on qBraid's QIR simulator." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "80ee5d99", - "metadata": {}, - "outputs": [], - "source": [ - "device = provider.get_device('qbraid_qir_simulator')" - ] - }, - { - "cell_type": "markdown", - "id": "4dce4910", - "metadata": {}, - "source": [ - "To run the job, simply pass a QASM string to the device. If you have a PyQASM module, use `dumps` to generate a QASM string" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "293bfb68", - "metadata": {}, - "outputs": [], - "source": [ - "module = qft.generate_program(4)\n", - "qasm_str = pyqasm.dumps(module)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "8a26081c", - "metadata": {}, - "outputs": [], - "source": [ - "job = device.run(qasm_str, shots=500)" - ] - }, - { - "cell_type": "markdown", - "id": "d3c7c3a9", - "metadata": {}, - "source": [ - "We can now get the counts from the job results." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "7e1a7a8a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'0000': 21, '0001': 39, '0010': 27, '0011': 35, '0100': 4, '0101': 4, '0110': 34, '0111': 16, '1000': 25, '1001': 18, '1010': 14, '1011': 8, '1100': 54, '1101': 58, '1110': 69, '1111': 74}\n" - ] - } - ], - "source": [ - "results = job.result()\n", - "counts = results.data.get_counts()\n", - "print(counts)" - ] - }, - { - "cell_type": "markdown", - "id": "65f39cab", - "metadata": {}, - "source": [ - "Finally, we can plot the results using qBraid Visualization." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "b201c84b", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from qbraid.visualization import plot_histogram\n", - "\n", - "plot_histogram(counts)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "de8bcb0f", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.11.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "23827387", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "\n", + "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), \"..\")))" + ] + }, + { + "cell_type": "markdown", + "id": "338ad3ca", + "metadata": {}, + "source": [ + "## Quantum Fourier Transform\n", + "#### In this tutorial, we will demonstrate the capabilities of qBraid Algorithms QFT and Inverse QFT (IQFT) Modules\n", + "Begin by importing the modules from qBraid Algorithms library" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ccfc911a", + "metadata": {}, + "outputs": [], + "source": [ + "import pyqasm\n", + "from qbraid_algorithms import qft\n", + "from qbraid_algorithms import iqft" + ] + }, + { + "cell_type": "markdown", + "id": "87d3980f", + "metadata": {}, + "source": [ + "We can load both QFT and IQFT as PyQASM modules by calling the `generate_program` method, passing the number of qubits to apply the operations to." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ff7dc849", + "metadata": {}, + "outputs": [], + "source": [ + "num_qubits = 3\n", + "qft_module = qft.generate_program(num_qubits)\n", + "iqft_module = iqft.generate_program(num_qubits)" + ] + }, + { + "cell_type": "markdown", + "id": "b68eeb17", + "metadata": {}, + "source": [ + "We can perform all standard [PyQASM](https://docs.qbraid.com/pyqasm/user-guide/overview) operations on the module, such as unrolling." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e8c1a74d", + "metadata": {}, + "outputs": [], + "source": [ + "qft_module.unroll()\n", + "iqft_module.unroll()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4ff9ef62", + "metadata": {}, + "outputs": [], + "source": [ + "qft_str = pyqasm.dumps(qft_module)\n", + "iqft_str = pyqasm.dumps(iqft_module)" + ] + }, + { + "cell_type": "markdown", + "id": "d626c8d5", + "metadata": {}, + "source": [ + "Below, we display a preview of the the unrolled QFT circuit, followed by the IQFT circuit." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b2795398", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "qubit[3] qb;\n", + "bit[3] cb;\n", + "h qb[0];\n", + "rz(0.7853981633974483) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "rx(1.5707963267948966) qb[0];\n", + "rz(3.141592653589793) qb[0];\n", + "...\n", + "rz(3.141592653589793) qb[2];\n", + "cx qb[1], qb[2];\n", + "rz(0.7853981633974483) qb[2];\n", + "rx(1.5707963267948966) qb[2];\n", + "rz(3.141592653589793) qb[2];\n", + "rx(1.5707963267948966) qb[2];\n", + "rz(3.141592653589793) qb[2];\n", + "h qb[2];\n", + "swap qb[2], qb[0];\n", + "\n" + ] + } + ], + "source": [ + "print(\n", + " \"\\n\".join(qft_str.split(\"\\n\")[:10])\n", + " + \"\\n...\\n\"\n", + " + \"\\n\".join(qft_str.split(\"\\n\")[-10:])\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "385c7c14", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "qubit[3] q;\n", + "bit[3] b;\n", + "h q[2];\n", + "rz(-0.7853981633974483) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "rx(1.5707963267948966) q[2];\n", + "rz(3.141592653589793) q[2];\n", + "...\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "rx(1.5707963267948966) q[0];\n", + "rz(3.141592653589793) q[0];\n", + "h q[0];\n", + "swap q[0], q[2];\n", + "b[0] = measure q[0];\n", + "b[1] = measure q[1];\n", + "b[2] = measure q[2];\n", + "\n" + ] + } + ], + "source": [ + "print(\n", + " \"\\n\".join(iqft_str.split(\"\\n\")[:10])\n", + " + \"\\n...\\n\"\n", + " + \"\\n\".join(iqft_str.split(\"\\n\")[-10:])\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "35ada7c7", + "metadata": {}, + "source": [ + "## Using Quantum Fourier Transform in your own OpenQASM3 program\n", + "#### qBraid algorithms makes it easy to incorporate QFT and IQFT into your own OpenQASM3 circuit.\n", + "To use a QFT/IQFT in your circuit, first generate the subroutine using the `save_to_qasm` method, which takes the number of qubits to use. The method will create a QASM3 file containing the algorithm as a subroutine within your current working directory." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3da3ed9d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Subroutine 'qft' has been added to /Users/vinay/Desktop/qbraid-algorithms/examples/qft.qasm\n", + "Subroutine 'iqft' has been added to /Users/vinay/Desktop/qbraid-algorithms/examples/iqft.qasm\n" + ] + } + ], + "source": [ + "qft.save_to_qasm(3)\n", + "iqft.save_to_qasm(3)" + ] + }, + { + "cell_type": "markdown", + "id": "9fb21cfd", + "metadata": {}, + "source": [ + "To use the QFT subroutine in your own circuit, add `include \"qft.qasm\";` to your OpenQASM file, and call the `qft` method by passing an appropriately sized register of qubits." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "59fb5dec", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "\n", + "\n", + "def qft(qubit[3] q) {\n", + " int n = 3;\n", + " for int[16] i in [0:n - 1] {\n", + " h q[i];\n", + " for int[16] j in [i + 1:n - 1] {\n", + " int[16] k = j - i;\n", + " cp(2 * pi / (1 << (k + 1))) q[j], q[i];\n", + " }\n", + " }\n", + "\n", + " for int[16] i in [0:(n >> 1) - 1] {\n", + " swap q[i], q[n - i - 1];\n", + " }\n", + "}" + ] + } + ], + "source": [ + "%cat qft.qasm" + ] + }, + { + "cell_type": "markdown", + "id": "ab150803", + "metadata": {}, + "source": [ + "To use the IQFT subroutine in your own circuit, add `include \"qft.qasm\";` to your OpenQASM file, and call the `qft` method by passing an appropriately sized register of qubits." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "66248d06", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "\n", + "def iqft(qubit[3] q) {\n", + " int n = 3;\n", + " \n", + " for int[16] i in [0:n-1] {\n", + " int[16] target = n - i - 1;\n", + " for int[16] j in [0:(n - target - 2)] {\n", + " int[16] control = n - j - 1;\n", + " int[16] k = control - target;\n", + " cp(-2 * pi / (1 << (k + 1))) q[control], q[target];\n", + " }\n", + " h q[target];\n", + " }\n", + "\n", + " for int[16] i in [0:(n >> 1) - 1] {\n", + " swap q[i], q[n - i - 1];\n", + " }\n", + "}" + ] + } + ], + "source": [ + "%cat iqft.qasm" + ] + }, + { + "cell_type": "markdown", + "id": "d94efa88", + "metadata": {}, + "source": [ + "## Running Algorithms on qBraid\n", + "Running algorithms on qBraid is simple. First, import QbraidProvider from qBraid Runtime. Visit [here](https://docs.qbraid.com/sdk/user-guide/providers/native#qbraidprovider) for more information." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "f2b39f1a", + "metadata": {}, + "outputs": [], + "source": [ + "from qbraid.runtime import QbraidProvider" + ] + }, + { + "cell_type": "markdown", + "id": "e12616de", + "metadata": {}, + "source": [ + "If you have not yet configured QbraidProvider, provide your API key." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "4bb1e7e5", + "metadata": {}, + "outputs": [], + "source": [ + "# provider = QbraidProvider(api_key='API_KEY')\n", + "provider = QbraidProvider()" + ] + }, + { + "cell_type": "markdown", + "id": "629d7b9e", + "metadata": {}, + "source": [ + "We'll run our program on qBraid's QIR simulator." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "80ee5d99", + "metadata": {}, + "outputs": [], + "source": [ + "device = provider.get_device(\"qbraid_qir_simulator\")" + ] + }, + { + "cell_type": "markdown", + "id": "4dce4910", + "metadata": {}, + "source": [ + "To run the job, simply pass a QASM string to the device. If you have a PyQASM module, use `dumps` to generate a QASM string" + ] + }, + { + "cell_type": "markdown", + "id": "808f25de", + "metadata": {}, + "source": [ + "### QFT Algorithm" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "293bfb68", + "metadata": {}, + "outputs": [], + "source": [ + "module = qft.generate_program(4)\n", + "qasm_str = pyqasm.dumps(module)" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "8a26081c", + "metadata": {}, + "outputs": [], + "source": [ + "job = device.run(qasm_str, shots=500)" + ] + }, + { + "cell_type": "markdown", + "id": "d3c7c3a9", + "metadata": {}, + "source": [ + "We can now get the counts from the job results." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "7e1a7a8a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'0100': 500}\n" + ] + } + ], + "source": [ + "results = job.result()\n", + "counts = results.data.get_counts()\n", + "print(counts)" + ] + }, + { + "cell_type": "markdown", + "id": "65f39cab", + "metadata": {}, + "source": [ + "Finally, we can plot the results using qBraid Visualization." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "b201c84b", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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cO/evfZhLihojp395Cc/qfqlBAAAOJ09O3PnztWCBQvsTTnDzCngTz75ZI0coAwAANzln+yF/8wF/77KlJlpAAAANTrsmFPM/+3f/k25ubmRsp07d9p7Zl166aVVuXwAAACnP+z813/9lx2fY+743aFDB/vIyMiwZX/84x//uSUCAACoQic1ZsfcIXz16tV23M6XX35py8z4nYEDB1blsgEAAJzenp1FixbZgcimB8fzPHtbB3Nmlnn06tXLXmvngw8++OeXCgAAIB5h54knntCtt956wksym8s133bbbXrssceqatkAAABOb9j59NNPdfnll3/jdHPaubmqMgAAQI0MO/n5+Sc85TysVq1aKiwsrIrlAgAAOP1hp02bNvZKyd9k7dq1atWqVVUsFwAAwOkPO0OGDNF9992nkpKSr007cuSIHnjgAV1xxRVVs2QAAACn+9Tze++9V6+99po6deqkiRMn6qyzzrLl5vRzc6uIYDCoX/3qV1WxXAAAAKc/7CQnJ2vp0qWaMGGC7rnnHpWVldlycxr6oEGDbOAx8wAAANTYiwqmpaVpzpw52rdvnzZt2mQDT2Zmppo3b35qlhAAAOB0X0HZMOHGXEgQAADAuXtjAQAA1BSEHQAA4DTCDgAAcBphBwAAOI2wAwAAnEbYAQAATiPsAAAApxF2AACA0wg7AADAaYQdAADgNMIOAABwGmEHAAA4jbADAACcRtgBAABOI+wAAACnEXYAAIDTCDsAAMBphB0AAOA0wg4AAHAaYQcAADiNsAMAAJxG2AEAAE4j7AAAAKcRdgAAgNMIOwAAwGmEHQAA4DTCDgAAcBphBwAAOI2wAwAAnEbYAQAATiPsAAAAp8U17EyZMkW9evVS48aNlZSUpGHDhmnDhg2V5ikpKdHtt9+uhIQENWrUSCNGjFB+fn6lebKzszV06FA1aNDAvs/dd9+t48ePn+a1AQAA1VFcw87ixYttkPnoo480f/58HTt2TJdddpkOHToUmeeuu+7SW2+9pVmzZtn5c3NzNXz48Mj0YDBog87Ro0e1dOlSPfvss5o+fbruv//+OK0VAACoTryysrIyVROFhYW2Z8aEmgsvvFAHDhxQy5YtNWPGDF177bV2ni+//FKdO3fWsmXL1LdvX73zzju64oorbAhKTk628zz99NP6+c9/bt+vTp06f7fegwcPqmnT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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from qbraid.visualization import plot_histogram\n", + "\n", + "plot_histogram(counts)" + ] + }, + { + "cell_type": "markdown", + "id": "ea73db46", + "metadata": {}, + "source": [ + "### iQFT Algorithm" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "de8bcb0f", + "metadata": {}, + "outputs": [], + "source": [ + "iqft_module = iqft.generate_program(4)\n", + "iqft_qasm_str = pyqasm.dumps(iqft_module)" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "23dbf104", + "metadata": {}, + "outputs": [], + "source": [ + "iqft_job = device.run(iqft_qasm_str, shots=500)" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "cfdc6eb1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'0000': 31, '0001': 25, '0010': 27, '0011': 31, '0100': 36, '0101': 34, '0110': 37, '0111': 27, '1000': 32, '1001': 25, '1010': 26, '1011': 34, '1100': 41, '1101': 31, '1110': 31, '1111': 32}\n" + ] + } + ], + "source": [ + "iqft_results = iqft_job.result()\n", + "iqft_counts = iqft_results.data.get_counts()\n", + "print(iqft_counts)" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "39f4b121", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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iYysmiFHRERETZqpgczIkSPVejF1waTexx57TL2IiIiILDNHhoiIiOhkGMgQERGRZTGQISIiIstiIENERESWxUCGiIiILIuBDBEREVkWAxkiIiKyLAYyREREZFkMZIiIiMiyGMgQERGRZTGQISIiIstiIENERESWxUCGiIiILIuBDBEREVkWAxkiIiKyLAYyREREZFkMZIiIiMiyGMgQERGRZTGQISIiIstiIENERESWxUCGiIiILIuBDBEREVkWAxkiIiKyLAYyREREZFkMZIiIiMiyGMgQERGRZTGQISIiIstiIENERESWxUCGiIiILIuBDBEREVkWAxkiIiKyrJAOZB599FGx2WzVXt27dzd7s4iIiChEREiI69mzpyxevNjzfUREyG8yERERBUnIRwUIXBISEk759ysrK9VLV1paGqAtIyIiIrOFfCCzfft2SUxMlMjISBk6dKhMnTpVOnfuXOfv4+dTpkz5VfqqVaskOjpa/b9v375SVlYm2dnZnp9jyMput8vmzZs9aampqRIbGyurV6/2pMXHx0tKSoqsW7dObujqUGn5FTb5X4Em5yU5JPE09++VnRD5IMcuQzs4pUcbl+fvZ2zT1PdDOnjTPt6lSVSEyNgkpyetuLhYoqKiZP369Z60pKQk9XVSpkM0W1X9lNrk20JNLk1xSNsW7rR9x0Tm77bLOYlOSY1251PpFJm1wy4D2julTztv3rOzNekUJfLbBKdkZWWptB49eqhhvC1btqjvUc7vimySU2aT67t4t3HjIZus3K/J1RkOaWl3p+0ut8niPZpckOyQhJbutJITIh/l2GVYvFO6tfbmDXv37pW8vDzP97169ZKkKJeM6eTNZ3GBJoePi1ye5k1bud8mGw9pMrmrQ6qqQraV2OS7Ik0uS3VIm+butKKjIgvy7DIq0Skp0S5VRgTH/fv3V/kif72Ms3ZokhztkhEJ3m2cv9s9+nphZ2/eywptklduk2sNdbGh2CarDmhyTYZDIqvqIrfcvWWox/LycvX/li1bqjIOj3dK16q6wL8zttmlV1unDIrz5v1hjqbKMdpQF9jPsG+NT/WmZe23yaZDmmd/RBnj4uIkLS1NNmzYIMeOHVPp+Ew+z7PL6ESndK7aL446RN7NtsugOKf0auvN+50dmqS1csnweG/aZ7s1td+NS/bmvbRQk4IKd546HK/YV6/t4pAWVYPXu8ptsmSPJhd2dkiHSHfaoUqRubl2+U2CUzJj3Pk4XSIzt9ulTzunDGjvzfv9nZrERor6HPUyduvWTZo3by4bN270/N7pbZzy02GbTO7q3UZ8v3yfJlekOaRVM3faniMii/Ltaj/D/gYVv4i8t9Mug+Oc0tNQF29v1yQjxr3vGHuKHQ6Hp87hm72aFB4VuSrdm/fagzZZe1CT67s4pFlVXewss6nf/V1nh7SvqouDlSKf5NrVcYi89DZr4MCBUlBQoF66qAiXxEWKOr51i/I1OeYQuSTFm7Z8n022lthkUqY3bcthm6zYp8nv0xwSXVUXBUds8kW+ptofvYwtWrSQPn36SG5ubrUyztyuqWN4qKH9mperqX3+PEP7hc96/zGRKw11seaATdYVazIh0yERVQct2uCMjAzZtGmTHDlyxF2+qCj1dWRHp6S3cudzwol90i79Yp3SL9ab95ydmmpn8Lu6hXmaHHeKXGyoi++LbJJdapMJmd4ysi13BrwtR6fCtm3bPGmZmZmqDUS7pEtOTpaOHTtWO77at28v6enp1c7H9bG5XK7qWxNCFi5cqE4AaLBQSQhQcEBjp2/VqtUp98igokpKSiQmJsav25d6/wIJlF3TxoV9niyjhHWeLKP/8zMjT5bR//mFW5676sivsXD+bt269UnP3yHdI3P++ed7/t+7d28ZPHiwiqDff/99ufHGG2v9G1xJ4EVEREThL6TvWqqpTZs20rVrV9mxY4fZm0JEREQhwFKBDIaZMKaK8TQiIiKikA5k/vznP8vSpUtl165d8sMPP8ill16qJuReffXVZm8aERERhYCQniOTn5+vgpaDBw+quzCGDx8uK1asUP8nIiIiCulAZs6cOWZvAhEREYWwkB5aIiIiIqoPAxkiIiKyLAYyREREZFkMZIiIiMiyGMgQERGRZTGQISIiIstiIENERESWxUCGiIiILIuBDBEREVkWAxkiIiKyLAYyREREZFkMZIiIiMiyGMgQERGRZTGQISIiIstiIENERESWxUCGiIiILIuBDBEREVkWAxkiIiKyLAYyREREZFkMZIiIiMiyGMgQERGRZTGQISIiIstiIENERESWxUCGiIiILIuBDBEREVkWAxkiIiKyLAYyREREZFkMZIiIiMiyGMgQERGRZTGQISIiIsuyRCDz4osvSmpqqkRG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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_histogram(iqft_counts)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/demo_qasmbuilder.ipynb b/examples/qtran.ipynb similarity index 96% rename from examples/demo_qasmbuilder.ipynb rename to examples/qtran.ipynb index 28c799c..3bb2321 100644 --- a/examples/demo_qasmbuilder.ipynb +++ b/examples/qtran.ipynb @@ -10,7 +10,7 @@ "import sys\n", "import os\n", "\n", - "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '..')))" + "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), \"..\")))" ] }, { @@ -20,8 +20,8 @@ "metadata": {}, "outputs": [], "source": [ - "from qbraid_algorithms.qtran import *\n", - "from qbraid_algorithms.qft import QFTLibrary\n", + "from qbraid_algorithms.qtran import *\n", + "from qbraid_algorithms.qft import QFTLibrary\n", "from qbraid_algorithms.amplitude_amplification import *\n", "import pyqasm as pq" ] @@ -118,7 +118,7 @@ ], "source": [ "# Create 10-qubit circuit with OpenQASM 3.0\n", - "alg = QasmBuilder(5, version=\"3\") \n", + "alg = QasmBuilder(5, version=\"3\")\n", "register = [*range(5)]\n", "\n", "# Import standard gates library\n", @@ -128,24 +128,23 @@ "qft = alg.import_library(QFTLibrary)\n", "\n", "# Apply gates\n", - "program.h(1) # X gate on qubit 1\n", + "program.h(1) # X gate on qubit 1\n", "program.comment(\"This is a \\nMulti-line comment\") # Add documentation\n", - "program.comment(\"Single line comment\") # More documentation\n", + "program.comment(\"Single line comment\") # More documentation\n", "\n", "# Loop example\n", - "program.begin_loop(5) # Loop 5 times\n", - "program.x(\"i\") # X gate using loop variable (default i)\n", - "program.comment(\"Inside loop\") # Scoped comment\n", - "program.end_loop() # End loop\n", + "program.begin_loop(5) # Loop 5 times\n", + "program.x(\"i\") # X gate using loop variable (default i)\n", + "program.comment(\"Inside loop\") # Scoped comment\n", + "program.end_loop() # End loop\n", "# qft.QFT(register[:5])\n", "# Measurement\n", - "program.measure([1,2], [1,2]) # Measure qubit 1 → classical bit 1\n", + "program.measure([1, 2], [1, 2]) # Measure qubit 1 → classical bit 1\n", "\n", "prog = alg.build()\n", "print(prog)\n", "res = pq.loads(prog)\n", - "pq.draw(res)\n", - " " + "pq.draw(res)" ] }, { @@ -278,15 +277,14 @@ } ], "source": [ - "alg = QasmBuilder(5,version=\"3\") \n", + "alg = QasmBuilder(5, version=\"3\")\n", "qft = alg.import_library(QFTLibrary)\n", "\n", "\n", - "#call QFT gate\n", + "# call QFT gate\n", "qft.QFT([*range(4)])\n", "\n", "\n", - "\n", "prog = alg.build()\n", "res = pq.loads(prog)\n", "print(res)" @@ -352,7 +350,7 @@ ], "source": [ "# Create 10-qubit circuit with OpenQASM 3.0\n", - "alg = QasmBuilder(5, version=\"3\") \n", + "alg = QasmBuilder(5, version=\"3\")\n", "reg = [*range(5)]\n", "\n", "# Import standard gates library\n", @@ -361,21 +359,24 @@ "# Import Amplitude amplification library\n", "ampl = alg.import_library(AALibrary)\n", "\n", + "\n", "class Za(GateLibrary):\n", " name = \"Z_on_two\"\n", - " def __init__(self,*args,**kwargs):\n", - " super().__init__(*args,**kwargs)\n", + "\n", + " def __init__(self, *args, **kwargs):\n", + " super().__init__(*args, **kwargs)\n", " self.name = \"Z_on_two\"\n", " self.call_space = \"{}\"\n", "\n", - " def apply(self,qubits):\n", + " def apply(self, qubits):\n", " sys = self.builder\n", " std = sys.import_library(std_gates)\n", - " ind = dict(zip(range(len(qubits)),qubits))\n", + " ind = dict(zip(range(len(qubits)), qubits))\n", " ind.pop(2)\n", - " self.controlled_op(\"cp\",(qubits[2],list(ind.values())),n=len(qubits)-2)\n", + " self.controlled_op(\"cp\", (qubits[2], list(ind.values())), n=len(qubits) - 2)\n", + "\n", "\n", - "ampl.Grover(Za,reg,2)\n", + "ampl.Grover(Za, reg, 2)\n", "\n", "prog = alg.build()\n", "print(prog)\n", diff --git a/qbraid_algorithms/hhl/hhl.py b/qbraid_algorithms/hhl/hhl.py index a25f6bd..7840b03 100644 --- a/qbraid_algorithms/hhl/hhl.py +++ b/qbraid_algorithms/hhl/hhl.py @@ -19,20 +19,23 @@ # Importing package modules # pylint: disable=invalid-name +from typing import Any + from qbraid_algorithms.qpe import PhaseEstimationLibrary +from qbraid_algorithms.qtran import std_gates class HHLLibrary(PhaseEstimationLibrary): """HHL library using base Phase Estimation implementation""" - def HHL(self, a: list, b: list, clock: list): + def HHL(self, a: Any, b: list, clock: list): # pylint: disable=too-many-locals """ Main implementation of the HHL algorithm Args: - a (list): Quantum register for eigenvectors (input state), e.g., list of qubit indices - b (list): Quantum register for eigenvalues (ancilla for phase estimation), e.g., list of qubit indices - clock (list): Quantum register for clock qubits used in phase estimation, e.g., list of qubit indices + a (Any): Hamiltonian operator to be applied (Matrix - 'A'). + b (list): List of qubits representing the input state (Vector - 'b'). + clock (list): List of ancilla qubits used as the clock register. Returns: None @@ -47,6 +50,48 @@ def HHL(self, a: list, b: list, clock: list): # rationale: simple evolution can be represented with negative time values, # but static Hamiltonians require explicitly implementing the inverse operation + if sys.qubits != len(b) + len(clock): + raise ValueError( + f"System qubits ({sys.qubits}) do not match the number of qubits in " + f"the input state ({len(b)}) and clock ({len(clock)})" + ) + + # Check for duplicates within b and clock + b_duplicates = [x for x in set(b) if b.count(x) > 1] + clock_duplicates = [x for x in set(clock) if clock.count(x) > 1] + overlap = list(set(b) & set(clock)) + + if b_duplicates: + raise ValueError( + f"Input state {b} contains duplicate qubits {b_duplicates}" + ) + if clock_duplicates: + raise ValueError( + f"Clock register {clock} contains duplicate " + f"qubits {clock_duplicates}" + ) + if overlap: + raise ValueError( + f"Input state {b} and clock register {clock} " + f"contain overlapping qubits {overlap}" + ) + + # Validate qubit index ranges + max_qubit_index = sys.qubits - 1 + invalid_b = [q for q in b if q > max_qubit_index or q < 0] + invalid_clock = [q for q in clock if q > max_qubit_index or q < 0] + + if invalid_b: + raise ValueError( + f"Input state qubit indices {invalid_b} are " + f"out of range for system qubits ({max_qubit_index})" + ) + if invalid_clock: + raise ValueError( + f"Clock register qubit indices {invalid_clock} " + f"are out of range for system qubits ({max_qubit_index})" + ) + Phase = sys.import_library(PhaseEstimationLibrary) # Import the root Phase Estimation library for local application @@ -59,10 +104,14 @@ def HHL(self, a: list, b: list, clock: list): Phase.phase_estimation(b, clock, a) # Apply the phase estimation routine with registers (b, clock, a) - for i in range(len(clock) - 1): + sys.import_library( + std_gates + ) # Import standard gates library to make gates available + + for i, clock_qubit in enumerate(clock): # Apply controlled rotations depending on clock qubits # Controlled rotation around Y-axis by angle pi/(2^{i+1}), where i is the clock qubit index - self.controlled_op("ry", (anc_q[0], clock[i], f"pi/(2^{i+1})")) + self.controlled_op("ry", (anc_q[0], clock_qubit, f"pi/(2**{i+1})")) # Controlled rotation around Y-axis, scaling by power of 2 (pi / 2^(i+1)) Phase.inverse_op(b, clock, a) diff --git a/qbraid_algorithms/qpe/phase_est.py b/qbraid_algorithms/qpe/phase_est.py index 8fa2340..d38448c 100644 --- a/qbraid_algorithms/qpe/phase_est.py +++ b/qbraid_algorithms/qpe/phase_est.py @@ -69,6 +69,11 @@ def phase_estimation( sys = GateBuilder() std = sys.import_library(std_gates) ham = sys.import_library(hamiltonian) + if ham.reg_size > len(qubits): + raise ValueError( + f"Hamiltonian '{hamiltonian.name}' has more qubits ({ham.reg_size})" + f"than the input state ({len(qubits)})" + ) ham.call_space = " {}" qft = sys.import_library(QFTLibrary) qft.call_space = " {}" diff --git a/qbraid_algorithms/qtran/qasm_builder.py b/qbraid_algorithms/qtran/qasm_builder.py index d4ff403..8934cd8 100644 --- a/qbraid_algorithms/qtran/qasm_builder.py +++ b/qbraid_algorithms/qtran/qasm_builder.py @@ -297,6 +297,8 @@ def build(self): qasm_code += "\n".join( f'include "{import_line}";' for import_line in self.imports ) + if self.imports: # Add newline after includes if there are any + qasm_code += "\n" # Add qubit declaration circuit_def = f"qubit[{int(self.qubits)}] qb;\n"