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 b60b47e..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_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 `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_algorithm(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/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/examples/QPE/qpe.ipynb b/examples/QPE/qpe.ipynb index d7c16a9..e1d5706 100644 --- a/examples/QPE/qpe.ipynb +++ b/examples/QPE/qpe.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(), \"../..\")))" ] }, { @@ -39,7 +39,7 @@ "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", + "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", @@ -58,7 +58,7 @@ "metadata": {}, "outputs": [], "source": [ - "module = qpe.load_program(\"unitary.qasm\", \"prepare_state.qasm\")" + "module = qpe.generate_program(\"unitary.qasm\", \"prepare_state.qasm\")" ] }, { @@ -250,7 +250,7 @@ "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." + "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." ] }, { @@ -268,7 +268,7 @@ } ], "source": [ - "qpe.generate_subroutine(\"unitary.qasm\")" + "qpe.save_to_qasm(\"unitary.qasm\")" ] }, { @@ -429,7 +429,7 @@ "metadata": {}, "outputs": [], "source": [ - "device = provider.get_device('qbraid_qir_simulator')" + "device = provider.get_device(\"qbraid_qir_simulator\")" ] }, { @@ -467,7 +467,7 @@ "metadata": {}, "outputs": [], "source": [ - "module = qpe.load_program(\"unitary.qasm\", \"prepare_state.qasm\", num_qubits=3)\n", + "module = qpe.generate_program(\"unitary.qasm\", \"prepare_state.qasm\", num_qubits=3)\n", "module.unroll()\n", "qasm_str = pyqasm.dumps(module)" ] @@ -594,4 +594,4 @@ }, "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..2eb3741 100644 --- a/examples/bells_inequality.ipynb +++ b/examples/bells_inequality.ipynb @@ -5,14 +5,14 @@ "execution_count": 1, "id": "70d162e3", "metadata": { - "hide_input": true + "hide_input": true }, "outputs": [], "source": [ "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(), \"..\")))" ] }, { @@ -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()" ] }, { @@ -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 c4f3b2a..459083f 100644 --- a/examples/bernstein_vazirani.ipynb +++ b/examples/bernstein_vazirani.ipynb @@ -14,7 +14,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(), \"..\")))" ] }, { @@ -48,12 +48,12 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "97649823", "metadata": {}, "outputs": [], "source": [ - "module = bv.load_program('1011')" + "module = bv.generate_program(\"1011\")" ] }, { @@ -148,7 +148,7 @@ } ], "source": [ - "bv.generate_oracle('111')" + "bv.generate_oracle(\"111\")" ] }, { @@ -193,7 +193,7 @@ "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." + "Similarly, you can generate an entire Bernstein Vazirani circuit as a submodule using the `save_to_qasm` method, again passing your desired secret string." ] }, { @@ -211,7 +211,7 @@ } ], "source": [ - "subroutine = bv.generate_subroutine('011')" + "subroutine = bv.save_to_qasm(\"011\")" ] }, { @@ -323,7 +323,7 @@ "metadata": {}, "outputs": [], "source": [ - "device = provider.get_device('qbraid_qir_simulator')" + "device = provider.get_device(\"qbraid_qir_simulator\")" ] }, { @@ -336,7 +336,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "51206d57", "metadata": {}, "outputs": [ @@ -367,8 +367,8 @@ } ], "source": [ - "secret_string = '101'\n", - "module = bv.load_program(secret_string)\n", + "secret_string = \"101\"\n", + "module = bv.generate_program(secret_string)\n", "module.unroll()\n", "qasm_str = pyqasm.dumps(module)\n", "print(qasm_str)" @@ -484,7 +484,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -498,7 +498,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.11" + "version": "3.13.2" } }, "nbformat": 4, 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 6a062ae..9d42bbb 100644 --- a/examples/qft.ipynb +++ b/examples/qft.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(), \"..\")))" ] }, { @@ -40,7 +40,7 @@ "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." + "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." ] }, { @@ -51,8 +51,8 @@ "outputs": [], "source": [ "num_qubits = 3\n", - "qft_module = qft.load_program(num_qubits)\n", - "iqft_module = iqft.load_program(num_qubits)" + "qft_module = qft.generate_program(num_qubits)\n", + "iqft_module = iqft.generate_program(num_qubits)" ] }, { @@ -105,30 +105,34 @@ "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", + "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", - "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", + "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\".join(qft_str.split(\"\\n\")[:10]) + \"\\n...\\n\" + \"\\n\".join(qft_str.split(\"\\n\")[-10:]))" + "print(\n", + " \"\\n\".join(qft_str.split(\"\\n\")[:10])\n", + " + \"\\n...\\n\"\n", + " + \"\\n\".join(qft_str.split(\"\\n\")[-10:])\n", + ")" ] }, { @@ -166,7 +170,11 @@ } ], "source": [ - "print(\"\\n\".join(iqft_str.split(\"\\n\")[:10]) + \"\\n...\\n\" + \"\\n\".join(iqft_str.split(\"\\n\")[-10:]))" + "print(\n", + " \"\\n\".join(iqft_str.split(\"\\n\")[:10])\n", + " + \"\\n...\\n\"\n", + " + \"\\n\".join(iqft_str.split(\"\\n\")[-10:])\n", + ")" ] }, { @@ -176,7 +184,7 @@ "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." + "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." ] }, { @@ -189,14 +197,14 @@ "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" + "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.generate_subroutine(3)\n", - "iqft.generate_subroutine(3)" + "qft.save_to_qasm(3)\n", + "iqft.save_to_qasm(3)" ] }, { @@ -217,23 +225,23 @@ "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", + "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", "}" ] } @@ -260,25 +268,25 @@ "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", + "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", "}" ] } @@ -298,19 +306,10 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 26, "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" - ] - } - ], + "outputs": [], "source": [ "from qbraid.runtime import QbraidProvider" ] @@ -325,7 +324,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 27, "id": "4bb1e7e5", "metadata": {}, "outputs": [], @@ -344,12 +343,12 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 52, "id": "80ee5d99", "metadata": {}, "outputs": [], "source": [ - "device = provider.get_device('qbraid_qir_simulator')" + "device = provider.get_device(\"qbraid_qir_simulator\")" ] }, { @@ -360,20 +359,28 @@ "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": 14, + "execution_count": 53, "id": "293bfb68", "metadata": {}, "outputs": [], "source": [ - "module = qft.load_program(4)\n", + "module = qft.generate_program(4)\n", "qasm_str = pyqasm.dumps(module)" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 54, "id": "8a26081c", "metadata": {}, "outputs": [], @@ -391,7 +398,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 55, "id": "7e1a7a8a", "metadata": {}, "outputs": [ @@ -399,7 +406,7 @@ "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" + "{'0100': 500}\n" ] } ], @@ -419,13 +426,13 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 56, "id": "b201c84b", "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "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": [ "
" ] @@ -440,18 +447,80 @@ "plot_histogram(counts)" ] }, + { + "cell_type": "markdown", + "id": "ea73db46", + "metadata": {}, + "source": [ + "### iQFT Algorithm" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 48, "id": "de8bcb0f", "metadata": {}, "outputs": [], - "source": [] + "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+quzCGDx8uK1asUP8nIiIiCulAZs6cOWZvAhEREYWwkB5aIiIiIqoPAxkiIiKyLAYyREREZFkMZIiIiMiyGMgQERGRZTGQISIiIstiIENERESWxUCGiIiILIuBDBEREVkWAxkiIiKyLAYyREREZFkMZIiIiMiyGMgQERGRZTGQISIiIstiIENERESWxUCGiIiILIuBDBEREVkWAxkiIiKyLAYyREREZFkMZIiIiMiyGMgQERGRZTGQISIiIstiIENERESWxUCGiIiILIuBDBEREVkWAxkiIiKyLAYyREREZFkMZIiIiMiyGMgQERGRZTGQISIiIsuyRCDz4osvSmpqqkRGRsrgwYMlKyvL7E0iIiKiEBDygcx7770n99xzj/ztb3+TNWvWSJ8+fWTs2LGyb98+szeNiIiITBYhIe6ZZ56Rm2++WSZPnqy+f+WVV2TBggXyn//8R+6///5f/X5lZaV66UpKStTX0tJSv2+bs/KIBEpd2xtOebKMEtZ5soz+z8+MPFlG/+cXbnmWBuD8anxfl8tV/y+6QlhlZaXLbre75s6dWy19woQJrt/97ne1/s3f/vY3lJgvvvjiiy+++BLrv/Ly8uqNFUK6R+bAgQPicDgkPj6+Wjq+//nnn2v9mwceeEANRemcTqcUFxdLbGys2Gw2MQsiy+TkZMnLy5OYmJiwy8+MPJtCGc3Ik2UMjzxZxvDIsymUsS7oiSkrK5PExESpT0gHMr5o0aKFehm1adNGQgV2imDuGMHOz4w8m0IZzciTZQyPPFnG8MizKZSxNq1btxZLT/Zt37692O12KSoqqpaO7xMSEkzbLiIiIgoNIR3ING/eXAYMGCBfffVVtaEifD906FBTt42IiIjMF/JDS5jvMnHiRBk4cKCceeaZMn36dKmoqPDcxWQVGO7CLeQ1h73CJT8z8mwKZTQjT5YxPPJkGcMjz6ZQxsayYcavhLgXXnhBnnrqKSksLJS+ffvKc889pxbGIyIioqbNEoEMERERkeXmyBARERHVh4EMERERWRYDGSIiIrIsBjJERERkWQxkKGiawrzyplBGIqJQwkAmxE584XgixPOyjLCoYbhpCmUM5320Lk2prERWFfIL4oWzrVu3yqxZs2T37t0yfPhw9erevbs6CWpaYGJMPN6hpKREunbtKsHw008/yfPPPy979uyRHj16yOWXX65Waw7miSjQDwttCmWEyspKtUDW8ePH1ddg5QvBygsPyzt69KhaVbxt27Yqz2CW0wzhXj5gGcMbe2RMsmXLFrWoH75u375d3njjDTn33HPV4xcQxATiShAnXKyO/Mgjj8jmzZsl0PCE8iFDhsiRI0ckIiJCVq9eLcOGDZN33nknoPnm5+ervCDQB3ZTKCNgP50wYYKMGjVKrr/+elmyZEnA883JyZFvvvlG/V8PKAJp48aNcv7558tZZ50lY8eOlRtuuEF++eWXoJ0cgtWLV1xcrOp2586d6vtgnvxq9lwGSlMo4759+9Q+m5WVFbQy6scgjouQggXxKLh++eUX13XXXee69tprPWlr16513XjjjS673e6aP3++SnM4HH7Ls6CgwHXWWWe5+vTp4zrzzDNVXhs3bnQF0v/93/+5LrnkEs/3RUVFrocffliV8aWXXlJpTqfTr3n+/PPPrvj4eNegQYNc3377rSvQmkIZt27d6oqJiXHdcsstrj/84Q+uK6+80mWz2Vx///vfXcXFxQHLMzY21tW+fXvXZ5995kn3d13qdu3a5YqLi3Pde++9ro8++sj1z3/+05WZmenq1auXa/v27a5AwXs/+uijroqKCr8f87VZv369agNSUlJcGRkZrrFjx7pyc3MDmudPP/3kuvnmm12lpaWe9i+QmkIZ161bp/bPtLQ01Rb0799ftQX6fhQImzZtcl1wwQWuQ4cOqe9PnDjhChUMZExw/Phx129/+1vX/fffXy193759rttvv90VGRnpWr58uV/z/Oqrr9QBjQNg5syZascPdDBz2WWXqTxqeuKJJ9SJcMGCBX49Oe3du9c1cuRI17Bhw1znn3++a8yYMa5ly5a5AqkplPGhhx5ynXvuudXSXnvtNVU+7MP+bjwRDJ533nmqbAj2Tz/9dNcnn3wS0GAGwcvAgQNdJSUlnrTs7GzX4MGDXT169FDb5O9AA0FMhw4dVMB2zz33BDyYycvLcyUmJqrP7JtvvnF98MEHrgEDBrg6d+7sWrx4cUBOvjt27HB16tRJtWnjx48P+Im+KZQRbUB6errrwQcfVEHbypUrXaNHj3Z17NjR9cYbb3jy96edO3eqoAnHPOpTD2YCHbCdKgYyJrnjjjtcQ4cO/dUV7e7du9XBgMjX2Kg21tGjR10//PCD5/v//Oc/nmBmw4YNvzpJ+KMxxZVmcnKy6g0yvjcCudtuu02dIHBQ+gsO6FGjRrm+//5718KFC4Nyom8KZUQ5fve733n2C33fePvtt12apqmgxp8BxubNm10XXnihOvGsWbPGNWnSpIAHMy+88ILq/dHpZdyzZ4+6ukfg6E+HDx9WPXmXX3656//9v/+nAqa77roroMHMkiVLVD2iTDqciLAP4SSoXzz5K++ysjIViKKM06dPdw0ZMsR18cUXB/RE3xTKuGrVKleXLl1Uz6zR5MmTVcA2e/Zsvx4f2Cf/+Mc/qvPSe++9p8rYu3fvkApmGMiYBDtE3759Xf/6179+FUGjxwRXFQhq/Knmzl1bz8yUKVNUlO8rYwPx448/qhMAhiNqXtHiJIUyYkjNn9DjpENviH6iX7p06a+20R+NGRpGDNmFcxlffPFF12mnnebatm2bp+HS9yUML7Vp00ZdlfqTsZFGwz1x4kR1gpo3b54n3R8NqF4ODD3gqnrq1Kmen+l1h6ARJ445c+Y0Oj/je+OKGu9ZWVnpeuyxx1Qw86c//anWYMYfJ6b3339ffVbHjh1T3yNfHYJjBN3+DhBRn++88476rPA10Cf6plDGr7/+WgXd6DGECkOP6NVXX60CNvTug7/KiosVBEjw3XffhVwww0AmCHJyctSOgG6/RYsWedJx8uvatauaS3Hw4MFqV6RoOPHV33nW3On0YOamm25y/f73v1dX2L7kq+/QNd9/2rRp6v1x1Zmfn+9Jx/8xxouDorHqO1l//vnnapgCw2p6rwVOFitWrGhwPjhZozw4eaM3QodgFEFpIMtYH3+WsbbPEOXA+6OXEHNJjOPjW7ZscSUlJbm++OKLRpejvsZw9erVnmBG75nBVSKGhHyhn+jQcwbo/USPyG9+8xtPg63Dz3Cc/uMf/3D5g15O1KF+ojly5Ii6iNCDGXxv3E5/9R6g9xC9wTr9RI8eRQxXYG6QP9R2AkVeOG5qnujRW9zY3mc9P5QR+2MwymjcX/X8A1lGY149e/asNjfvmGE/QbB25513+i2v2sqNdqZmMIN9FkNQgZ7nVRsGMgGGYRuMgeNDx8Sz6Oho1VWu7+DoDTnjjDNUI4oT5f79+11/+ctfVMN54MABv+WJQKVmd6vuzTffdDVr1szVunVrn3oPcDLD+OkjjzziSdNPEPDXv/5VNdAXXXSR6k3A3ACMYWMyXmOGXYzBU82Dx3gA6kMwOBnj4Mc4L4YsGgI9VqgfzG3CJNsWLVqo99OH5R5//HGV7u8yGoMnXO0ZGT9Df5QRsP/V9v6zZs1yDR8+XHWh61eC+hAJGk7jhFx/5Vnzc9SDGUzARdDmaxkxafHSSy9V8wrwPphLoffKjBs3Tn3GGHo1Qr0+/fTTv9qmxgRPNU+0+DmCGRy3aA+wf6N9wDwsX+nbiq+o23//+98q6DaezHHsIO8RI0aouTqNZcyztuDtrbfe8pzo0cbdeuut6nPwZfIoTp769ut5Pvfcc+oEG6gy1sxTp3+u/i6jkd7O4XhLTU1VwXzN/eiqq65yTZgwweUvxs9Rzx9p6AHWgxn0RuPCHG1EICcc14WBTADh6gDzYPToGCc0nHDatWunujn1oQg0XrgS1CdSJSQk+NRAnyxPNMbGIQDslGhgcDC0bdtWNfANheEvNIzoeUBAhrLojN26M2bMUCdalBG/hxO8r2WsK3iqL5jBgY8yotvZODRzqg0XGiHcoaRfXSF/9JphWEnv+cCVmD/LWFvwhBOtsafFeOJvTBkBZcKERePkZeNJ9/XXX1cTjdFwYdgMw2oYHkFXtq/DoLXlWV8wg7Ljihvl9GUIFMNj+h1Y6EFDYIbPC3eaoQFGTyZ6JhEs4c5CBI+YI4S/0YfWfFEzeMJJwHh86GXWgxnsVzimcBHiy8R/BNJZWVme40KvQ/RKoLcC7YzxWAUEwPfdd5/PwVrNPGvS3xMncxwrKCOGSKKionzqPcTxgXYUJ1P0UOA9EfwhuMZJFT3B/i5jzTyxf+jz42oGbP4oI+7e03s7jduLMiKwxgXvzTffXO1vEMggzfi5+yPPmvAz9Mxg+kBERIQqI6YTmIGBTADhhIeDqebYOnYU7NyY0KhDUIOAA8MQmHkfqDxxIBtPFGh40JBjEmlDYUd+8skn1ZDD//73P9ff/vY3V/fu3esMZgA7OoauGtNLUV/wVFswgzRc4bZq1crnu7RwsOpXePpVFRownNTxM31MGnXrjzLWFzzhqhKTGo1lbmwZMXyE2/Kx72BeD070tX2GyBcneARV6InB5+1rsFZfnrUNM6GMuJpG4OPr54iABfOJjHAFj0D/z3/+swrc0HOJIVlsFwLIs88+26fA8GTBE44X423BepkxBIFACsGacSL+qcKx3rJlS5UH5lPUnKSN4we9vuitRWCFHr8bbrhBBU24jdgXdeVZk35iLC8vV1fvKKMvnyV6BfG3CMqef/55deGGAB49z7hYw00UCFgwlOSvMtaWJ77H54p5XDq93I0tI/Yb7OuoU9x9pdefXoeYjoBpCUlJSa5+/fqpO14x8Rjz2Xy5KK0vz7qgXcLFFY4fX/P0BwYyAYQdGRMIjSda/QoXV5OIYHHXS7DzxDBFXUM0DYWTNebZ6MGYHswYy1WzK70xTiV4qnkSxMkAdWJsbBqSHw5W3JqLK/OaJ3eUHwcxGhF/qy94Qi+NMeBFQ9mYMmJ4EUMYOAmh9wxrUxgDi5rd6DgZIH/jsJC/86zZDY9GFle4jenlwjoxeiBjfP9XXnlFnQAwsdkInz1ejVFX8IThX5xsCwsLq+1XCEixLb4EMfg8cIGEk8s111yjTqJYeqFmMIMTPXrVsF3nnHOOuivN10n+J8uzJtQ76gQnTF8DRPRGIKA3wvAnAkCczBEgoofNX2WsL08cjxjKMQYraPMaU0a0yQh4cacQAibMXcQNIjWDGewv2dnZarrCFVdc8avt8GeeNaGdRYDYvHlzv9/Q0FAMZAIME0ERMRvnEOgndsyrwNwRRNb+nCB1qnnqDbk/Z/Hjara2YAZ3m/hrZvupBE8169PXiXZ63WBSKXohjJN89RMc0jBejYmw/qjLUw2e0H1u1JjJhDgZffjhh+r/yBtzRBBYGLutjZNT/eFU8qy5zyBQbwzMEUGvlT4cYOxtQjCMQN/fi6fVFzwhv5dffrnaPouTiC8BKSD4wYkcQT6GenBLLgILnND1+qytrWnMxcbJ8qwtP0ycbmxQgV5ZDKUb3x+9COi1fOCBB371N429oKovT/QOY70l43AO1pLytYwITjDpG2048sN8OwQWek97XcNGvzSijT1ZnrXlh2MWPcVmYyDjRziJY1gBdwnpOxTG3BEpYw5MzTs70JChe74xk6OCnWdt+dU2Dq8HF/iKK0x0VRrHkv2pvuBJb7wacgLWy2VsrBD4YS4Ruqpr3tXy8ccfN2pydmOCJ5x09e31Ncio7e/QkOm9JMbAAnMC/HGib2ie2Kfr+ruGQOCCq2rMcdA/L71OESDirh58nv50suAJwx3+XGrBeEWOIR9crSOw+PLLL6vNjWtsT1ND8tQnG/vrLiz0FGAoS++dM9YpAkP0EtQc6mjsvuNLno1hXFEaFynovUNg8e6771ar04OGO14bW8aT5QnIM1ArevuKgYyfIPLG5E6c0DBBs1u3burDx4kU80/Q9Yrxdn2HQDrGqTFE4OtKjMHOs2Z+CBxwUtcPJGMwg+ACdyshgEGD5usVZrCDJzTImNCqn1iMwQwaKQx9YDI2hgZwIkAPASa8Yj5FYw7uYAdPteVXE/YR45AP5qegTn0NZIKdJ06o2N9xUsViZfpkXQx7YG4OJm0aTwL4/LD/NOYOrFAJnoxQbj2w0HtJMB8IwyKBeuRDoPI0/i0mT6Pu9JsmjEESemVwjPpDsPOs6/hAoK8HFnovCXr7pk2b5re7oRqSpz+nDDQWAxk/wERPNIA4oaF7DidPPI8GJxpccWFnxzgphgkwuxsrhaJRw0Hu69hisPOsKz/07iBwqG0Bpuuvv15NcmzMejjBDJ5w1Y/GCH+PrmJ9DoqxkcAVC4bn0FOC30W94hk9jb07KZjBU3351daQYS4L6gTDWb4GpMHOE/sc9hfcqYcxf/wf8yT03i0EKwhmcOcbei0xiRlzGlDPjelxCnbwVFd+NfdbPbDAIxFwgYO69XXYI9h5ImiobakFHBuYR4bP0DhfDL3NmPxac7mCUM6zZn510QMLtD+YhI46XefjPCMz8gwUBjJ+gEYT3fw1G1x8+LhFD2OrONniBIRbKTHZFkM8jXkYXbDzrC8/TLDDpFTjcBXu+sBdBI05wQczeEKAgBMZrrhw0sEVNAKo2oIZfbIrTra4StGHPXwR7OCprvzqCyww5wHDH74GpMHOEz0guLPKODyFOsS+gx7KV199VaVhbB8roaIuESjjuMFaNb4KdvBUW364Qwe3yeuM+xF+H70JCA59PREFO098RhiywSTU2uaA4a5LBMhoa/C5ovcZczswkdq43lEo53my/GpCrx7aQNSpr8HoFhPyDCQGMn6AHg7cMaKvqqqvygkYGsAJ0d8ffrDzPFl+aJyN+eFODKzy2BjBDp7Q06N3n2LiLuYYGYOZUxkaCeXg6WT51VYuDHngd3ztiTEjT8BDLvU7oIyPIUAPAa6osRqysV4RJPt6B5YZwVN9+aHnFfNydHqvJYZbsfClr3e1BDtPtCG4Sw3BIJaOwLy/2k666NXCECROtBhex80MvrYBwc7zVPMz1uvdd9+tLgp8uavNrDwDjYGMj/QJczqsF2C8Nc84doq7T7BIkdXy9DU/f92dFIzgCWWsbawXjTCurvQTr/7oAZyY0WD5a/XKYAdPJ8uvZj64EjM+diHU80R94fNEjw6uNrGP6msJAT5TLBiJRe90/ponEuzgqb78UMeffvppteEg3B7dmB7SYOeJdbVwOzfm+2GOHHoD6jvpYp/BUEljlpMIdp4NzQ/HDKYLNOZ254Um5BloDGR87CnA7YYY78YCTFjiHFdVWGAKO4ROv6JG5I6l662UpxllDHbwZCwjlhCfP3++52d6I43FtfRgBkESFsNCvo1pLIMdPDU0P9Qx8sPYuK+CnWfNzx/7q91ur9ZLoP8OfobJi/66wyTYwdOp5oeeEqPGfJ5m5ImhY31xPcAQuX7Sxcq2usZOdDUzz1PNz7i/GC/orJJnoDGQ8eGpvBgXxgkU46KYr4CuYyyIhitP3GGC1XNx0OsHObpj8fu+rsMR7DzNKGOwg6fayogABV3hOr0caKQxJo6uVaz7oS/D3tgyBiN4MiNYC3aeuPLHnDDjs8QAaQhYjPM3APsUhgQaM7fJjODJ1/waGzQFM8+6Lkj0dgbL/Bt7ENAGYXVbrGHjq2Dn6Wt+NR/+G+p5BhMDmQbAwYmJp8YrK9wu+thjj6lJfOiuw9olGPvGCyd7/C5Ofr6OSwc7TzPKGOzgqa4yYkItFryq+ewSzA1APjjQG3MHVrCDJzOCtWDniTka+FzwHlgEzThUgx4s3MGnP0sJPT64YwjbhcnH+mRxXwQ7eDIjWAuVMtakD4fg+EUPEebgGJ8hF8p5NoUymoGBTANhLLjmMtU4CT711FOqaxUTUPE9JqSiVwGrrzbm5GdGnsHOz4zgqa4y4oDHiRfrJOjbhtuecRXamPkFwQ6ezAjWgp0n7sjD83PwWeLRAghY8CwjY4CCoBdPIsadQZhvhbvg8FynxtydFOzgyYxgLZTKWBs8k06/Pd/XzzLYeTaFMpqFgcwp0q8icVLDxD1cedacxY6TOmav1/bYcyvkaUYZgx08nUoZccLFrH59fB8TGBvz5GOzgqdg5xfsPDFujwBGn0iMlVdrC2YAvQR44jQmOjZm8nKwgyczgrVQKWNdJ10EwJh8itWSfQ26g51nUyijmRjINBC62nDLGnYQ/USnnxyx2Bd2lgULFvj1johg5xnM/MwKnk6ljMa7TBoj2MGTGcGaWQFizWcvIajBZ4dVZPUGG0OP/np+UrCDJzOCtVAqY20nXQxB4rb1xsxVC3aeTaGMZmIg4wMsZIXFyTBB0bhDYLlxzAn44YcfLJ9nsPMzI0AMxTL6K3gyIz+z8tQnM+r5YIEyvcHGLc5YAwNP2UbQ44/9JtjBU7DzMyPP+vLTH+uAixd9ZWh/POsn2Hk2hTKahYGMj3A1iZMgGkjsHFjoCmPEHTt2rLZ0tZXzDHZ+ZgSI4V7GphB064y3A+OzxGRFLFaGR3QEYg2MYAZPZuRnRp4nyw/z4/x9K3Cw82wKZQw2BjKNgPFgPIARt5DiFmFMRG3sHINQyzPY+ZkRIIZ7GZtC0K1DY6032Fi5FJMWA7kaabCDp2DnZ0ae9eUXqOMy2Hk2hTIGEwOZRsI99xgnRmPZmFU6QznPYOdnRoAY7mVsCkG38epTX1I9GM+FMSN4CmZ+ZuTJMoZPnsHAQIZCkhkBYrCFe0BqVp4IZPDcrWAuqR7s4CnY+ZmRJ8sYPnkGWoQQhaCYmBj1CmfBLqMZdWpGnna7XW644Qax2WxBzbdnz56yZs0a6d27d1jmZ0aeLGP45BlINkQzAc2BiKgJQFMazOAp2PmZkSfLGD55BhIDGSIiIrIszewNICIiIvIVAxkiIiKyLAYyREREZFkMZIiIiMiyGMgQERGRZTGQISIiIstiIENERESWxUCGiIiILIuBDBEREYlV/X+MPVKXYelqWQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_histogram(iqft_counts)" + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -465,7 +534,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.11" + "version": "3.13.2" } }, "nbformat": 4, 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/__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..9f0ec98 100644 --- a/qbraid_algorithms/bells_inequality/__init__.py +++ b/qbraid_algorithms/bells_inequality/__init__.py @@ -13,20 +13,55 @@ # 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/ + + 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 6df5370..b48ab18 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 @@ -24,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 cd58216..ef840f2 100644 --- a/qbraid_algorithms/bernstein_vazirani/__init__.py +++ b/qbraid_algorithms/bernstein_vazirani/__init__.py @@ -13,20 +13,53 @@ # 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/ + + 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 312ed63..a2eb6a6 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 @@ -28,23 +28,24 @@ 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. 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 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 +64,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 +80,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 +114,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/__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/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/__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/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/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..7840b03 100644 --- a/qbraid_algorithms/hhl/hhl.py +++ b/qbraid_algorithms/hhl/hhl.py @@ -13,33 +13,33 @@ # 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 # 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''' + """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 - ''' + """ sys = self.builder # Access to the quantum circuit builder (assumed to be defined in the parent class) @@ -50,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,17 +101,21 @@ 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): + 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) + 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..d0c0fe5 100644 --- a/qbraid_algorithms/iqft/__init__.py +++ b/qbraid_algorithms/iqft/__init__.py @@ -13,19 +13,61 @@ # 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^n-1)} + \\end{pmatrix}` + + +.. admonition:: Functions + :class: seealso + + .. autosummary:: + :toctree: ../stubs/ + + 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 076fa45..fb50cb4 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 @@ -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 fc5bf16..1716c8a 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/ + + 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 e83ede9..8dc2508 100644 --- a/qbraid_algorithms/qft/qft.py +++ b/qbraid_algorithms/qft/qft.py @@ -13,7 +13,7 @@ # limitations under the License. """ -Module providing Quantum Fourier Transform (QFT) algorithm implementation. +Quantum Fourier Transform (QFT) Algorithm Interface """ import os @@ -29,9 +29,10 @@ 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: num_qubits (int): The number of qubits for the QFT. @@ -45,12 +46,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. @@ -64,7 +63,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/qft/qft_lib.py b/qbraid_algorithms/qft/qft_lib.py index d93b856..c01065b 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) Library 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..b0f963a 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/ + + 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", "get_result",'PhaseEstimationLibrary'] +__all__ = [ + "generate_program", + "save_to_qasm", + "get_result", + "PhaseEstimationLibrary", +] diff --git a/qbraid_algorithms/qpe/phase_est.py b/qbraid_algorithms/qpe/phase_est.py index 01e38aa..d38448c 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) + phase estimation algorithms. - 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,40 +62,50 @@ 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) 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 = " {}" 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 +115,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..fca154f 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 @@ -30,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, @@ -54,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 @@ -82,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, @@ -104,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/__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/qtran/gate_library.py b/qbraid_algorithms/qtran/gate_library.py index 799717b..ba114b4 100644 --- a/qbraid_algorithms/qtran/gate_library.py +++ b/qbraid_algorithms/qtran/gate_library.py @@ -22,15 +22,18 @@ 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 """ + + # 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: @@ -39,15 +42,18 @@ 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 """ - 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 +65,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 +95,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 +122,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 +143,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 @@ -201,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) @@ -234,7 +246,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 +283,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 +306,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 +350,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 +394,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: @@ -410,18 +435,40 @@ 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 - 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 +492,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..4346b91 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 @@ -38,16 +38,18 @@ 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. """ + @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 +63,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..8934cd8 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,17 +58,18 @@ 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 - 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 +92,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 +115,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): @@ -125,15 +128,16 @@ 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): ret = super().import_library(lib_class, annotated) ret.call_space = " {}" - return ret + return ret def build(self): """ @@ -151,8 +155,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 @@ -166,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): @@ -254,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 @@ -277,14 +285,20 @@ 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 + ) + 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" @@ -313,10 +327,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): @@ -342,14 +357,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/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() 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,