diff --git a/examples/bells_inequality.ipynb b/examples/bells_inequality.ipynb new file mode 100644 index 0000000..273e3de --- /dev/null +++ b/examples/bells_inequality.ipynb @@ -0,0 +1,138 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "70d162e3", + "metadata": { + "hide_input": true + }, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "\n", + "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '..')))" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9233f1b4", + "metadata": {}, + "outputs": [], + "source": [ + "from qbraid_algorithms import bells_inequality\n", + "import pyqasm" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b514527a", + "metadata": {}, + "outputs": [], + "source": [ + "program = bells_inequality.load_program()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "efb9f3e3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "qubit[2] q0;\n", + "qubit[2] q1;\n", + "qubit[2] q2;\n", + "bit[2] c0;\n", + "bit[2] c1;\n", + "bit[2] c2;\n", + "reset q0;\n", + "reset q1;\n", + "reset q2;\n", + "x q0[0];\n", + "x q0[1];\n", + "h q0[0];\n", + "cx q0[0], q0[1];\n", + "x q1[0];\n", + "x q1[1];\n", + "h q1[0];\n", + "cx q1[0], q1[1];\n", + "x q2[0];\n", + "x q2[1];\n", + "h q2[0];\n", + "cx q2[0], q2[1];\n", + "rx(pi / 3) q0[1];\n", + "rx(2 * pi / 3) q1[1];\n", + "rx(pi / 3) q2[0];\n", + "rx(2 * pi / 3) q2[1];\n", + "c0 = measure q0;\n", + "c1 = measure q1;\n", + "c2 = measure q2;\n", + "\n" + ] + } + ], + "source": [ + "print(program)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b1780758", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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4dKlS/D398f06dMxb9484W0WFxcb/fvHH38EAPTv399kHhERkRzc3d3lLuGOFHNpIzY2FmvWrMEnn3yCiIgIXL16FadPn27Ul2dxtDEREdk7e/+YVkSQuHXrFnx9fbF8+XKLfusmgwQREdk7e/+YVsSljVOnTqG8vBxDhgyx6HZzcnIMP7/11ltYtWoVnnzySSxdutSi+yEiImqqFNEj8dtvv6F79+64cOEC2rRpY7HtskeCiIjsnb1/TCviro327dvD1dUV+/btk7sUIiIiuo0iLm24uLggNjYWr776KpycnDBgwADk5eXh5MmTmDp1qvB2i4qKAADffvstHnvsMbRv3x7Hjh2zVNlERERNniKCBAC8+eabcHBwwPz585GVlYXAwEDMmDGjUdusvaXm8OHDAIDBgwfb/W02RERE9kQRYySsrUePHjh+/Di+/vprPPbYY3KXQ0REpBjNPkjk5+fDx8cHer0e2dnZ8Pf3l7skIiIixVDMpQ1rycrKQnh4OCoqKhgiiIiIzNTseyRqVVRUwMnJSe4yiIiIFIVBgoiIiIQp4jkSREREZJ8YJIiIiEgYgwQREREJY5AgIiIiYQwSREREJIxBgoiIiIQxSBAREZEwBgkiIiISxiBBREREwhgkiIiISBiDBBEREQljkCAiIiJhDBJEREQkjEGCiIiIhDFIEBERkTAGCSIiIhLGIEFERETCGCSIiIhIGIMEERERCWOQICIiImEMEkRERCSMQYKIiIiEMUgQERGRMAYJIiIiEsYgQURERMIYJIiIiEgYgwQREREJY5AgIiIiYQwSREREJIxBgoiIiIQxSBAREZGw/w80vJ0vrvX3rwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "pyqasm.draw(program)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f60b68cb", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/demo_hamiltonians.ipynb b/examples/demo_hamiltonians.ipynb new file mode 100644 index 0000000..5387527 --- /dev/null +++ b/examples/demo_hamiltonians.ipynb @@ -0,0 +1,912 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "adcf5a4f", + "metadata": {}, + "source": [ + "# Quantum Algorithms Demo Notebook\n", + "\n", + "This notebook demonstrates the core quantum algorithms using an abstract hamiltonian interface: GQSP, Trotter decomposition, and Preparation-Selection methods using numpy and vanilla Python.\n", + "\n", + "## Table of Contents\n", + "\n", + "1. [Setup and Test Environment](#setup)\n", + "2. [GQSP Algorithm - Basic vs Multi-Depth](#gqsp)\n", + "3. [Trotter Decomposition - Two-Hamiltonian vs Multi-Hamiltonian](#trotter)\n", + "4. [Preparation-Selection - Matrix vs Operator Chain](#prepsel)\n", + "5. [Complete integration](#integration)\n", + "6. [Amplitude Amplification and Grovers](#Grover)\n" + ] + }, + { + "cell_type": "markdown", + "id": "4e72fbe8", + "metadata": {}, + "source": [ + "\n", + "---\n", + "\n", + "## 1. Setup and Test Environment " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21685242", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from itertools import combinations\n", + "import pyqasm as pq\n", + "import string\n", + "\n", + "# Import quantum algorithm libraries\n", + "from qbraid_algorithms.evolution import GQSP, Trotter, create_test_hamiltonians\n", + "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)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d8527d79", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quantum Algorithms Demo \n", + " ========================================\n", + "Testing GQSP, Trotter, and Prep-Select algorithms \n", + " ========================================\n", + "Created 5 test Hamiltonians\n", + "Available Hamiltonians: ['tfim', 'heisenberg', 'random_dense', 'random_sparse', 'hubbard']\n" + ] + } + ], + "source": [ + "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", + "test_qubits = [*range(3)]\n", + "\n", + "print(f\"Created {len(test_hamiltonians)} test Hamiltonians\")\n", + "print(f\"Available Hamiltonians: {list(test_hamiltonians.keys())}\")" + ] + }, + { + "cell_type": "markdown", + "id": "fa802d73", + "metadata": {}, + "source": [ + "\n", + "---\n", + "\n", + "## 2. GQSP Algorithm Demonstrations \n", + "\n", + "### Demo 2.1: Basic GQSP vs Variable Depth" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5714c87f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "GQSP Algorithm - Basic vs Multi-Depth\n", + "------------------------------------------\n", + "Configuration 1: Basic GQSP (depth=1)\n", + "Basic GQSP: 564 characters\n", + "\tPhases used: 3\n", + "OPENQASM 3;\n", + "include \"stdgates.inc\";\n", + "qubit[4] qb;\n", + "bit[4] cb;\n", + "gate TFIM_3q_J100_h70(time) aa,ab,ac{\n", + "\tcnot aa, ab;\n", + "\trz(2.0 * time) ab;\n", + "\tcnot aa, ab;\n", + "\tcnot ab, ac;\n", + "\trz(2.0 * time) ac;\n", + "\tcnot ab, ac;\n", + "\tcnot ac, aa;\n", + "\trz(2.0 * time) aa;\n", + "\tcnot ac, aa;\n", + "\trx(1.4 * time) aa;\n", + "\trx(1.4 * time) ab;\n", + "\trx(1.4 * time) ac;\n", + "}\n", + "\n", + "gate GQSP_1_TFIM(θa,θb,θc) aa,ab,ac,ad{\n", + "\try(θa) aa;\n", + "\tctrl(1) @ TFIM_3q_J100_h70(0.1) aa, ab, ac, ad;\n", + "\tp(θb) aa;\n", + "\try(θc) aa;\n", + "}\n", + "\n", + "GQSP_1_TFIM(0.1,0.2,0.3) qb[3],qb[0],qb[1],qb[2];\n", + "cb[{3}] = measure qb[{3}];\n", + "cb[{0, 1, 2}] = measure qb[{0, 1, 2}];\n", + "\n", + "\n", + "Configuration 2: Multi-depth GQSP (depth=3)\n", + "Multi-depth GQSP: 746 characters\n", + "\tPhases used: 7\n", + "OPENQASM 3;\n", + "include \"stdgates.inc\";\n", + "qubit[4] qb;\n", + "bit[4] cb;\n", + "gate TFIM_3q_J100_h70(time) aa,ab,ac{\n", + "\tcnot aa, ab;\n", + "\trz(2.0 * time) ab;\n", + "\tcnot aa, ab;\n", + "\tcnot ab, ac;\n", + "\trz(2.0 * time) ac;\n", + "\tcnot ab, ac;\n", + "\tcnot ac, aa;\n", + "\trz(2.0 * time) aa;\n", + "\tcnot ac, aa;\n", + "\trx(1.4 * time) aa;\n", + "\trx(1.4 * time) ab;\n", + "\trx(1.4 * time) ac;\n", + "}\n", + "\n", + "gate GQSP_3_TFIM(θa,θb,θc,θd,θe,θf,θg) aa,ab,ac,ad{\n", + "\try(θa) aa;\n", + "\tctrl(1) @ TFIM_3q_J100_h70(0.1) aa, ab, ac, ad;\n", + "\tp(θb) aa;\n", + "\try(θe) aa;\n", + "\tctrl(1) @ TFIM_3q_J100_h70(0.1) aa, ab, ac, ad;\n", + "\tp(θc) aa;\n", + "\try(θf) aa;\n", + "\tctrl(1) @ TFIM_3q_J100_h70(0.1) aa, ab, ac, ad;\n", + "\tp(θd) aa;\n", + "\try(θg) aa;\n", + "}\n", + "\n", + "GQSP_3_TFIM(0.1,0.2,0.3,0.15,0.25,0.35,0.05) qb[3],qb[0],qb[1],qb[2];\n", + "cb[{3}] = measure qb[{3}];\n", + "cb[{0, 1, 2}] = measure qb[{0, 1, 2}];\n", + "\n", + "\n", + "Comparison:\n", + " Basic (depth=1): 564 chars, 3 phases\n", + " Multi (depth=3): 746 chars, 7 phases\n", + " Size ratio: 1.32x\n" + ] + } + ], + "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", + " # Get a test Hamiltonian\n", + " hamiltonian = list(test_hamiltonians.values())[0]\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", + "\n", + " # Setup qasm program\n", + " builder1 = QasmBuilder(3)\n", + " std1 = builder1.import_library(std_gates)\n", + " gqsp1 = builder1.import_library(GQSP)\n", + "\n", + " # Hamiltonian abstraction interface (need to remove time evolution for GQSP)\n", + " class BasicHam(hamiltonian):\n", + " def apply(self, *args, **kwargs):\n", + " super().apply(0.1, *args, **kwargs)\n", + " def controlled(self, *args, **kwargs):\n", + " super().controlled(0.1, *args, **kwargs)\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", + " print(f\"Basic GQSP: {len(basic_program)} characters\")\n", + " print(f\"\\tPhases used: {len(basic_phases)}\")\n", + " print(basic_program)\n", + " \n", + " except Exception as e:\n", + " print(f\"Basic GQSP failed: {str(e)}\")\n", + " return\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", + " builder2 = QasmBuilder(3)\n", + " std2 = builder2.import_library(std_gates)\n", + " gqsp2 = builder2.import_library(GQSP)\n", + " \n", + " class MultiHam(hamiltonian):\n", + " def apply(self, *args, **kwargs):\n", + " super().apply(0.1, *args, **kwargs)\n", + " def controlled(self, *args, **kwargs):\n", + " super().controlled(0.1, *args, **kwargs)\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", + " print(f\"Multi-depth GQSP: {len(multi_program)} characters\")\n", + " print(f\"\\tPhases used: {len(multi_phases)}\")\n", + " print(multi_program)\n", + " \n", + " except Exception as e:\n", + " print(f\"Multi-depth GQSP failed: {str(e)}\")\n", + " return\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", + " return {\n", + " 'basic': {'length': len(basic_program), 'phases': len(basic_phases)},\n", + " 'multi': {'length': len(multi_program), 'phases': len(multi_phases)}\n", + " }\n", + "\n", + "# Run GQSP configurations demo\n", + "gqsp_results = demo_gqsp_configurations()" + ] + }, + { + "cell_type": "markdown", + "id": "bbc8097b", + "metadata": {}, + "source": [ + "\n", + "---\n", + "\n", + "## 3. Trotter Decomposition Examples \n", + "\n", + "### Demo 3.1: Two-Hamiltonian vs Multi-Hamiltonian Trotter" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "7c651bcd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Trotter Decomposition - Two vs Multi-Hamiltonian\n", + "----------------------------------------------------\n", + "Configuration 1: Two-Hamiltonian Trotter (Suzuki)\n", + "Two-Hamiltonian: 1858 characters\n", + "\tMethod: Suzuki-Trotter, Depth: 2\n", + "OPENQASM 3;\n", + "include \"stdgates.inc\";\n", + "qubit[3] qb;\n", + "bit[3] cb;\n", + "gate TFIM_3q_J100_h70(time) aa,ab,ac{\n", + "\tcnot aa, ab;\n", + "\trz(2.0 * time) ab;\n", + "\tcnot aa, ab;\n", + "\tcnot ab, ac;\n", + "\trz(2.0 * time) ac;\n", + "\tcnot ab, ac;\n", + "\tcnot ac, aa;\n", + "\trz(2.0 * time) aa;\n", + "\tcnot ac, aa;\n", + "\trx(1.4 * time) aa;\n", + "\trx(1.4 * time) ab;\n", + "\trx(1.4 * time) ac;\n", + "}\n", + "\n", + "gate HeisenbergXYZ_3q_Jx100_Jy120_Jz80(time) aa,ab,ac{\n", + "\try(pi/2) aa;\n", + "\try(pi/2) ab;\n", + "\tcnot aa, ab;\n", + "\trz(2.0 * time) ab;\n", + "\tcnot aa, ab;\n", + "\try(-pi/2) aa;\n", + "\try(-pi/2) ab;\n", + "\trx(-pi/2) aa;\n", + "\trx(-pi/2) ab;\n", + "\tcnot aa, ab;\n", + "\trz(2.4 * time) ab;\n", + "\tcnot aa, ab;\n", + "\trx(pi/2) aa;\n", + "\trx(pi/2) ab;\n", + "\tcnot aa, ab;\n", + "\trz(1.6 * time) ab;\n", + "\tcnot aa, ab;\n", + "\try(pi/2) ab;\n", + "\try(pi/2) ac;\n", + "\tcnot ab, ac;\n", + "\trz(2.0 * time) ac;\n", + "\tcnot ab, ac;\n", + "\try(-pi/2) ab;\n", + "\try(-pi/2) ac;\n", + "\trx(-pi/2) ab;\n", + "\trx(-pi/2) ac;\n", + "\tcnot ab, ac;\n", + "\trz(2.4 * time) ac;\n", + "\tcnot ab, ac;\n", + "\trx(pi/2) ab;\n", + "\trx(pi/2) ac;\n", + "\tcnot ab, ac;\n", + "\trz(1.6 * time) ac;\n", + "\tcnot ab, ac;\n", + "}\n", + "\n", + "def trot_suz_3_TFIM_HeisenbergXYZ_2(qubit[3] qubits,float time,int recursion_depth) {\n", + "\tif (recursion_depth < 2){\n", + "\t\tTFIM_3q_J100_h70(time/2) qb[qubits[0]],qb[qubits[1]],qb[qubits[2]];\n", + "\t\tHeisenbergXYZ_3q_Jx100_Jy120_Jz80(time) qb[qubits[0]],qb[qubits[1]],qb[qubits[2]];\n", + "\t\tTFIM_3q_J100_h70(time/2) qb[qubits[0]],qb[qubits[1]],qb[qubits[2]];\n", + "\t\treturn;\n", + "\t}\n", + "\tfloat suzuki_coeff = 1.0/(4.0 - pow(4.0, 1.0/(2.0*recursion_depth - 1.0)));\n", + "\ttrot_suz_3_TFIM_HeisenbergXYZ_2(qubits, suzuki_coeff*time, recursion_depth-1);\n", + "\ttrot_suz_3_TFIM_HeisenbergXYZ_2(qubits, suzuki_coeff*time, recursion_depth-1);\n", + "\ttrot_suz_3_TFIM_HeisenbergXYZ_2(qubits, (1.0-4.0*suzuki_coeff)*time, recursion_depth-1);\n", + "\ttrot_suz_3_TFIM_HeisenbergXYZ_2(qubits, suzuki_coeff*time, recursion_depth-1);\n", + "\ttrot_suz_3_TFIM_HeisenbergXYZ_2(qubits, suzuki_coeff*time, recursion_depth-1);\n", + "}\n", + "trot_suz_3_TFIM_HeisenbergXYZ_2({0,1,2}, 0.5, 2);\n", + "cb[{0, 1, 2}] = measure qb[{0, 1, 2}];\n", + "\n", + "\n", + "Configuration 2: Multi-Hamiltonian Trotter\n", + "Multi-Hamiltonian: 3342 characters\n", + "\tHamiltonians: 3, Depth: 2\n", + "\n", + "Configuration 3: Linear Trotter Decomposition\n", + "Linear Trotter: 1769 characters\n", + "Method: First-order, Steps: 4\n", + "Trotter Configuration Comparison:\n", + "\tTwo-Hamiltonian (Suzuki): 1858 chars\n", + "\tMulti-Hamiltonian: 3342 chars\n", + "\tLinear decomposition: 1769 chars\n" + ] + } + ], + "source": [ + "def demo_trotter_configurations():\n", + " \"\"\"Compare two-Hamiltonian and multi-Hamiltonian Trotter decomposition.\"\"\"\n", + " print(\"\\nTrotter Decomposition - Two vs Multi-Hamiltonian\")\n", + " print(\"-\" * 52)\n", + " \n", + " hamiltonians = list(test_hamiltonians.values())\n", + " \n", + " # Configuration 1: Two-Hamiltonian Trotter\n", + " print(\"Configuration 1: Two-Hamiltonian Trotter (Suzuki)\")\n", + " ham1, ham2 = hamiltonians[0], hamiltonians[1]\n", + " \n", + " builder1 = QasmBuilder(3)\n", + " std1 = builder1.import_library(std_gates)\n", + " trotter1 = builder1.import_library(Trotter)\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", + " print(f\"Two-Hamiltonian: {len(two_ham_program)} characters\")\n", + " print(f\"\\tMethod: Suzuki-Trotter, Depth: 2\")\n", + " print(two_ham_program)\n", + " \n", + " except Exception as e:\n", + " print(f\"Two-Hamiltonian Trotter failed: {str(e)}\")\n", + " return\n", + " \n", + " # Configuration 2: Multi-Hamiltonian Trotter\n", + " print(\"\\nConfiguration 2: Multi-Hamiltonian Trotter\")\n", + " multi_hams = hamiltonians[:3] # Use 3 Hamiltonians\n", + " \n", + " builder2 = QasmBuilder(3)\n", + " std2 = builder2.import_library(std_gates)\n", + " trotter2 = builder2.import_library(Trotter)\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", + " print(f\"Multi-Hamiltonian: {len(multi_ham_program)} characters\")\n", + " print(f\"\\tHamiltonians: 3, Depth: 2\")\n", + " # print(multi_ham_program)\n", + "\n", + " except Exception as e:\n", + " print(f\"Multi-Hamiltonian Trotter failed: {str(e)}\")\n", + " return\n", + " \n", + " # Configuration 3: Linear Trotter (bonus comparison)\n", + " print(\"\\nConfiguration 3: Linear Trotter Decomposition\")\n", + " \n", + " builder3 = QasmBuilder(3)\n", + " std3 = builder3.import_library(std_gates)\n", + " trotter3 = builder3.import_library(Trotter)\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", + " print(f\"Linear Trotter: {len(linear_program)} characters\")\n", + " print(f\"Method: First-order, Steps: 4\")\n", + " \n", + " except Exception as e:\n", + " print(f\"Linear Trotter failed: {str(e)}\")\n", + " linear_program = \"\"\n", + " \n", + " # Compare all configurations\n", + " print(f\"Trotter Configuration Comparison:\")\n", + " print(f\"\\tTwo-Hamiltonian (Suzuki): {len(two_ham_program)} chars\")\n", + " print(f\"\\tMulti-Hamiltonian: {len(multi_ham_program)} chars\")\n", + " if linear_program:\n", + " 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", + " }\n", + "\n", + "# Run Trotter configurations demo\n", + "trotter_results = demo_trotter_configurations()" + ] + }, + { + "cell_type": "markdown", + "id": "60f138e8", + "metadata": {}, + "source": [ + "\n", + "---\n", + "\n", + "## 4. Preparation-Selection Algorithms \n", + "\n", + "### Demo 4.1: Matrix Input vs Operator Chain Input" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4b4b3bff", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Preparation-Selection - Matrix vs Operator Chain\n", + "----------------------------------------------------\n", + "Configuration 1: Matrix Input\n", + "Testing Pauli-Z matrix (2, 2)...\n", + "[('Z', np.complex128(1+0j))]\n", + "Pauli-Z: 508 characters\n", + "Testing Random 4x4 matrix (4, 4)...\n", + "[('II', np.complex128(0.4604637367120007+0.838946781387169j)), ('XI', np.complex128(0.5655598377382944+0.588833075307832j)), ('XX', np.complex128(0.4238844968885312+0.3326317061016123j)), ('IX', np.complex128(0.33106499350145013+0.33007330113196753j)), ('XY', np.complex128(-0.06002262495215413+0.2992097507618375j)), ('IY', np.complex128(-0.1797796889495182+0.20356340119461624j)), ('YI', np.complex128(-0.13521719508002197+0.19794667244217146j)), ('ZX', np.complex128(0.02442078075316162-0.23360780554666702j)), ('XZ', np.complex128(-0.18057751029366081-0.148700172760367j)), ('ZY', np.complex128(0.2184605953247674+0.03821255057383305j)), ('IZ', np.complex128(-0.095147349933698+0.07351264181838879j)), ('ZI', np.complex128(-0.11616413227155017+0.02567510247519522j)), ('YY', np.complex128(-0.05126587163767765+0.10703189572053626j)), ('YZ', np.complex128(-0.09614107149005136-0.02889548280897075j)), ('YX', np.complex128(-0.0870637778551501+0.04327330864487838j)), ('ZZ', np.complex128(0.0896637111235748-0.035398963079813106j))]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Random 4x4: 2019 characters\n", + "\n", + "Configuration 2: Operator Chain Input\n", + "Testing Single Pauli chain (3 operators)...\n", + "[('X', 0.5), ('Z', 0.3), ('Y', 0.2)]\n", + "\tSingle Pauli: 719 characters\n", + "\tOperators: ['X', 'Z', 'Y']\n", + "OPENQASM 3;\n", + "include \"stdgates.inc\";\n", + "qubit[5] qb;\n", + "bit[5] cb;\n", + "gate PREP_3905172865891942725 aa,ab{\n", + "\try(1.0808368368267307) aa;\n", + "\try(0.6608524809625362) ab;\n", + "\tcry(-0.5404152269046208) aa,ab;\n", + "\tcry(-0.540415208375584) aa,ab;\n", + "}\n", + "\n", + "gate X aa{\n", + "\tx aa;\n", + "}\n", + "\n", + "gate Z aa{\n", + "\tz aa;\n", + "}\n", + "\n", + "gate Y aa{\n", + "\ty aa;\n", + "}\n", + "\n", + "gate SEL_8998145479025848162 aa,ab,ac,ad{\n", + "\tctrl(2) @ X aa,ab,ac,ad;\n", + "\tx aa;\n", + "\tctrl(2) @ Z aa,ab,ac,ad;\n", + "\tx ab;\n", + "\tctrl(2) @ Y aa,ab,ac,ad;\n", + "\tx aa;\n", + "\tx ab;\n", + "}\n", + "\n", + "gate PS_2_8654309695166846104 aa,ab,ac,ad{\n", + "\tPREP_3905172865891942725 aa,ab;\n", + "\tSEL_8998145479025848162 aa,ab,ac,ad;\n", + "\tinv @ PREP_3905172865891942725 aa,ab;\n", + "}\n", + "\n", + "PS_2_8654309695166846104 qb[3],qb[4],qb[0],qb[1];\n", + "cb[{3, 4}] = measure qb[{3, 4}];\n", + "cb[{0, 1, 2, 3}] = measure qb[{0, 1, 2, 3}];\n", + "\n", + "Testing Two-qubit Pauli chain (3 operators)...\n", + "[('XX', 0.7), ('ZZ', 0.4), ('XY', 0.1)]\n", + "\tTwo-qubit Pauli: 756 characters\n", + "\tOperators: ['XX', 'ZZ', 'XY']\n", + "\n", + "Prep-Select Configuration Comparison:\n", + "Matrix inputs:\n", + "\tPauli-Z: 508 chars\n", + "\tRandom 4x4: 2019 chars\n", + "Operator chains:\n", + "\tSingle Pauli: 719 chars\n", + "\tTwo-qubit Pauli: 756 chars\n" + ] + } + ], + "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", + " test_qubits = [*range(4)]\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", + " ]\n", + " \n", + " matrix_results = {}\n", + " \n", + " for name, matrix in test_matrices:\n", + " print(f\"Testing {name} matrix {matrix.shape}...\")\n", + " \n", + " builder = QasmBuilder(3)\n", + " std = builder.import_library(std_gates)\n", + " prep_sel = builder.import_library(PrepSelLibrary)\n", + " \n", + " try:\n", + " prep_sel.prep_select(test_qubits, matrix, approximate=0.1)\n", + " std.measure(test_qubits, test_qubits)\n", + " \n", + " program = builder.build()\n", + " matrix_results[name] = len(program)\n", + " \n", + " print(f\"{name}: {len(program)} characters\")\n", + " \n", + " except Exception as e:\n", + " matrix_results[name] = 0\n", + " print(f\"{name}: {str(e)}\")\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", + " ]\n", + " \n", + " chain_results = {}\n", + " \n", + " for name, chain in test_chains:\n", + " print(f\"Testing {name} chain ({len(chain)} operators)...\")\n", + " \n", + " builder = QasmBuilder(3)\n", + " std = builder.import_library(std_gates)\n", + " prep_sel = builder.import_library(PrepSelLibrary)\n", + " \n", + " try:\n", + " prep_sel.prep_select(test_qubits[:2], chain)\n", + " std.measure(test_qubits, test_qubits)\n", + " \n", + " program = builder.build()\n", + " chain_results[name] = len(program)\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", + " print(program)\n", + "\n", + " except Exception as e:\n", + " chain_results[name] = 0\n", + " print(f\"{name}: {str(e)}\")\n", + " \n", + " # Compare configurations\n", + " print(f\"\\nPrep-Select Configuration Comparison:\")\n", + " print(f\"Matrix inputs:\")\n", + " for name, length in matrix_results.items():\n", + " print(f\"\\t{name}: {length} chars\")\n", + " print(f\"Operator chains:\")\n", + " for name, length in chain_results.items():\n", + " print(f\"\\t{name}: {length} chars\")\n", + "\n", + " return {'matrix': matrix_results, 'chain': chain_results}\n", + "\n", + "# Run prep-select configurations demo\n", + "prep_select_results = demo_prep_select_configurations()" + ] + }, + { + "cell_type": "markdown", + "id": "af33b962", + "metadata": {}, + "source": [ + "\n", + "---\n", + "\n", + "## 5. Algorithm Integration Example \n", + "\n", + "### Demo 5.1: Combined Algorithm Pipeline\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a44b4a64", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "🔗 Algorithm Integration - Combined Pipeline\n", + "--------------------------------------------\n", + "Building combined algorithm pipeline...\n", + "Pipeline: Trotter → GQSP → Prep-Select\n", + " Step 1: Applying Trotter decomposition...\n", + " Step 2: Applying GQSP...\n", + " Step 3: Applying prep-select...\n", + "[('Z', np.complex128(1+0j))]\n", + "Integrated pipeline: 2465 characters\n", + " Contains Trotter: True\n", + " Contains GQSP: True\n", + " Contains PrepSelect: True\n" + ] + } + ], + "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", + " hamiltonians = list(test_hamiltonians.values())[:2]\n", + " \n", + " print(\"Building combined algorithm pipeline...\")\n", + " print(\"Pipeline: Trotter → GQSP → Prep-Select\")\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", + " class IntegratedHam(hamiltonians[0]):\n", + " def apply(self, *args, **kwargs):\n", + " super().apply(0.1, *args, **kwargs)\n", + " def controlled(self, *args, **kwargs):\n", + " super().controlled(0.1, *args, **kwargs)\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", + " # 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", + " # 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", + " # Measure all qubits\n", + " std.measure(qubits, qubits)\n", + " \n", + " # Build complete program\n", + " integrated_program = builder.build()\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", + " return {\n", + " 'success': True,\n", + " 'total_length': len(integrated_program),\n", + " 'algorithms_used': 3\n", + " }\n", + " \n", + " except Exception as e:\n", + " print(f\"Algorithm integration failed: {str(e)}\")\n", + " return {'success': False, 'error': str(e)}\n", + "\n", + "# Run integration demo\n", + "integration_results = demo_algorithm_integration()" + ] + }, + { + "cell_type": "markdown", + "id": "182eb0b7", + "metadata": {}, + "source": [ + "\n", + "---\n", + "\n", + "## 6. Amplitude Amplification Example \n", + "\n", + "### Demo 6.1: Grovers\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "22dfb86d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3;\n", + "include \"stdgates.inc\";\n", + "qubit[3] qb;\n", + "gate Z_on_two3 aa,ab,ac{\n", + "\tx ac;\n", + "\tctrl(2) @ z aa, ab, ac;\n", + "\tx ac;\n", + "}\n", + "\n", + "def Grover3Z_on_two3(qubit[3] reg) {\n", + "\th reg;\n", + "\tfor int i in [0:2] {\n", + "\t\t//Za\n", + "\t\tZ_on_two3 reg[0], reg[1], reg[2];\n", + "\t\th reg;\n", + "\t\t//Z0\n", + "\t\tx reg;\n", + "\t\tctrl(2) @ z reg[0], reg[1], reg[0];\n", + "\t\tx reg;\n", + "\t\th reg;\n", + "\t}\n", + "}\n", + "\n", + "input angle[32] theta ;\n", + "Grover3Z_on_two3(qb[{0 ,1 ,2}]);\n", + "\n" + ] + } + ], + "source": [ + "# Create 3-qubit circuit with OpenQASM 3.0\n", + "alg = QasmBuilder(3, 0, version=\"3\")\n", + "reg = list(range(3))\n", + "\n", + "# Import standard gates and algorithm libraries\n", + "program = alg.import_library(std_gates)\n", + "ampl = alg.import_library(AALibrary)\n", + "\n", + "\n", + "class Za(GateLibrary):\n", + " \"\"\"Custom gate: controlled-Z on all qubits except index 2.\"\"\"\n", + " name = \"Z_on_two\"\n", + " def __init__(self, *args, **kwargs):\n", + " super().__init__(*args, **kwargs)\n", + "\n", + " self.name = f\"Z_on_two{len(reg)}\"\n", + " names = string.ascii_letters\n", + " qargs = [\n", + " names[i // len(names)] + names[i % len(names)] for i in range(len(reg))\n", + " ]\n", + "\n", + " sys = GateBuilder()\n", + " std = sys.import_library(std_gates)\n", + " std.call_space = \" {}\"\n", + "\n", + " ind = dict(zip(range(len(reg)), qargs))\n", + " ind.pop(2)\n", + "\n", + " # Gate definition\n", + " std.begin_gate(self.name, qargs)\n", + " std.x(qargs[2])\n", + " std.controlled_op(\"z\", (qargs[2], list(ind.values())), n=len(reg) - 1)\n", + " std.x(qargs[2])\n", + " std.end_gate()\n", + "\n", + " # Collect gate definitions and imports\n", + " self.merge(*sys.build(),self.name)\n", + "\n", + " def apply(self, qubits):\n", + " \"\"\"Apply the custom gate to a set of qubits.\"\"\"\n", + " self.call_gate(self.name, qubits[-1], qubits[:-1])\n", + "\n", + " def controlled(self, qubits, control):\n", + " \"\"\"Controlled version of the custom gate.\"\"\"\n", + " self.controlled_op(self.name, (qubits[-1], [control] + qubits[:-1]))\n", + "\n", + "\n", + "# Define input parameter\n", + "theta = program.add_var(\"theta\", type=\"input angle[32]\")\n", + "\n", + "# Apply Grover with custom gate\n", + "ampl.grover(Za, reg, 3)\n", + "\n", + "# Build and validate program\n", + "prog = alg.build()\n", + "print(prog)\n", + "\n", + "# res = pq.loads(prog)\n", + "# print(res)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be6f2e09", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/demo_qasmbuilder.ipynb b/examples/demo_qasmbuilder.ipynb new file mode 100644 index 0000000..28c799c --- /dev/null +++ b/examples/demo_qasmbuilder.ipynb @@ -0,0 +1,497 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "id": "70e484ad", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "\n", + "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '..')))" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f118cb1e", + "metadata": {}, + "outputs": [], + "source": [ + "from qbraid_algorithms.qtran import *\n", + "from qbraid_algorithms.qft import QFTLibrary\n", + "from qbraid_algorithms.amplitude_amplification import *\n", + "import pyqasm as pq" + ] + }, + { + "cell_type": "markdown", + "id": "1e215654", + "metadata": {}, + "source": [ + "
\n", + "

QasmBuilder Demo

\n", + "\n", + "QasmBuilder is a small tool to help build out algorithms within the qbraid algorithms package.
\n", + "It is a macroed string builder at heart which exposes development at all levels of abstraction,
\n", + "and is a QOL feature. Its patterns can be pierced at any point in time and development if the
\n", + "overhead is too much. This comes at minimal cost to system QOL benefits like easy import and
\n", + "ancilla tracking, with the only risk being the lack of guarantee of programmatic correctness of
\n", + "developer added code\n", + "***\n", + "This notebook looks to demonstrate the package structure and the design patterns first expected
\n", + "from this package\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "c7846830", + "metadata": {}, + "source": [ + "\n", + "QOL items this package provides:
\n", + "RESOURCE MANAGEMENT:
\n", + "\n", + "- Use claim_qubits() and claim_clbits() for dynamic allocation\n", + "- Track resource usage across library imports\n", + "- Ensure proper cleanup of scope levels before building\n", + "\n", + "ERROR HANDLING:
\n", + "\n", + "- All builders check for unclosed scopes before generation\n", + "- Invalid gate references are caught during library operations\n", + "- Resource conflicts are handled through the allocation system\n" + ] + }, + { + "cell_type": "markdown", + "id": "e533e561", + "metadata": {}, + "source": [ + "
\n", + "Here is a short demo of the power of the QasmBuilder library before diving deeper \n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f6c9051c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3;\n", + "include \"stdgates.inc\";\n", + "qubit[5] qb;\n", + "bit[5] cb;\n", + "h qb[1];\n", + "/*\n", + "This is a \n", + "Multi-line comment\n", + "*/\n", + "//Single line comment\n", + "for int i in [0:4] {\n", + "\tx qb[i];\n", + "\t//Inside loop\n", + "}\n", + "cb[{1, 2}] = measure qb[{1, 2}];\n", + "\n" + ] + }, + { + "data": { + "image/png": "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jRkzRtnZ2ZXWMcvMcBUzisDUpWbPnq3k5OSrbrtz586Vhri8//77zvCXI0eOeNtM+NEb776uU2dPaO7ExZXGLA948Pf6+C8rNWPhJHW/+z7FRAfnhZooL84eENy8DljTi122bJlzu6Ri7/R6wjDQrwji6r45+JU+WLtEj/YdrI5J/3rDNEzYvjRihh57vq8Twi8MD45PdD311FM/+7wZpojayW8XucwHEUyZITMzs8avefXVV52bGefpFo/16OHcrmTdSy8pmJgPE3y19tgVnzehu3/tjze0TUCtClgzPtDw9iOvw4cP129/+1vnvvlYLQAEfcBu3rzZ64Hw18KMuTU3AHADLnMCgCUELABYQsACgCUELABYQsACgCUELABYQsACgCUELABYQsACgCUELABYQsACgCUEbFUej2obTy07ADMjgJu5vf21CQFbhTdTrQSk0FB5vJwSJSzS3f8NwiJCvJ5Sx83c3v7axN1/WRaExMQopEEDuVV406byePk1kLE3Rci1PFI9L9ufmJjozC3nVpfmw0PgI2AvI7pbN4XUry+3CW3cWJH33OP16+KaR6lhyyjXVUdCwz36xZ31FR7h3RtKWFiY7rvvPtdN7WJKA7fccou1yRThe54y5mm5opLsbJXm5Zm5bBTQQkIUWq+eQupe3xxXJUWlKsguUpnvZ/C2Eq5R9cPlCbn2dwXzX//MmTMqLDSz6l4fM5Psli1bnPvdu3fXtm3bnPtmtmUT6NfLTKDYoEED170p1HZ+mzLGDULr13dutUVoeIjqNnJxueAaeoSNGjXyybaKiorK7zdt2rT8frNmzVxdjsD1oUQAAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgSZitDbtZWVmZsjILdP74BRUVlkhlCmweKSImVHHNohSbEOnv1iBAHTt2TAcPHtS5c+dUWlqqQObxeBQVFaUWLVqoffv2CglxZ1+QgL2Mn/5vrs78kC83uZhXopyTF5V4a6katoz2d3MQYI4ePaotW7Y4nQe3OH/+vH766SedOXNG9957r9zInW8LFpUWl+ns390VrhWddtkbA26Mb775xlXhWtH333+v/Hx3/r8mYKu4kFesshK5VlFBiYovBvbpH2480wt0szMubT8BW0VZqTvf5YPtGOBbgV5zvZqSEnf2eghYALCEgAUASwhYALCEgAUASwhYALCEgAUASwhYALCEgAUASwhYALCEgAUASwhYALCEgL0BUtKf1V2/aakjmYerPbdo5R90+wMJ2rxro1/aBsBFAduqVSvNnTv3ql+ma25xcXFebXvw4MHlr129erXcYvxTkxUZEaWp88dXWp554qgWrJitPt36qmeXX/mtfQCCrAe7ZMkSHThwoPzxn//8Z/Xp00eNGzdWbGysunbtqg0bNlR6zbx583T8+HG5TXxcYz0/ZKL+tn+71nz6Yfny6RmpCgsNU+qw6X5tH+Ctt99+2+nwvPPOO9Wee/fdd53n3n77bdV2fgtY03tt0qRJ+eMvvvjCCdj169drz5496tWrl/r166d9+/aVr1O/fn0lJCTIjR5Ofkydb/2lZi6aonPnz2r9ltXatudzjRyUopsaJfq7eYDXGjZsqF27dunixYvly8z9nTt3Kj4+3q9tc23A5uXladCgQapbt64SExM1a9Ys9ezZU6NHjy5fJycnRwMGDFBMTIyaNWumjIyMq27XlBXGjx+ve+65R23bttWrr77q/Pzkk08UDExZ4+WRacrJz9G0+SlKWzhJt7XtpAEPDvF304Br0rJlSydId+/eXb7MdI7MMjOXFq4hYMeNG+fM7bNmzRpt3LhRmzdv1t69eyutk56erk6dOjm9z9TUVI0aNUqbNm3y+guCTVCbd8lg0aZlew1+6Blt2PaJsrLP6OWR6a6dzA0wzFxZ27ZtK3+8detWde/e3a9tcu2kh7m5uVq8eLGWL1+u3r17O8uWLl2q5s2bV1qvW7duTrAaSUlJ2r59u+bMmeOUAGpq5syZzv5++9vfyhbTG6+qsMDuN6c3iP3/bxiN4xPUtmV7K/sw8xeFlRDcN1JxcXH5/YrzRzn/FmHBO7eouVayatUqnT592nlsZq195pln9N133/l0PxcuXLjs36s5Sw5kXv3LHz582KmxdOnSpXyZ6WG2a9eu2i+96uOrjSyo6P3339eUKVOcXnLFOq2vmTJHVZ1vvUfLZtopSxw/dUwZ76U7wXrw6Hf600cZGjbgOZ/vp02bNjqdddLn28WV1alTRwsXLiwfSTN//nznvvn/W7FG6S+XuxjlC+aCtDlbNb1YM6miuV+vXj2f7+d3v/tdpVLEJYE+kWPAdXM++OADPfnkk1q5cqXuv/9+BZNX33rR+fnWtPeV3L2fFn44T/84ftTfzQJ8UiYwZ6punV47IHqwrVu3Vnh4uHPl8FIROysryxlu1aNHj/L1zFXEiszjDh06XHX7K1as0JAhQ5yQ7du3r2wzJYiqCrNLdOKrAp/v69Md6/X5zg1KeXqqEho1Vcqwadq+d7NeeTNVC6at8Om+Dh06pLCIgHvvDPoSwaWx2UeOHNHatWud+ydPngyIEsFHH31kbdt33HGHc/zmQm7Hjh2t7GP58uXVSpFuEObtKfXQoUOdC13mSqE5/ZkwYUK1CzXmnSwtLU39+/d3Lm6ZGs26deuuWhZ44oknnLGupgRx4sQJZ3lUVJQzPMuGy9VvPM7pnG8DNi8/V68tmKgOrTtqYL+hzrIm8Qka8XiKXv/jRG3Y+rGS7/21z/YXHR2t8MhQn20PV1dUVFTp91/xvumUBDPz9//aa6+V37chIiIi4Outl+P1b8OMEDCnAWaMqjmFN1cM77777krrjBkzxqmXdO7cWdOnT9fs2bOVnJz8s9s19SvzLvjss886w78u3cwIBLd7493XdersCU0amabQ0H8F34AHf69b29yhGQsnOSEMuJXpCJkbKvP63MX0YpctW+bcLqnYOzWnR9fCDPcKRt8c/EofrF2iR/sOVsekzpWeM2H70ogZeuz5vk4IvzCcT3TBHZ566qmffT4YOka+4LfikPkggikzZGZm1vg1w4cPd2oxbmI+TPDV2mNXfN6E7v61P97QNgEI4oA1Y+WMiqfLNTF16lSNHTvWuW/KBwAQ9AHr7em9Gad5LcxFNZvjYgHAlxjLAwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBW5XHI7fzhLj/GOBbZjoXNwtx6fT27my1RRExoa7OWDMXV2i4iw8AVjRo0EBuFhcXJzciYKsIDQ9R/aaRcqsGLaJc31uB77Vr105u1bRpU2d6cDfy/3SXAajp7bEKiwzV+ROFKiookcoCvyRQJzpUcc0iFX+z+yaGg31JSUnOG6+ZAfrcuXMqLS31yXYvbcecwle8f708Ho8zx5eZvfrOO++UWxGwVwism5LqOjcgWLRt29a5+XImXTMbtPHII49o5cqVzv1HH3006GfSrSlKBABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJYQsABgCQELAJaE2dpwMCgpKlVRYamkMgUyj8ej8MhQhYR5/N0UABUQsJdRfKFUx/ZnK/fMxUDP1nKeECk2IVJNb49VSChBCwQCSgSXkfnlOeWedk+4GmWlUvaPhTr+f877uykA/omAraKosER5Z4vkVudPXFBZqYveGYAgRsBWUVRQIjcrLS5TSREBCwQCAjYIlZURsEAgIGABwBICFgAsIWABwBICFgAsIWABwBICFgAsIWABwBICFgAsIWABwBICFgAsIWABwBIC9gZISX9Wd/2mpY5kHq723KKVf9DtDyRo866NfmkbABcFbKtWrTR37tyrfgO/ucXFxXm17cGDB5e/dvXq1XKL8U9NVmRElKbOH19peeaJo1qwYrb6dOurnl1+5bf2AQiyHuySJUt04MCB8sfbtm1Tt27dFB8fr6ioKLVv315z5syp9Jp58+bp+PHjcpv4uMZ6fshE/W3/dq359MPy5dMzUhUWGqbUYdP92j4AQTZljOm9NmnSpPxxTEyMRowYoTvuuMO5bwJ32LBhzv2nn37aWad+/frOzY0eTn5Maz5dqZmLpqjHL/tox74vtG3P53ph+HTd1CjR380DEAg92Ly8PA0aNEh169ZVYmKiZs2apZ49e2r06NHl6+Tk5GjAgAFOODZr1kwZGRlX3W7nzp2d19x2221OmeF3v/udkpOTtXXrVgUDU9Z4eWSacvJzNG1+itIWTtJtbTtpwIND/N00AIESsOPGjdOWLVu0Zs0abdy4UZs3b9bevXsrrZOenq5OnTpp3759Sk1N1ahRo7Rp0yav9mNeu2PHDvXo0UPBok3L9hr80DPasO0TZWWf0csj0xUSwnVGIFh5VSLIzc3V4sWLtXz5cvXu3dtZtnTpUjVv3rzSeqaWaoLVSEpK0vbt2516ap8+fa66D7OtU6dOqbi4WJMnT9aTTz4pW0xvvKpCy1PGNIht6PxsHJ+gti3bW9lHfn6+wkoI7hvJ/H+t+PuveD8sLDgnbw6EY46JiVEg8+q3cPjwYV28eFFdunQpX9awYUO1a9eu0npdu3at9vhqIwsuMSUBE+Q7d+50QrpNmzZO6cAGU+aoqvOt92jZzE+s7O/4qWPKeC/dCdaDR7/Tnz7K0LABz/l8P+Z3djrrpM+3iyurU6eOFi5c6Nw3Ja758+c79811BvM3E4wC4ZjLAnx6pIDr5tx8883q2LGjnnrqKT333HNOLzZYvPrWi87Pt6a9r+Tu/bTww3n6x/Gj/m4WgEDowbZu3Vrh4eHatWuXWrRo4SzLyspyhltVrJWa3mdF5nGHDh28blxpaakuXLggW0xPuarC7BKd+KrA5/v6dMd6fb5zg1KenqqERk2VMmyatu/drFfeTNWCaSt8uq9Dhw4pLCLg3juDmjldvjQ2+8iRI1q7dq1z/+TJk0FdIqhtx+ytMG9PqYcOHepc6DLjVc2pwIQJE6pdqDE117S0NPXv39+5uLVq1SqtW7fuZ7dtRhqY0DbjX40vvvhCM2fO1H//93/rRtZvPM6pjW8DNi8/V68tmKgOrTtqYL+hzrIm8Qka8XiKXv/jRG3Y+rGS7/21z/YXHR2t8MhQn20PV1dUVFTp91/xvumUBKPaeMze8vptxowQMD2/fv36qV69ehozZoyys7MrrWOW7d69W1OmTFFsbKxmz57tDLm6Wm/1hRde0A8//OC8+5ne8owZM5yxsG73xruv69TZE5o7cbFCQ/8VfAMe/L0+/stKzVg4Sd3vvk8x0dVrwgBqUcCaXuyyZcuc2yUVe6fmVOFajBw50rkFm28OfqUP1i7Ro30Hq2NS50rPmbB9acQMPfZ8XyeEzYcOAAQPvxVKzMgAU2bIzMys8WuGDx/uDBFzE/Nhgq/WHrvi8yZ096/98Ya2CUAQB+zBgwednxVPl2ti6tSpGjt2rHPffIoMAII+YM2nubwdp3ktzEW1it9fAACBjLE8AGAJAQsAlhCwAGAJAQsAlhCwAGAJAQsAlhCwAGAJAQsAlhCwAGAJAQsAlhCwAGAJARuMPP5uAACDgK0iJNT96RQMxwAEAwK2ioi6YQoNd29ARcaGKTSMf1YgEPCXWIUnxKOb2tWTG3lCpJuSmHYGCBRM/XgZDX4R5fQEz58oVFFBqcoU2HOvezwe1YkOVf3ESKcHDiAw8Nd4BVH1w50bAFwrSgQAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWELAAYAkBCwCWhNnasNsVXyxV7qkLKiooUVmZAponxKM60aGq1zhCIWEefzcHwD8RsJeRe/qC/r73nMpK5CqhdTxq9csGiqwX7u+mAKBEcHk/fn3edeFqlFws04n/k+PvZgD4JwK2igu5xSoqKJVb5Z0tUmlJgNc0gFqCgK2ipMi94XpJSbH7jwEIBgRsMKIDCwQEAhYALCFgAcASAhYALCFgAcASAhYALCFgAcASAhYALCFgAcASAhYALCFgAcASAhYALCFgb4CU9Gd1129a6kjm4WrPLVr5B93+QII279rol7YBcFHAtmrVSnPnzv3ZdTwej3OLi4vzatuDBw8uf+3q1avlFuOfmqzIiChNnT++0vLME0e1YMVs9enWVz27/Mpv7QMQZD3YJUuW6MCBA5d9bvv27QoLC9Odd95Zafm8efN0/PhxuU18XGM9P2Si/rZ/u9Z8+mH58ukZqQoLDVPqsOl+bR+AIAtY03tt0qRJteXnzp3ToEGD1Lt372rP1a9fXwkJCXKjh5MfU+dbf6mZi6bo3PmzWr9ltbbt+VwjB6XopkaJ/m4egEAI2Ly8PCcA69atq8TERM2aNUs9e/bU6NGjy9fJycnRgAEDFBMTo2bNmikjI6PG2x8+fLgGDhyorl27KpiYssbLI9OUk5+jafNTlLZwkm5r20kDHhzi76YBCJRJD8eNG6ctW7ZozZo1Tg/0xRdf1N69eyudzqenpzvLp0yZog0bNmjUqFFKSkpSnz59rlo2+P7777V8+XJNn27/tNm8WVRVWGBvMq42Ldtr8EPPaNHKNxQaEqo3pyxXSIjvTyLy8/MVVsL1yxupuLi40u+/4n1T7gpGgXDMMTExCmRe/RZyc3O1ePFiJwAvncIvXbpUzZs3r7Ret27dlJqa6tw3wWpqqnPmzPnZgD148KDzmq1bt96wfxzTC6+q8633aNnMT6zts0FsQ+dn4/gEtW3Z3so+2rRpo9NZJ61sG5dXp04dLVy4sPxC7/z58537phNy8eJFBaNAOOayssCevsOrbs7hw4edX1yXLl3KlzVs2FDt2rWrtF7V03vz+Ntvv73idktKSpyygOnxmkAOVsdPHVPGe+lOsJ44dUx/+qjmpRMA7hMQ5y6mZrt7927t27dPI0aMcJaVlpY6706mN7tx40bdd999Pt+v6ZFXVZhdohNfFciGV9960fn51rT3lb7wZS38cJ4e6PmQfpHY0qf7OXTokMIiKBHc6NPlS0MHjxw5orVr1zr3T548GdQlgtp2zN7y6rfQunVrhYeHa9euXWrRooWzLCsryxlu1aNHj/L1du7cWel15nGHDh2uuN3Y2Fj97//+b6Vlb775pj777DN99NFHuvnmm3Wj6jce59TG9wH76Y71+nznBqU8PVUJjZoqZdg0bd+7Wa+8maoF01b4dF/R0dEKjwz16Tbx84qKiir9/iveN38zwag2HrPVgDU1y6FDhzoXuuLj451ay4QJE6pdqDE117S0NPXv31+bNm3SqlWrtG7duitu17z+9ttvr7TMbDsyMrLacjfKy8/VawsmqkPrjhrYb6izrEl8gkY8nqLX/zhRG7Z+rOR7f+3vZgLwMa/78WaEgDm17tevn+rVq6cxY8YoOzu70jpmmTnlNzVV0zudPXu2kpOTVVu98e7rOnX2hOZOXKzQ0H/1LAc8+Ht9/JeVmrFwkrrffZ9ioqtfdANQiwLW9GKXLVvm3C6p2Ds1tRhfmDx5snNzu28OfqUP1i7Ro30Hq2NS50rPmbB9acQMPfZ8XyeEXxjOJ7qAYOK3SrT5IIIpM2RmZnr1IQQzRMxNzIcJvlp77IrPm9Ddv/bHG9omAEEcsGbMq1HxdLkmpk6dqrFjxzr3zafIACDoA3bz5s1eD4S/FubC1+W+vwAAAhGDJQHAEgIWACwhYAHAEgIWACwhYAHAEgIWACwhYAHAEgIWACwhYAHAEgIWACwhYAHAEgI2CHk8/m4BAIOArcLtc1l5QqSQMHcfAxAs+Eusok50mCLquXfCtrqNIhQSShcWCAQE7GU071Rf4VHu+9VExoYp8fZ6/m4GgH9yb1fNosh6YWrbo5EKsotVVFAiN9Rc68SEKrIeM3kCgYSAvQKPx6PouHDJ3ADgGrjvPBgAXIKABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsISABQBLCFgAsCTM1oYBXLtdu3bpH//4h/Ly8tSvXz81bNhQwaykpERbtmxRdna2QkNDFRkZqX//939XbGys3IyABa6BCb7CwsLyx8XFxeX3s7Kyyu+fPXtWYWHV/8xMgMTExFxx+y1bttTtt9+u//mf/1EgHq+3xxx5leM1kpKS1KxZM3k8Hn377bfasWOH/vM//1Nu5ikrKyvzdyMAt/W2Pvroo2qB4w0TOI888ojTW/s5Zj/33XefX3uwN/J4Lzl9+rQ2b97svMbNqMECXgoJCblqb+xqzOvNdtzAH8f77bffqkWLFnI7d/wLAwHEnMJ27tz5urZhXm+24wY3+nj379+vnJwc3XXXXXI7Aha4Bk2bNlV8fLzXIWnWN68zr3eTG3W8X3/9tf7+97/r/vvvv2zt2m0IWOA6enXeXsIw67up93ojj/ebb77RDz/8oD59+qhOnToKBu5/iwD83KszV81rEjwmZMzFqpr05v76178qMzNTBQUF2rRpk8LDw/XQQw8pWI83Ly9Pu3fvVt26dbVhwwZnmbkg1rdvX7kZowhqqFWrVoqIiFBUVJTz+IUXXtB//dd/+btZ8LNjx47p008/rfH65tTXDEVyq9p2vNeLHqwXPvzwQ915553+bgYCSE17dd705gJZbTve60UNFrgBtUm31l5r+/FeLwLWC4MGDVLHjh01dOhQnTp1yt/NgUuusLt15MCV1LbjvR4EbA198cUXzvi8vXv3qlGjRnriiSf83SS4pFcXbL252na814OAraFLnyoxV3NHjx6trVu3+rtJcEGvLlh7c7XteK8VAVsDZgjJuXPnyh+vWLHiuj/ZguBypV5dsPbmatvxXitGEdTATz/9pIcfftj50gvzH+iWW27Ru+++6+9mIcCvsAf7lfTadrzXgnGwgMVxosE+DrS2Ha+3KBFUYN5r8vPz/d0MBEGvzqgNtcjadrzeImArOHjwoOLi4tS7d2+vP3MNGOY02XwLVP369Z2fwV6LrG3H6y1qsBV8/vnnKioqcmqt/EfBtTK9uP79+6u2qG3H6w16sBWYb1A3evXq5e+mAAgCrgnY0tJSpaWlqU2bNs6Xrphxqa+88orPtm9KAqYHa/Ts2dNn2wVQe7mmRGC+vertt9/WnDlz1L17dx0/flzffffddY9vvcRsywzHMnMHmY/DVnwOQGCKuc6pbGxzxTAtM31E48aNNX/+fD355JM+2y51VsDdygI8vlxRIjAToF24cMG5ug8AbuGKEsGlL7n2NVMSuPQueNttt+nMmTP6+OOP1aVLFyv7A1C7uKJEYOZjNx/Be+ONNygRACgX6PHlih6sufCUkpKi8ePHO5OhdevWzfk+VjNJmvluVgAIRK4IWOOll15ypvGdNGmSfvzxRyUmJmr48OHXtc3c3Fzn58CBA53SwMsvv6xx48b5qMUAajtXlAhsj69t0qSJU3/dsWOHunbt6u8mAQgSrhhFYNPXX3/thKsZT/dv//Zv/m4OgCBS6wN2z549zk/z4QUzWwEA+EqtLxEYpqabnZ2tDh06+LspAIIIAQsAltT6EgEA2ELAAoAlBCwAWELAAoAlBCwAWELAAoAlBCwAWELAAoAlBCwAWELAAoAlBCwAWELAAoAlBCwAWELAAoAlBCwAWELAAoAlBCwAWELAAoAlBCwAWELAAoAlBCwAWELAAoAlBCwAWELAAoAlBCwAWELAAoAlBCwAWELAAoAlBCwAWELAAoAlBCwAyI7/B4ex7BcMxtd7AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Create 10-qubit circuit with OpenQASM 3.0\n", + "alg = QasmBuilder(5, version=\"3\") \n", + "register = [*range(5)]\n", + "\n", + "# Import standard gates library\n", + "program = alg.import_library(std_gates)\n", + "\n", + "# Import QFT library\n", + "qft = alg.import_library(QFTLibrary)\n", + "\n", + "# Apply gates\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", + "\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", + "# qft.QFT(register[:5])\n", + "# Measurement\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", + " " + ] + }, + { + "cell_type": "markdown", + "id": "d0c3d818", + "metadata": {}, + "source": [ + "
\n", + "

The QasmBuilder Object

\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "642df612", + "metadata": {}, + "source": [ + "This library provides a flexible framework for generating OpenQASM code through a hierarchical builder pattern. Built on top of the root FileBuilder class which separates text content from structure/semantics requirements unique to each file type.\n", + "\n", + "**Key Features:**\n", + "- Automatic scope and indentation management\n", + "- Library import and gate definition tracking \n", + "- Multiple output formats (QASM circuits, includes, gate definitions)\n", + "- Resource allocation for qubits and classical bits\n", + "- Extensible design for custom quantum libraries\n", + "\n", + "**Class Extensions:** GateBuilder, QasmBuilder, IncludeBuilder\n", + "\n", + "### Typical Usage Patterns:\n", + "\n", + "#### 1. Complete Quantum Circuit\n", + "```python\n", + "builder = QasmBuilder(qubits=5, clbits=5)\n", + "gates = builder.import_library(std_gates)\n", + "gates.h(0)\n", + "gates.cx(0, 1)\n", + "circuit_code = builder.build()\n", + "```\n", + "\n", + "#### 2. Gate Library Development \n", + "```python\n", + "builder = GateBuilder()\n", + "gates = builder.import_library(std_gates)\n", + "# Define custom gates...\n", + "program, imports, definitions = builder.build()\n", + "```\n", + "\n", + "#### 3. Include File Creation\n", + "```python\n", + "builder = IncludeBuilder()\n", + "# Add gate definitions and utilities...\n", + "include_content = builder.build()\n", + "with open(\"custom.inc\", 'w') as f:\n", + " f.write(include_content)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "e582bbb3", + "metadata": {}, + "source": [ + "
\n", + "

The GateLibrary Object

\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "f0c22fc9", + "metadata": {}, + "source": [ + "GateLibrary is a base framework for macroing gate, import, and algorithm generation and is \n", + "built to inject definitions into whatever FileBuilder class it is connected to.\n", + "\n", + "Key (Base) Features: \n", + "- Gate application with controls and phases \n", + "- Measurements and classical bit operations \n", + "- Control flow (loops, conditionals) \n", + "- Gate and subroutine definitions \n", + "- Code generation and scope management \n", + "\n", + "Class Extensions:\n", + "- std_gates\n", + "\n", + "\n", + "### GATELIBRARY OBJECT TYPICAL USAGE PATTERNS:\n", + "\n", + "\n", + "##### standard gate applications:\n", + "The most common way a gatelibary is used to to directly correlate to applying a gate either from a static call
\n", + "to an import library like std_gates, or to a more dynamic gate generator for more bulky components like QFT to
\n", + "the top of the file where only a few gate types are used (import generator is currently a stub but should allow
\n", + "for the sequestering of these components to import files themselves local to the main algorithm)\n", + "\n", + "The following is a demo of one of the more complex dynamic gate calls:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "bd538440", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3.0;\n", + "include \"stdgates.inc\";\n", + "qubit[5] qb;\n", + "bit[5] cb;\n", + "gate QFT4S aa, ab, ac, ad {\n", + " h aa;\n", + " cp(pi / 2) aa, ab;\n", + " cp(pi / 4) aa, ac;\n", + " cp(pi / 8) aa, ad;\n", + " h ab;\n", + " cp(pi / 2) ab, ac;\n", + " cp(pi / 4) ab, ad;\n", + " h ac;\n", + " cp(pi / 2) ac, ad;\n", + " h ad;\n", + " swap ad, aa;\n", + " swap ac, ab;\n", + "}\n", + "QFT4S qb[0], qb[1], qb[2], qb[3];\n", + "\n" + ] + } + ], + "source": [ + "alg = QasmBuilder(5,version=\"3\") \n", + "qft = alg.import_library(QFTLibrary)\n", + "\n", + "\n", + "#call QFT gate\n", + "qft.QFT([*range(4)])\n", + "\n", + "\n", + "\n", + "prog = alg.build()\n", + "res = pq.loads(prog)\n", + "print(res)" + ] + }, + { + "cell_type": "markdown", + "id": "e2761514", + "metadata": {}, + "source": [ + "##### Blind/ Closure applications\n", + "many algorithms provide advantage through being agnostic to the application of another component such as the
\n", + "inner workings of a Hamiltonian. Thus passing the Hamilitonian (or other compenent) to a generator is a workflow
\n", + "currently near completion. This interface uses the hamiltonian as a gatebuilder object itself, and has some
\n", + "stricter requirements for application, first there are three methods which are needed depending on the
\n", + "generator: apply, unapply (inverse), and controlled (apply/unapply). for the most part, this lets generators
\n", + "agnostically work with the component but sometimes generators absolutely must need a gated version of the
\n", + "component to work with (this is expected to be fixed in later revisions). Further work is also expected to
\n", + "bring selective annotation to the output generation and have most generators result in gates rather than sub-
\n", + "routines.\n", + "\n", + "lastly for these closures, there tends to be some customizability to the level of interface with the generators
\n", + "by default, the generators will blindly call apply to the hamiltonian but arguments passed to the generator which
\n", + "allow for further parameters to be prepended to the Hamil call (ie having phase estimation set an evolution time
\n", + "for the Hamiltonian will have it pass a time t as the first argument in apply). You must setup your closure to
\n", + "accept these parameters. \n", + "\n", + "the following is a demo of a grovers search algorithm with a blind application of an index hamiltonian:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "547536c8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENQASM 3;\n", + "include \"stdgates.inc\";\n", + "qubit[5] qb;\n", + "bit[5] cb;\n", + "def Grover5Z_on_two2(qubit[5] reg) {\n", + "\th reg;\n", + "\tfor int i in [0:1] {\n", + "\t\t//Za\n", + "\t\tctrl(3) @ cp reg[0],reg[1],reg[3],reg[4],reg[2];\n", + "\t\th reg;\n", + "\t\t//Z0\n", + "\t\tx reg;\n", + "\t\tctrl(4) @ z reg[0], reg[1], reg[2], reg[3], reg[0];\n", + "\t\tx reg;\n", + "\t\th reg;\n", + "\t}\n", + "}\n", + "\n", + " Grover5Z_on_two2(qb[{0 ,1 ,2 ,3 ,4}]);\n", + "\n" + ] + } + ], + "source": [ + "# Create 10-qubit circuit with OpenQASM 3.0\n", + "alg = QasmBuilder(5, version=\"3\") \n", + "reg = [*range(5)]\n", + "\n", + "# Import standard gates library\n", + "program = alg.import_library(std_gates)\n", + "\n", + "# Import Amplitude amplification library\n", + "ampl = alg.import_library(AALibrary)\n", + "\n", + "class Za(GateLibrary):\n", + " name = \"Z_on_two\"\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", + " sys = self.builder\n", + " std = sys.import_library(std_gates)\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", + "\n", + "ampl.Grover(Za,reg,2)\n", + "\n", + "prog = alg.build()\n", + "print(prog)\n", + "res = pq.loads(prog)" + ] + }, + { + "cell_type": "markdown", + "id": "7e078ba2", + "metadata": {}, + "source": [ + "
\n", + "

Some Hang Ups

\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "8905d8a2", + "metadata": {}, + "source": [ + "### Loop Syntax in Quantum Gate Library\n", + "Loop syntax is quite diverse and thus the macro generator accepts several different parameterizations for generation, along with some extended capture syntax for working with loop variables. The following section explains several different ways a loop can be called.\n", + "\n", + "#### 1. Simple Integer Loops\n", + "The most basic loop form takes a single integer and creates a range from 0 to that number (exclusive).\n", + "\n", + "```python\n", + "gates.begin_loop(5) # Creates: for int i in [0:5]\n", + "gates.h(\"i\") # Apply Hadamard to qubits 0,1,2,3,4\n", + "gates.end_loop()\n", + "```\n", + "\n", + "#### 2. Range Loops with Start and End\n", + "Use a tuple `(start, end)` to specify custom ranges.\n", + "\n", + "```python\n", + "gates.begin_loop((2, 7)) # Creates: for int i in [2:7]\n", + "gates.x(\"i\") # Apply X to qubits 2,3,4,5,6\n", + "gates.end_loop()\n", + "```\n", + "\n", + "#### 3. Stepped Loops\n", + "Use a tuple `(start, step, end)` for custom step sizes and directions.\n", + "\n", + "```python\n", + "gates.begin_loop((0, 2, 8)) # Creates: for int i in [0:8:2]\n", + "gates.z(\"i\") # Apply Z to qubits 0,2,4,6\n", + "gates.end_loop()\n", + "```\n", + "\n", + "#### 4. Floating-Point Loops\n", + "For algorithms requiring continuous parameters, use float ranges with `(start, step_value, count)`.\n", + "\n", + "```python\n", + "gates.begin_loop((0.0, 0.5, 4)) # Creates float range with 4 values\n", + "gates.phase(\"i\", 1) # Use loop variable as phase parameter\n", + "gates.end_loop()\n", + "```\n", + "\n", + "#### 5. Custom Type and Domain Loops\n", + "For maximum flexibility, specify both type and domain explicitly using `(type_string, domain_string)`.\n", + "\n", + "```python\n", + "gates.begin_loop((\"uint\", \"[1:2:8]\")) # Creates: for uint i in [1:2:8]\n", + "gates.sx(\"i\")\n", + "gates.end_loop()\n", + "```\n", + "\n", + "#### 6. Direct OpenQASM Syntax\n", + "For complete control, pass raw OpenQASM loop syntax as a string.\n", + "\n", + "```python\n", + "gates.begin_loop(\"bit b in {0, 1}\") # Custom boolean loop\n", + "gates.x(0) # Operations inside loop\n", + "gates.end_loop()\n", + "```\n", + "\n", + "## Loop Parameter Summary\n", + "\n", + "| Pattern | Syntax | Generated OpenQASM | Use Case |\n", + "|---------|--------|-------------------|----------|\n", + "| Simple | `begin_loop(5)` | `for int i in [0:5]` | Basic iteration |\n", + "| Range | `begin_loop((2,7))` | `for int i in [2:7]` | Custom start/end |\n", + "| Stepped | `begin_loop((0,2,8))` | `for int i in [0:8:2]` | Skip iterations |\n", + "| Float | `begin_loop((0.0,0.5,4))` | `for float i in {...}` | Continuous params |\n", + "| Custom | `begin_loop((\"uint\",\"[1:8]\"))` | `for uint i in [1:8]` | Type control |\n", + "| Direct | `begin_loop(\"custom syntax\")` | `for custom syntax` | Full control |" + ] + }, + { + "cell_type": "markdown", + "id": "b61c000e", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyproject.toml b/pyproject.toml index 9c59af3..ce0a431 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,10 +58,10 @@ multi_line_output = 3 include_trailing_comma = true force_grid_wrap = 0 use_parentheses = true -line_length = 100 +line_length = 120 [tool.pylint.'MESSAGES CONTROL'] -max-line-length = 100 +max-line-length = 120 disable = "W0108,W0511,W0401,R0902,R0903,R0913,E0401" [tool.pylint.MASTER] diff --git a/qbraid_algorithms/__init__.py b/qbraid_algorithms/__init__.py index 7e00090..6c0bcd4 100644 --- a/qbraid_algorithms/__init__.py +++ b/qbraid_algorithms/__init__.py @@ -24,15 +24,20 @@ .. autosummary:: :toctree: ../stubs/ - + bernstein_vazirani qft iqft qpe + qtran + hhl + evolution + embedding + amplitude_amplification + rodeo """ -from . import bernstein_vazirani, iqft, qft, qpe from ._version import __version__ __all__ = [ @@ -41,4 +46,10 @@ "iqft", "bernstein_vazirani", "qpe", + "qtran", + 'evolution', + 'embedding', + 'amplitude_amplification', + 'hhl', + 'rodeo' ] diff --git a/qbraid_algorithms/amplitude_amplification/__init__.py b/qbraid_algorithms/amplitude_amplification/__init__.py new file mode 100644 index 0000000..2d28049 --- /dev/null +++ b/qbraid_algorithms/amplitude_amplification/__init__.py @@ -0,0 +1,32 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 providing Amplitude Amplification implementation. + +Functions +---------- + +.. autosummary:: + :toctree: ../stubs/ + + AALibrary + +""" + +from .amp_ampl import AALibrary + +__all__ = [ + "AALibrary" +] diff --git a/qbraid_algorithms/amplitude_amplification/amp_ampl.py b/qbraid_algorithms/amplitude_amplification/amp_ampl.py new file mode 100644 index 0000000..e42316c --- /dev/null +++ b/qbraid_algorithms/amplitude_amplification/amp_ampl.py @@ -0,0 +1,269 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +''' +from typing import List + +from qbraid_algorithms.qtran import GateBuilder, GateLibrary, std_gates + +# TODO: once again Physics notation was originally used convert to better naming +# pylint: disable=invalid-name +# mypy: disable_error_code="call-arg" + +class AALibrary(GateLibrary): + """ + Amplitude Amplification Library implementing 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 + + Both algorithms use the principle of selective phase rotation to amplify desired + quantum state amplitudes while suppressing unwanted ones. + """ + + name = "AmplitudeAmplification" + + def __init__(self, *args, **kwargs): + """Initialize the AALibrary by calling the parent GateLibrary constructor.""" + super().__init__(*args, **kwargs) + self.name = "AmplAmp" + + def grover(self, H, qubits: List[int], depth: int) -> None: + """ + Implement Grover's algorithm for quantum search. + + Grover's algorithm provides a quadratic speedup for searching unsorted databases. + It uses amplitude amplification with a specific oracle (H) to amplify the amplitude + of target states while suppressing others. + + 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) + + Args: + H: Oracle/Hamiltonian that marks target states + qubits: List of qubit indices to operate on + depth: Number of Grover iterations to perform + """ + # Generate unique subroutine name based on parameters + name = f'Grover{len(qubits)}{H.name}{depth}' + + # Check if subroutine already exists to avoid regeneration + if name in self.gate_ref: + qubit_list = "{" + " ,".join(str(i) for i in qubits) + "}" + self.call_subroutine(name, [self.call_space.format(qubit_list)]) + # Alternative gate-based call (currently commented out): + # self.call_gate(name, qubits[-1], qubits[:-1]) + return + + # Create new gate builder for defining the subroutine + gate_system = GateBuilder() + std_library = gate_system.import_library(std_gates) + std_library.call_space = " {}" + oracle_library = gate_system.import_library(H) + + # NOTE: Alternative gate-based implementation is commented out below. + # The current subroutine approach keeps generated code compact, + # whereas gates cannot use loops (would require Python loops instead). + + # Alternative gate implementation (commented out): + # std_library.begin_gate(name, qargs) + # # Initial superposition + # [std_library.h(i) for i in qargs] + # # Grover iteration: Za -> Z0 + # std_library.begin_loop(depth) + # std_library.comment("Za") + # oracle_library.apply(qargs) + # [std_library.h(i) for i in qargs] + # std_library.comment("Z0") + # [std_library.x(i) for i in qargs] + # std_library.controlled_op("z", (qargs[-1], qargs[:-1]), n=len(qubits)-1) + # [std_library.x(i) for i in qargs] + # [std_library.h(i) for i in qargs] + # std_library.end_loop() + # std_library.end_gate() + + # Current subroutine-based implementation + register = "reg" + std_library.begin_subroutine(name, [f"qubit[{len(qubits)}] {register}"]) + + # Initialize all qubits in superposition + std_library.h(register) + + # Main Grover iteration loop + std_library.begin_loop(depth) + + # Apply oracle (marks target states with phase flip) + std_library.comment("Za") + oracle_library.apply([f"reg[{i}]" for i in range(len(qubits))]) + + # Apply diffusion operator (inverts amplitudes about average) + std_library.h(register) + 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.x(register) # Flip back + std_library.h(register) + + std_library.end_loop() + std_library.end_subroutine() + + # Build and merge the subroutine into main library + self.merge(*gate_system.build(), name) + + # Call the created subroutine + qubit_list = "{" + " ,".join(str(i) for i in qubits) + "}" + self.call_subroutine(name, [self.call_space.format(qubit_list)]) + + def amp_ampl(self, Z, H, qubits: List[int], depth: int) -> None: + """ + Implement general amplitude amplification algorithm. + + This is a generalization of Grover's algorithm that works with arbitrary + oracles Z and state preparation operators H. It amplifies amplitudes of + states marked by oracle Z after preparation by operator H. + + The algorithm structure: + 1. Unapply state preparation Z† + 2. Initialize superposition + 3. Repeat depth times: + - Apply state preparation H + - Unapply oracle Z† + - Apply diffusion operator Z0 + - Apply oracle Z + + Args: + Z: Oracle operator that marks target states + H: State preparation operator + qubits: List of qubit indices to operate on + depth: Number of amplitude amplification iterations + + Note: + 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}' + + # Check if subroutine already exists + if name in self.gate_ref: + qubit_list = "{" + " ,".join(str(i) for i in qubits) + "}" + self.call_subroutine(name, [self.call_space.format(qubit_list)]) + # Alternative gate-based call (currently commented out): + # self.call_gate(name, qubits[-1], qubits[:-1]) + return + + # Create new gate builder for defining the subroutine + gate_system = GateBuilder() + std_library = gate_system.import_library(std_gates) + oracle_z = gate_system.import_library(Z) + state_prep_h = gate_system.import_library(H) + + # NOTE: Alternative gate-based implementations are commented out below. + # Multiple different approaches were tried during development. + + # Alternative gate implementation attempt 1 (commented out): + # std_library.begin_gate(name, qargs) + # std_library.call_space = " {} " + # # Initial superposition + # [std_library.h(i) for i in qargs] + # # Amplitude amplification iteration + # std_library.begin_loop(depth) + # std_library.comment("Za") + # state_prep_h.apply(qargs) + # [std_library.h(i) for i in qargs] + # std_library.comment("Z0") + # [std_library.x(i) for i in qargs] + # std_library.controlled_op("cz", (qargs[-1], qargs[:-1]), n=len(qubits)-2) + # [std_library.x(i) for i in qargs] + # [std_library.h(i) for i in qargs] + # std_library.end_loop() + # std_library.end_gate() + + # Alternative gate implementation attempt 2 (commented out): + # for _ in range(depth): + # std_library.comment("Za") + # oracle_z.apply(qargs) + # [std_library.h(i) for i in qargs] + # std_library.comment("Z0") + # print((qargs[-1], qargs[:-1])) # Debug print + # std_library.controlled_op("cp", (qargs[-1], qargs[:-1]), n=len(qubits)-2) + # [std_library.h(i) for i in qargs] + + # Current subroutine-based implementation + register = "reg" + std_library.begin_subroutine(name, [f"qubit[{len(qubits)}] {register}"]) + + # Initial unapplication of oracle (inverse preparation) + oracle_z.unapply([f"reg[{i}]" for i in range(len(qubits))]) + + # Initialize superposition + std_library.h(register) + + # Main amplitude amplification loop + std_library.begin_loop(depth) + + # Apply state preparation operator + std_library.comment("H") + state_prep_h.apply([f"reg[{i}]" for i in range(len(qubits))]) + + # Unapply oracle (Z†) + std_library.comment("Zp*") + oracle_z.unapply([f"reg[{i}]" for i in range(len(qubits))]) + + # Apply diffusion operator (same as Grover) + std_library.comment("Z0") + std_library.x(register) + 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) + + # Reapply oracle (Z) + std_library.comment("Zp") + oracle_z.apply([f"reg[{i}]" for i in range(len(qubits))]) + + std_library.end_loop() + std_library.end_subroutine() + + # Build and merge the subroutine + self.merge(*gate_system.build(), name) + + # Call the created subroutine + # Alternative gate-based call (commented out): + # self.call_gate(name, qubits[-1], qubits[:-1]) + qubit_list = "{" + " ,".join(str(i) for i in qubits) + "}" + self.call_subroutine(name, [self.call_space.format(qubit_list)]) diff --git a/qbraid_algorithms/bells_inequality/__init__.py b/qbraid_algorithms/bells_inequality/__init__.py new file mode 100644 index 0000000..4508629 --- /dev/null +++ b/qbraid_algorithms/bells_inequality/__init__.py @@ -0,0 +1,32 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 providing Bell's Inequality experiment implementation. + +Functions +---------- + +.. autosummary:: + :toctree: ../stubs/ + + load_program + +""" + +from .bells_inequality import load_program + +__all__ = [ + "load_program", +] diff --git a/qbraid_algorithms/bells_inequality/bells_inequality.py b/qbraid_algorithms/bells_inequality/bells_inequality.py new file mode 100644 index 0000000..6df5370 --- /dev/null +++ b/qbraid_algorithms/bells_inequality/bells_inequality.py @@ -0,0 +1,35 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +""" +Bell's Inequality Experiment Implementation + +Simple functions for loading and running Bell's inequality circuits. +""" + +from pathlib import Path + +import pyqasm +from pyqasm.modules.base import QasmModule + + +def load_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" + return pyqasm.load(str(qasm_path)) diff --git a/qbraid_algorithms/bells_inequality/bells_inequality.qasm b/qbraid_algorithms/bells_inequality/bells_inequality.qasm new file mode 100644 index 0000000..59a54a0 --- /dev/null +++ b/qbraid_algorithms/bells_inequality/bells_inequality.qasm @@ -0,0 +1,59 @@ +// Bell's Inequality Circuit based on Amazon Braket Experimental Library +OPENQASM 3.0; +include "stdgates.inc"; + +/* +Create bell inequality circuits +*/ +// Create 3 2-qubit registries (we need 3 total circuits) +qubit[2] q0; +qubit[2] q1; +qubit[2] q2; + + +// Create 3 2-bit classical registries for measurment +bit[2] c0; +bit[2] c1; +bit[2] c2; + +// Initialize all qubits to 0 +reset q0; +reset q1; +reset q2; + +/* +Prepare bell singlet states between each of the qubit pairs +*/ +x q0[0]; +x q0[1]; +h q0[0]; +cx q0[0], q0[1]; + +x q1[0]; +x q1[1]; +h q1[0]; +cx q1[0], q1[1]; + +x q2[0]; +x q2[1]; +h q2[0]; +cx q2[0], q2[1]; + +/* +Apply the rotations (angle A = 0 = no rotation) +*/ + +// Circuit AB +rx(pi / 3) q0[1]; + +// Circuit AC +rx(2 * pi / 3) q1[1]; + +// Circuit BC +rx(pi / 3) q2[0]; +rx(2 * pi / 3) q2[1]; + +// Perform measurements for each of the three circuits +c0 = measure q0; +c1 = measure q1; +c2 = measure q2; \ No newline at end of file diff --git a/qbraid_algorithms/embedding/__init__.py b/qbraid_algorithms/embedding/__init__.py new file mode 100644 index 0000000..a66ed60 --- /dev/null +++ b/qbraid_algorithms/embedding/__init__.py @@ -0,0 +1,35 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 providing Several different implementations of block encoding + +Functions +---------- + +.. 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'] diff --git a/qbraid_algorithms/embedding/prep_sel.py b/qbraid_algorithms/embedding/prep_sel.py new file mode 100644 index 0000000..3284b71 --- /dev/null +++ b/qbraid_algorithms/embedding/prep_sel.py @@ -0,0 +1,442 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +""" +Quantum Gate Library for Preparation and Selection Operations + +This module implements quantum gates for state preparation, operator selection, +and Pauli string decomposition using quantum compilation techniques. +""" +# 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 +# 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" +import itertools +import string + +import numpy as np +from scipy.optimize import minimize + +from qbraid_algorithms.qtran import GateBuilder, GateLibrary, std_gates + + +class PrepSelLibrary(GateLibrary): + """Library for combined preparation and selection quantum operations.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def prep_select(self, qubits, matrix, approximate=0): + """ + Create a preparation-selection gate for a given matrix/operator chain. + + Args: + qubits: Target qubits for the operation + matrix: Either a matrix to decompose or pre-computed operator chain + approximate: Approximation threshold for Pauli decomposition + + Returns: + Gate name and operation counts (if new gate created) + """ + # print(matrix) + # Handle both matrix and pre-computed operator chain inputs + if isinstance(matrix[0],tuple) : + op_chain = matrix + gate_id = abs(hash(tuple(matrix))) # BUG FIX: Use tuple for abs(hashable + else: + op_chain = self.gen_pauli_string(matrix, approximate) + 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) + name = f"PS_{len(qubits)}_{gate_id}" + print(op_chain) + # Claim quantum resources + anc_q = self.builder.claim_qubits(qb) + anc_c = self.builder.claim_clbits(qb) + + # Use existing gate if available + if name in self.gate_ref: + self.call_gate(name, qubits[-1], anc_q + qubits[:-1]) + self.measure(anc_q, anc_c) + return name + + # Build new gate + sys = GateBuilder() + std = sys.import_library(std_gates) + prep = sys.import_library(Prep) + prep.call_space = "{}" + sel = sys.import_library(Select) + sel.call_space = "{}" + + # 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)] + + 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 + self.merge(*sys.build(), name) + self.call_gate(name, qubits[-1], anc_q + qubits[:-1]) + self.measure(anc_q, anc_c) + + return name, nprep, nsel + + @staticmethod + def gen_pauli_string(matrix, epsilon): + """ + Decompose a square matrix into tensor products of Pauli matrices. + + Args: + matrix (np.ndarray): A 2^n x 2^n complex matrix. + epsilon (float): Threshold parameter for filtering. + + Returns: + List[Tuple[str, float]]: Sorted list of (Pauli string, coefficient). + """ + # Define Pauli matrices + paulis = { + "I": np.array([[1, 0], [0, 1]], dtype=complex), + "X": np.array([[0, 1], [1, 0]], dtype=complex), + "Y": np.array([[0, -1j], [1j, 0]], dtype=complex), + "Z": np.array([[1, 0], [0, -1]], dtype=complex), + } + + # Check size + dim = matrix.shape[0] + n = int(np.log2(dim)) + if 2**n != dim: + raise ValueError("Matrix size must be a power of 2.") + + # Generate all Pauli tensor products + pauli_labels = list(paulis.keys()) + basis = list(itertools.product(pauli_labels, repeat=n)) + + result = [] + threshold = epsilon / np.log2(dim) + + for label_tuple in basis: + # Build the tensor product matrix + op = paulis[label_tuple[0]] + for index in label_tuple[1:]: + op = np.kron(op, paulis[index]) + + # Compute coefficient: Tr(P^† M) / 2^n + coef = np.trace(op.conj().T @ matrix) / (2**n) + + if abs(coef) > threshold: + pauli_str = "".join(label_tuple) + result.append((pauli_str, coef)) + + # Sort by absolute value of coefficient (descending) + result.sort(key=lambda x: abs(x[1]), reverse=True) + return result + + +class Prep(GateLibrary): + """Quantum state preparation library using amplitude encoding.""" + + def prep(self, qubits, dist): + """ + Prepare a quantum state with given amplitude distribution. + + Args: + qubits: Target qubits for state preparation + dist: Probability/amplitude distribution + + Returns: + Gate name and state mapping + """ + # 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 + 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) + + # 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)] + + std.begin_gate(name, qargs) + + # Apply rotation gates in structured pattern + angle_idx = 0 + + # Initial Y-rotations + for i in range(qb): + std.ry(angles[angle_idx], qargs[i]) + angle_idx += 1 + + # Controlled Y-rotations in three layers + for layer in range(2): + # 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]) + 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]) + angle_idx += 1 + + std.end_gate() + + self.merge(*sys.build(), name) + self.call_gate(name, qubits[-1],qubits[:-1]) # BUG FIX: Simplified call + return name, mapping + + def gen_prep_angles(self, dist): + """ + Generate rotation angles for state preparation via optimization. + + Args: + dist: Target probability distribution + + Returns: + 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) + # 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 = ref_dist / np.linalg.norm(ref_dist) + sorted_dist = np.sort(ref_dist) + sort_indices = np.argsort(ref_dist) + + def render_state(params): + """Simulate quantum circuit with given parameters.""" + # Initial Y-rotations + sy = y_rot(params[0]) + param_idx = 1 + + for _ in range(1, qb): + if param_idx < len(params): + sy = np.kron(y_rot(params[param_idx]), sy) + param_idx += 1 + fit = sy + if qb > 1: + # 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) + param_idx += 1 + + for _ in range(1, qb//2): + if param_idx < len(params): + dy = np.kron(cy_rot(params[param_idx]), dy) + param_idx += 1 + + if qb % 2 == 1: + dy = np.kron(np.eye(2), dy) + + # Upper controlled gates + uy = np.eye(2) + for _ in range((qb-1)//2): + if param_idx < len(params): + uy = np.kron(cy_rot(params[param_idx]), uy) + param_idx += 1 + + if qb % 2 == 0: + uy = np.kron(np.eye(2), uy) + # print(uy.shape,dy.shape,fit.shape) + fit = uy @ dy @ fit + + return fit[:, 0] + + def cost_function(params): + """Optimization cost: 1 - fidelity with target distribution.""" + simulated = render_state(params) + sorted_sim = np.sort(simulated) + 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) + # print(result) + + + # Create mapping from original to sorted indices + final_state = render_state(result.x) + mapping = dict(zip(sort_indices, np.argsort(final_state))) + # print(ref_dist) + # print(final_state) + # print([final_state[mapping[i]] for i in range(len(dist))]) + return result.x, mapping + + +class Select(GateLibrary): + """Quantum operator selection library for controlled operations.""" + + def select(self, qubits, anc, operators, mapping): + """ + Apply selected operators based on ancilla qubit states. + + Args: + qubits: Target qubits for operations + anc: Ancilla qubits encoding selection + operators: List of operators to select from + mapping: Index mapping for operator selection + + Returns: + Gate name + """ + gate_id = abs(hash((tuple(operators), tuple(mapping.items())))) + 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 + 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))] + + sys = GateBuilder() + std = sys.import_library(std_gates) + std.call_space = "{}" + pauli_lib = sys.import_library(PauliOperator) + pauli_lib.call_space = "{}" + + # Invert mapping for lookup + pinv = {v: k for k, v in mapping.items()} + + std.begin_gate(name, qargs) + + prev_gray = None + for i in range(len(operators)): + # Gray code for efficient state transitions + gray_code = i ^ (i >> 1) + + if prev_gray is not None: + # Flip qubits that changed in Gray code + diff = gray_code ^ prev_gray + bit_pos = (diff & -diff).bit_length() - 1 # Find rightmost set bit + if bit_pos < len(anc): + std.x(qargs[bit_pos]) + + # Apply selected operator + mapped_idx = pinv.get(i, i) # BUG FIX: Handle missing mappings + if mapped_idx < len(operators): + op = operators[mapped_idx] + + if isinstance(op, str): + # Pauli string operator + 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)]) + + prev_gray = gray_code + for j in range(len(anc)): + 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 + return name + + +class PauliOperator(GateLibrary): + """Library for Pauli string operations.""" + + def pauli_operator(self, qubits, op): + """ + Apply a Pauli string operator to qubits. + + Args: + qubits: Target qubits + op: Pauli string (e.g., "XYZI") + + Returns: + Gate name or None if invalid + """ + if not isinstance(op, str): + # Not a Pauli string - skip + return None + + # Validate Pauli string + 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]) + 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)}") + 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) + + sys = GateBuilder() + std = sys.import_library(std_gates) + std.begin_gate(op, qargs) + std.call_space = "{}" + + # Apply Pauli gates + for i, gate in enumerate(op): + match gate: + case 'I': + pass # Identity - no operation + case 'X': + std.x(qargs[i]) # BUG FIX: Use qargs instead of index + case 'Y': + std.y(qargs[i]) + case 'Z': + std.z(qargs[i]) + case _: + print(f"Unknown Pauli gate: {gate}") + + std.end_gate() + + self.merge(*sys.build(), op) + self.call_gate(op, qubits[-1],qubits[:-1]) + return op diff --git a/qbraid_algorithms/embedding/toeplitz.py b/qbraid_algorithms/embedding/toeplitz.py new file mode 100644 index 0000000..55617df --- /dev/null +++ b/qbraid_algorithms/embedding/toeplitz.py @@ -0,0 +1,277 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +""" +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 +QASM subroutines have been fully debugged in PyQASM. + +This module provides: +- Toeplitz: real Toeplitz matrix embedding via circulant diagonalization. +- Diagonal: diagonal scaling and phase projection methods. + +Dependencies: + numpy, scipy, qbraid_algorithms (QFTLibrary, GateBuilder, GateLibrary, std_gates) + +author: Rhys Takahashi +""" +# pylint: disable=too-many-locals +# mypy: disable_error_code="import-untyped" +import string +from itertools import combinations + +import numpy as np +import scipy as scp + +from qbraid_algorithms.qft import QFTLibrary +from qbraid_algorithms.qtran import GateBuilder, GateLibrary, std_gates + + +class Toeplitz(GateLibrary): + """Gate library for real Toeplitz embeddings via circulant diagonalization.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def real_toeplitz(self, qubits, vals, ancilla=True): + """ + Build a real Toeplitz operator using circulant diagonalization. + + Args: + qubits (list): Target qubits for operation. + vals (array-like): Vector defining Toeplitz structure. + ancilla (bool): Whether to allocate ancilla qubits/clbits. + + Returns: + str: Gate name. + """ + qb = int(np.log2(len(vals)) + 0.01 + (1 if ancilla else 0)) + name = f"r_top_{qb}_{abs(hash(tuple(vals)))}" + + # Claim ancilla qubits/clbits + anc_q = self.builder.claim_qubits(2 if ancilla else 1) + anc_c = self.builder.claim_clbits(2 if ancilla else 1) + + # If already defined, just call it + if name in self.gate_ref: + self.call_gate(name, qubits[-1], anc_q + qubits[:-1]) + self.measure(anc_q, anc_c) + return name + + # Construct circulant embedding + if ancilla: + if len(np.array(vals).shape) > 1: + line = np.concatenate((vals[0], [0], np.conj(np.flip(vals[0])))) + else: + line = np.concatenate((vals, [0], np.flip(vals))) + circ_mat = scp.linalg.circulant(line[:-1]) + else: + if len(np.array(vals).shape) > 1: + circ_mat = vals + else: + line = np.concatenate((vals, [0], np.flip(vals))) + circ_mat = scp.linalg.circulant(line[:-1]) + circ_mat = circ_mat[:len(vals), :len(vals)] + + # Diagonalize via FFT + dft = np.fft.fft(np.eye(2 * len(vals))) + idft = np.fft.ifft(np.eye(2 * len(vals))) + diag = dft @ circ_mat @ idft + diag_vals = np.diag(diag) + + # Argument names + names = string.ascii_letters + qargs = [ + names[i // len(names)] + names[i % len(names)] + for i in range(qb + (2 if ancilla else 1)) + ] + + # Build subcircuit + sys = GateBuilder() + std = sys.import_library(std_gates) + diagonal = sys.import_library(Diagonal) + qft = sys.import_library(QFTLibrary) + + 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))) + qft.QFT(qargs[1:]) + std.end_gate() + + self.merge(*sys.build(), name) + # Finalize + self.call_gate(name, qubits[-1], anc_q + qubits[:-1]) + self.measure(anc_q, anc_c) + return name + + +class Diagonal(GateLibrary): + """Gate library for diagonal scaling and phase projection.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def diag_scale(self, qubits, vals, anc=None): + """ + Apply diagonal scaling with optional ancilla qubits. + + Args: + qubits (list): Target qubits. + vals (array-like): Scaling values. + anc (tuple or None): Pre-allocated (anc_qubits, anc_clbits). + + Returns: + str: Gate name. + """ + qb = int(np.log2(len(vals)) + 0.01) + name = f"diag{qb}_s_{abs(hash(tuple(vals)))}" + + # Claim ancilla if none provided + if anc is None: + anc_q = self.builder.claim_qubits(1) + anc_c = self.builder.claim_clbits(1) + else: + anc_q, anc_c = anc + + # If already defined + if name in self.gate_ref: + self.call_gate(name, qubits[-1], anc_q + qubits[:-1]) + if anc is None: + self.measure(anc_q, anc_c) + return name + + # Generate argument names + names = string.ascii_letters + qargs = [ + names[i // len(names)] + names[i % len(names)] + for i in range(len(qubits) + 1) + ] + + # Normalize values + norm = np.max(np.abs(vals)) + diag = vals / norm + + # Step 1: Approximate amplitudes using arccos trick + ddiag = 2 * np.arccos(np.abs(diag)) + + # Step 2: Correct residual phase + phasor = np.angle(diag) + phase_corr = phasor - ddiag / 2 + + # Build subcircuit + sys = GateBuilder() + std = sys.import_library(std_gates) + diagonal = sys.import_library(Diagonal) + + std.begin_gate(name, qargs) + std.h(qargs[0]) + diagonal.controlled_op(diagonal.diag, (qargs, ddiag), n=1) + std.h(qargs[0]) + diagonal.diag(qargs[1:], phase_corr) + std.end_gate() + + self.merge(*sys.build(), name) + self.call_gate(name, qubits[-1], anc_q + qubits[:-1]) + if anc is None: + self.measure(anc_q, anc_c) + return name + + def diag(self, qubits, vals, depth=3): + """ + Build a diagonal gate with phase decomposition. + + Args: + qubits (list): Target qubits. + vals (array-like): Diagonal values. + depth (int): Phase projector expansion depth. + + Returns: + str: Gate name. + """ + print("building diagonal gate:",qubits, vals, depth) + qb = int(np.log2(len(vals)) + 0.01) + name = f"diag{qb}_{np.abs(hash(tuple(vals)))}" + + if name in self.gate_ref: + self.call_gate(name, qubits[-1], qubits[:-1]) + return name + + # Argument names + names = string.ascii_letters + qargs = [ + names[i // len(names)] + names[i % len(names)] + for i in range(qb) + ] + + # Build subcircuit + sys = GateBuilder() + std = sys.import_library(std_gates) + projection = self.phase_projector(vals, depth) + + std.begin_gate(name, qargs) + std.x(qargs[0]) + std.call_gate("p",qargs[0],phases=projection[0] ) + std.x(qargs[0]) + + # Apply projections + pindex = 1 + for i in range(depth): + for c in [list(combo) for combo in combinations(range(qb), i + 1)]: + if np.abs(projection[pindex]) < 0.1: + pindex += 1 + continue + if len(c) == 1: + 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]), + n=len(c) - 1, + ) + pindex += 1 + + std.end_gate() + self.merge(*sys.build(), name) + self.call_gate(name, qubits[-1], qubits[:-1]) + return name + + def phase_projector(self,target, depth): + """ + Construct a phase projector decomposition. + + Args: + target (array-like): Target diagonal. + depth (int): Expansion depth. + + Returns: + np.ndarray: Projection coefficients. + """ + qb = int(np.log2(len(target)) + 0.01) + basis = np.arange(2**qb) + space = [] + + for i in range(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) + + if i == 0 and c == [0]: + space.append(np.logical_xor(r, np.ones(2**qb))) + space.append(r) + + sysmat = np.linalg.pinv(np.array(space).T) + return sysmat @ target diff --git a/qbraid_algorithms/evolution/__init__.py b/qbraid_algorithms/evolution/__init__.py new file mode 100644 index 0000000..0887734 --- /dev/null +++ b/qbraid_algorithms/evolution/__init__.py @@ -0,0 +1,43 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 providing Several different implementations of hamiltonian evolution + +Functions +---------- + +.. autosummary:: + :toctree: ../stubs/ + + GQSP + Trotter + TransverseFieldIsing + HeisenbergXYZ + FermionicHubbard + +""" +from .gqsp import GQSP +from .h_test_suite import ( + FermionicHubbard, + HeisenbergXYZ, + RandomizedHamiltonian, + TransverseFieldIsing, + create_test_hamiltonians, +) +from .trotter import Trotter + +__all__ = ['Trotter','GQSP','TransverseFieldIsing', + 'HeisenbergXYZ', 'FermionicHubbard', + 'RandomizedHamiltonian','create_test_hamiltonians'] diff --git a/qbraid_algorithms/evolution/gqsp.py b/qbraid_algorithms/evolution/gqsp.py new file mode 100644 index 0000000..99a9eee --- /dev/null +++ b/qbraid_algorithms/evolution/gqsp.py @@ -0,0 +1,274 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +""" +Generalized Quantum Signal Processing (GQSP) Module + +Implements quantum signal processing techniques for polynomial approximation +of Hamiltonian functions using controlled rotation gates. + +Reference: arXiv:2105.02859 - "Generalized Quantum Signal Processing" + +Current implementation uses simplified generation scheme: +{rY(θ_2n) * rZ(θ_2n-1) * (|1⟩⟨1| ⊗ H + |0⟩⟨0| ⊗ I)}^n * rY(θ_0) + +This generates arbitrary positive polynomials of H under normalization. +Works well for low-degree polynomials (degree < 5). +""" +# pylint: disable=invalid-name,broad-exception-caught +# mypy: disable_error_code="call-arg" +# mypy: disable_error_code="import-untyped" + +import string + +import numpy as np +import scipy as scp +import sympy as sp +from scipy.optimize import minimize + +from qbraid_algorithms.qtran import GateBuilder, GateLibrary, std_gates + + +class GQSP(GateLibrary): + """ + Generalized Quantum Signal Processing gate library. + + Implements GQSP circuits for approximating polynomial functions + of Hamiltonians using quantum phase processing techniques. + """ + + # Class-level symbolic matrix for GQSP operations + U = sp.Matrix([[sp.Symbol("id"), 0], [0, sp.Symbol('H')]]) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def GQSP(self, qubits, phases, hamiltonian, depth=3): + """ + Apply Generalized Quantum Signal Processing circuit. + + Args: + qubits: Target qubits for the operation + phases: Phase parameters for the GQSP sequence + hamiltonian: Hamiltonian gate library to apply + depth: Circuit depth (number of GQSP layers) + + Returns: + Gate name + """ + name = f'GQSP_{depth}_{hamiltonian.name}' + + # Claim ancilla resources + anc_q = self.builder.claim_qubits(1) + anc_c = self.builder.claim_clbits(1) + + # Use existing gate if available + if name in self.gate_ref: + 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 + + # Build new GQSP gate + sys = GateBuilder() + std = sys.import_library(std_gates) + ham = sys.import_library(hamiltonian) + + # 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)] + angles = [f"θ{names[i]}" for i in range(depth * 2 + 1)] + + std.begin_gate(name, qargs, params=angles) + + # Initial Y-rotation on ancilla + std.ry(angles[0], qargs[0]) + + # GQSP sequence + for i in range(depth): + # Controlled Hamiltonian application + ham.controlled(qargs[1:], qargs[0]) + + # Phase gate (assuming 'p' is a phase gate) + std.call_gate("p", qargs[0], phases=angles[i + 1]) + + # Y-rotation + std.ry(angles[depth + i + 1], qargs[0]) + + std.end_gate() # BUG FIX: Add missing end_gate call + + # Register and apply gate + self.merge(*sys.build(), name) + self.call_gate(name,qubits[-1],anc_q+qubits[:-1],phases=phases) + self.measure(anc_q, anc_c) + return name + + @staticmethod + def GQSP_recurse(mat, depth): + """ + Recursively construct symbolic GQSP matrix expression. + + Args: + mat: Input symbolic matrix + depth: Recursion depth + + Returns: + 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)]]) + + # Base case: just apply rotation + if depth <= 0: + return qr * mat + + # Phase rotation matrix + p = sp.Symbol(f'p{depth}') + rp = sp.Matrix([[1, 0], [0, sp.exp(1j * p)]]) + + # Recursive GQSP construction + return qr * rp * GQSP.U * GQSP.GQSP_recurse(mat, depth - 1) + + @staticmethod + def gen_cost(depth, t=1): + """ + Generate cost function for GQSP parameter optimization. + + Args: + depth: Circuit depth + t: Time parameter for target function + + Returns: + Cost function and parameter names + """ + # Get symbolic expression for GQSP circuit + initial_state = sp.Matrix([[1], [0]]) # BUG FIX: Proper column vector + expr = GQSP.GQSP_recurse(initial_state, depth)[0] # Take first component + + # Evaluation points + 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 + + # Extract and sort symbolic variables + syms = expr.free_symbols + names = sorted([(str(a), a) for a in syms]) + srefs = [name[1] for name in names] + + # Substitute identity symbol + expr = expr.subs({srefs[1]: 1}) # substitute 'id' for 1 + + # Target reference function + ref = np.polyval(poly, time * t) + + def cost(x): + """ + Cost function for parameter optimization. + + Args: + x: Parameter values to evaluate + + Returns: + Mean squared error between target and approximation + """ + # BUG FIX: Handle case where not enough parameters provided + param_dict = {} + for i, sym in enumerate(srefs[2:]): # Skip 'H' and 'id' symbols + if i < len(x): + param_dict[sym] = x[i] + + resolved = expr.subs(param_dict) + + # Create numerical evaluator + evaluator = sp.lambdify(srefs[0], resolved, "numpy") # srefs[0] should be 'H' + + try: + series = evaluator(time) + + # Normalize by first element if non-zero + if np.abs(series[0]) > 1e-12: + series = series / np.abs(series[0]) + + # Compute mean squared error + diff = np.sum(np.abs(series - ref)**2) + return float(diff) # BUG FIX: Ensure scalar return + + except (ValueError, TypeError, ZeroDivisionError): + # Return large penalty for invalid parameter values + return 1e6 + + return cost, names + + @staticmethod + def find_gqsp_spectrum( depth): + """ + Find optimal GQSP parameters across a spectrum of time values. + + Args: + depth: Circuit depth for optimization + + Returns: + List of optimal parameters and corresponding time points + """ + # Initialize parameter guess + x_init = np.ones(2 * depth + 1) + x_init[0] = 0 # Initial angle often zero + x_prev = x_init.copy() + + fits = [] + time = np.linspace(-1, 1, 100) + + print(f"Optimizing GQSP parameters for depth {depth}") + + for i, t in enumerate(time): + if abs(t) < 1e-12: # Handle t = 0 case + fits.append(x_init) + continue + + try: + # Get cost function for current time + 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}) + + if result.success: + fits.append(result.x) + + # Update initial guess with momentum + if i > 0 and t != -1: + diff = result.x - x_prev + x_prev = result.x + 0.25 * diff # Momentum factor + else: + x_prev = result.x + if t == -1: + print("Reset parameter tracking at t = -1") + + else: + # Optimization failed, use previous result + print(f"Optimization failed at t = {t:.3f}") + fits.append(x_prev) + + except Exception as e: + print(f"Error at t = {t:.3f}: {e}") + fits.append(x_prev) + + print(f"GQSP optimization complete. Final cost: {result.fun:.6f}") + return fits, time diff --git a/qbraid_algorithms/evolution/h_test_suite.py b/qbraid_algorithms/evolution/h_test_suite.py new file mode 100644 index 0000000..867a01d --- /dev/null +++ b/qbraid_algorithms/evolution/h_test_suite.py @@ -0,0 +1,386 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +""" +Enhanced Hamiltonian Test Definitions + +These Hamiltonian classes provide non-trivial, non-commuting quantum operations +suitable for testing GQSP, Trotter decomposition, and other quantum algorithms. +They primarily implement the expected interfaces for arbitrary circuit application: +data member: name +apply +controlled + +Each Hamiltonian implements: +- Complex multi-qubit interactions +- Non-commuting rotations (RX, RY, RZ) +- Controlled operations with different targets +- Proper parameterization for time evolution + +Designed for semantic testing (compilation) and integration testing (correctness). +#####WARNING##### +These are not true embeddings of their namesake, they are ancilla free representations +for product formula use and semi namesake testing of bare ancilla. True, blind ancilla +collecting versions will be added at a later date +""" + + +import random + +# pylint: disable=invalid-name,keyword-arg-before-vararg,too-many-locals,useless-parent-delegation +# name error disabled in pylint due to aligning of variable names to physics conventions partial shift over +# to subscript standard but incomplete in transfer +import string + +from qbraid_algorithms.qtran import GateBuilder, GateLibrary, std_gates + + +class TransverseFieldIsing(GateLibrary): + """ + Transverse Field Ising Model Hamiltonian: H = -J∑ZZ + h∑X + + Combines nearest-neighbor ZZ interactions with transverse X fields. + This creates strong non-commutativity between different terms. + 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): + super().__init__(*args, **kwargs) + self.reg_size = reg + self.j = j # Coupling strength + self.h = h # Transverse field strength + self.name = f"TFIM_{self.reg_size}q_j{int(j*100)}_h{int(h*100)}" + + # 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)] + + sys = GateBuilder() + std = sys.import_library(std_gates) + std.call_space = " {}" + + std.begin_gate(self.name, qargs, params=["time"]) + + # ZZ interactions between nearest neighbors + for i in range(self.reg_size - 1): + # Implement exp(-i * j * ZZ * time) using CNOT + RZ + CNOT + std.cnot(qargs[i], qargs[i + 1]) + std.rz(f"{2 * j} * time", qargs[i + 1]) + std.cnot(qargs[i], qargs[i + 1]) + + # Add periodic boundary condition for closed chain + if self.reg_size > 2: + std.cnot(qargs[-1], qargs[0]) + std.rz(f"{2 * j} * time", qargs[0]) + std.cnot(qargs[-1], qargs[0]) + + # Transverse field X rotations + for i in range(self.reg_size): + std.rx(f"{2 * h} * time", qargs[i]) + + std.end_gate() + # self.call_space = " {}" + + # Register the gate + 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]) + + def controlled(self, time, qubits, control): + """Apply controlled TFIM evolution.""" + self.controlled_op(self.name, (qubits[-1],[control]+qubits[:-1], time), n=1) + + + +class HeisenbergXYZ(GateLibrary): + """ + Heisenberg XYZ Model: H = Jx[XX + Jy[YY + Jz[ZZ + + 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): + super().__init__(*args, **kwargs) + self.reg_size = reg + self.j_x, self.j_y, self.j_z = j_x, j_y, j_z + 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)] + + sys = GateBuilder() + std = sys.import_library(std_gates) + std.call_space = " {}" + + std.begin_gate(self.name, qargs, params=["time"]) + + 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 + 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 + 1]) + + # YY interaction: exp(-i * j_y * YY * time) + 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 + 1]) + + # ZZ interaction: exp(-i * j_z * ZZ * time) + std.cnot(qargs[i], qargs[i + 1]) + std.rz(f"{2 * j_z} * time", qargs[i + 1]) + std.cnot(qargs[i], qargs[i + 1]) + + std.end_gate() + # self.call_space = " {}" + 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]) + + def controlled(self, time, qubits, control): + """Apply controlled Heisenberg evolution.""" + self.controlled_op(self.name, (qubits[-1],[control]+qubits[:-1], time), n=1) + + + +class RandomizedHamiltonian(GateLibrary): + """ + Randomized Non-Commuting Hamiltonian for stress testing. + + 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): + super().__init__(*args, **kwargs) + self.reg_size = reg + self.seed = seed + self.density = density # Fraction of possible interactions to include + self.name = f"RandomHam_{self.reg_size}q_s{seed}_d{int(100*density)}" + + # Use seed for reproducible randomness in testing + random.seed(seed) + + names = string.ascii_letters + qargs = [names[i // len(names)] + names[i % len(names)] + for i in range(self.reg_size)] + + sys = GateBuilder() + std = sys.import_library(std_gates) + std.call_space = " {}" + + std.begin_gate(self.name, qargs, params=["time"]) + + # Random single-qubit rotations + pauli_gates = ['rx', 'ry', 'rz'] + for i in range(self.reg_size): + if random.random() < density: + gate_type = random.choice(pauli_gates) + angle = random.uniform(0.1, 2.0) # Random coupling strength + std.call_gate(gate_type, qargs[i], phases=[f"{angle} * time"]) + + # Random two-qubit interactions + for i in range(self.reg_size): + 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']) + angle = random.uniform(0.1, 1.5) + + # Apply random basis rotations + std.call_gate(basis_rot, qargs[i], phases=["pi/2"]) + std.call_gate(basis_rot, qargs[j], phases=["pi/2"]) + + # Controlled interaction + std.cnot(qargs[i], qargs[j]) + std.rz(f"{angle} * time", qargs[j]) + std.cnot(qargs[i], qargs[j]) + + # Inverse basis rotations + std.call_gate(basis_rot, qargs[i], phases=["-pi/2"]) + std.call_gate(basis_rot, qargs[j], phases=["-pi/2"]) + + # 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']) + angle = random.uniform(0.1, 1.0) + 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) + + def apply(self, time, qubits): + """Apply Heisenberg XYZ evolution for given 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) + + +class FermionicHubbard(GateLibrary): + """ + Simplified Fermionic Hubbard Model for testing. + + Implements hopping and on-site interaction terms using Jordan-Wigner + 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): + super().__init__(*args, **kwargs) + self.reg_size = reg + self.t = t # Hopping parameter + self.U = U # On-site interaction + 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)] + + sys = GateBuilder() + std = sys.import_library(std_gates) + std.call_space = " {}" + + std.begin_gate(self.name, qargs, params=["time"]) + + # Hopping terms with Jordan-Wigner strings + for i in range(self.reg_size - 1): + # Forward hopping: c†_i c_{i+1} + # Implement as (X_i - iY_i)(X_{i+1} + iY_{i+1})/4 with JW string + + # Apply Jordan-Wigner Z string between sites + for _ in range(i + 1, i + 1): # No string needed for nearest neighbor + # this is a remnant method when i looked at the JW transformation for more non-local interactions + # may be implemented in the future if current approach proves wrong + pass + + # XX term + std.ry("pi/2", qargs[i]) + std.ry("pi/2", qargs[i + 1]) + std.cnot(qargs[i], qargs[i + 1]) + std.rz(f"{self.t} * time", qargs[i + 1]) + std.cnot(qargs[i], qargs[i + 1]) + std.ry("-pi/2", qargs[i]) + std.ry("-pi/2", qargs[i + 1]) + + # YY term (with opposite sign) + std.rx("-pi/2", qargs[i]) + std.rx("-pi/2", qargs[i + 1]) + std.cnot(qargs[i], qargs[i + 1]) + std.rz(f"{self.t} * time", qargs[i + 1]) + std.cnot(qargs[i], qargs[i + 1]) + std.rx("pi/2", qargs[i]) + std.rx("pi/2", qargs[i + 1]) + + # XY term + std.ry("pi/2", qargs[i]) + std.rx("-pi/2", qargs[i + 1]) + std.cnot(qargs[i], qargs[i + 1]) + std.rz(f"-{self.t} * time", qargs[i + 1]) + std.cnot(qargs[i], qargs[i + 1]) + std.ry("-pi/2", qargs[i]) + std.rx("pi/2", qargs[i + 1]) + + # YX term + std.rx("-pi/2", qargs[i]) + std.ry("pi/2", qargs[i + 1]) + std.cnot(qargs[i], qargs[i + 1]) + std.rz(f"{self.t} * time", qargs[i + 1]) + std.cnot(qargs[i], qargs[i + 1]) + std.rx("pi/2", qargs[i]) + std.ry("-pi/2", qargs[i + 1]) + + # On-site interaction terms: U n_i n_j (for different spin species) + # Simplified as local Z rotations + for i in range(0, self.reg_size - 1, 2): # Assume even sites are spin up + if i + 1 < self.reg_size: # Adjacent site is spin down + # Implement as ZZ interaction + std.cnot(qargs[i], qargs[i + 1]) + std.rz(f"{self.U} * time", qargs[i + 1]) + std.cnot(qargs[i], qargs[i + 1]) + + std.end_gate() + # self.call_space = " {}" + 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]) + + def controlled(self, time, qubits, control): + """Apply controlled Heisenberg evolution.""" + self.controlled_op(self.name, (qubits[-1],[control]+qubits[:-1], time), n=1) + + +# Test suite factory function +def create_test_hamiltonians(reg_size=4): + """ + Factory function to create a suite of test Hamiltonians. + + Args: + reg_size: Number of qubits for the test register + + Returns: + Dictionary of Hamiltonian instances for testing + """ + # test_reg = list(range(reg_size)) + def anonymize(lib, aparams): + """ + Create an anonymous subclass of the given library with specified parameters. + Made for testing the Hamiltonian interface in general, with need to initialize + couplings constants before abstract use + Args: + lib: The library class to subclass + aparams: The parameters to pass to the superclass constructor + + 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) + 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 + } + + 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 new file mode 100644 index 0000000..50f2939 --- /dev/null +++ b/qbraid_algorithms/evolution/trotter.py @@ -0,0 +1,234 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +""" +Generalized Trotterization Module + +Implements Suzuki-Trotter decomposition for Hamiltonian evolution using +Suzuki's 1992/2005 recursive symmetric fractal formulation. + +The algorithm decomposes the evolution operator exp(-iHt) where H = Hp + Hq +into a sequence of simpler evolution operators that can be implemented +with fractional applications of individual Hamiltonians. + +Reference: Suzuki's symmetric decomposition formulas for higher-order +approximations of time evolution operators. + +Key features: +- Recursive symmetric fractal structure +- Higher-order accuracy with increased depth +- Requires fractional time evolution of individual Hamiltonians +""" +# TODO: change names from physics notation to python standard naming convention +# pylint: disable=invalid-name,too-many-positional-arguments + +from qbraid_algorithms.qtran import GateLibrary, std_gates + + +class Trotter(GateLibrary): + """ + Trotter decomposition gate library for Hamiltonian evolution. + + Implements Suzuki's recursive symmetric decomposition for approximating + exp(-i(Hp + Hq)t) using sequences of exp(-iHp*τ) and exp(-iHq*τ). + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def trot_suz(self, qubits, t, Hp, Hq, depth): + """ + Apply Suzuki-Trotter decomposition for two-Hamiltonian evolution. + + Args: + qubits: List of qubits to apply evolution to + t: Evolution time parameter + Hp: First Hamiltonian gate library (must have 'apply' method) + Hq: Second Hamiltonian gate library (must have 'apply' method) + depth: Recursion depth (higher = more accurate, more gates) + + The decomposition approximates exp(-i(Hp + Hq)t) using Suzuki's + symmetric fractal formula with O(t^(2*depth+1)) error. + """ + # 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: + self.call_subroutine(name, [qubit_list, t, depth]) + return name # BUG FIX: Return subroutine name + + # Get builder reference (operates in root scope) + sys = self.builder + std = sys.import_library(std_gates) + Ha = sys.import_library(Hp) + Hb = sys.import_library(Hq) + + # Define subroutine signature + qubit_array_param = f"qubit[{len(qubits)}] qubits" + + 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 + + # Base case: depth < 2, use simple first-order Trotter step + # Formula: exp(-iHp*t/2) * exp(-iHq*t) * exp(-iHp*t/2) + std.begin_if("recursion_depth < 2") + + # Apply first half of Hp evolution + Ha.apply("time/2", [f"qubits[{i}]" for i in range(len(qubits))]) + + # Apply full Hq evolution + Hb.apply("time", [f"qubits[{i}]" for i in range(len(qubits))]) + + # Apply second half of Hp evolution + Ha.apply("time/2", [f"qubits[{i}]" for i in range(len(qubits))]) + + std.program("return;") + std.end_if() + + # 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") + + # 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} + # where S_{k-1} represents the (k-1)th order approximation + + # First U_k * S_{k-1} step + std.call_subroutine(name, ["qubits", f"{uk_var}*time", "recursion_depth-1"]) + + # Second U_k * S_{k-1} step + 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"]) + + # Fourth U_k * S_{k-1} step + std.call_subroutine(name, ["qubits", f"{uk_var}*time", "recursion_depth-1"]) + + # Fifth U_k * S_{k-1} step + std.call_subroutine(name, ["qubits", f"{uk_var}*time", "recursion_depth-1"]) + + std.end_subroutine() + + # Execute the subroutine with provided parameters + self.call_subroutine(name, [qubit_list, t, depth]) + + return name + + def multi_trot_suz(self, qubits, t, hamiltonians, depth): + """ + Apply Suzuki-Trotter decomposition for multiple Hamiltonians. + + For more than two Hamiltonians, recursively pairs them using + binary tree decomposition. + + Args: + qubits: List of qubits to apply evolution to + t: Evolution time parameter + hamiltonians: List of Hamiltonian gate libraries + depth: Recursion depth for each pairwise decomposition + + Returns: + constructed anonymous gatebuilder + """ + + if len(hamiltonians) == 2: + self.trot_suz(qubits, t, hamiltonians[0], hamiltonians[1], depth) + class Ha(Trotter): + '''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): + """abstract hamiltonian apply""" + self.trot_suz(qubits, t, hamiltonians[0], hamiltonians[1], depth) + return Ha + + # For multiple Hamiltonians, use binary tree approach + # Split into two groups and recursively apply Trotter + mid = len(hamiltonians) // 2 + left_hams = hamiltonians[:mid] + 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] + + # 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''' + name = m_name + 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): + """ + Apply simple first-order linear Trotter decomposition. + + Implements: Prod| exp(-iH_j * t/steps) repeated 'steps' times + This is the simplest Trotter decomposition with O((t/d)^2) error. + + Args: + qubits: List of qubits to apply evolution to + t: Evolution time parameter + hamiltonians: List of Hamiltonian gate libraries + steps: Number of Trotter steps (higher = more accurate) + + Returns: + Name of the constructed subroutine + """ + ham_names = [H.name for H in hamiltonians] + name = f"trot_linear_{len(qubits)}_{'_'.join(ham_names)}_{steps}" + + if name in self.gate_ref: + self.call_subroutine(name, [qubits, t]) + return name + + # Build linear Trotter subroutine + sys = self.builder + std = sys.import_library(std_gates) + + # Import all Hamiltonian libraries + ham_libs = [sys.import_library(H) for H in hamiltonians] + + std.begin_subroutine(name, [f"qubit[{len(qubits)}] qubits", "float time"]) + self.gate_ref.append(name) + + # Apply Trotter steps + dt_var = std.add_var("dt", assignment=f"time/{steps}", qtype="float") + + for step in range(steps): + std.comment(f"Trotter step {step + 1}") + + # Apply each Hamiltonian for time dt + for ham_lib in ham_libs: + ham_lib.apply(dt_var, [f"qubits[{j}]" for j in range(len(qubits))]) + + std.end_subroutine() + + # Execute the subroutine + qubit_list = "{" + ",".join([str(q) for q in qubits]) + "}" + self.call_subroutine(name, [qubit_list, t]) + + return name diff --git a/qbraid_algorithms/hhl/__init__.py b/qbraid_algorithms/hhl/__init__.py new file mode 100644 index 0000000..e7148d9 --- /dev/null +++ b/qbraid_algorithms/hhl/__init__.py @@ -0,0 +1,29 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 providing Qasm file generator for HHL + +Functions +---------- + +.. autosummary:: + :toctree: ../stubs/ + + HHLLibrary + +""" +from .hhl import HHLLibrary + +__all__ = ['HHLLibrary'] diff --git a/qbraid_algorithms/hhl/hhl.py b/qbraid_algorithms/hhl/hhl.py new file mode 100644 index 0000000..9d42260 --- /dev/null +++ b/qbraid_algorithms/hhl/hhl.py @@ -0,0 +1,75 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +""" +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: +""" + +# Importing package modules +# pylint: disable=invalid-name +from qbraid_algorithms.qpe import PhaseEstimationLibrary + + +class HHLLibrary(PhaseEstimationLibrary): + '''HHL library using base Phase Estimation implementation''' + + def HHL(self, a: list, b: list, clock: list): + ''' + 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 + + Returns: + None + ''' + sys = self.builder + # Access to the quantum circuit builder (assumed to be defined in the parent class) + + # A = sys.import_library(a) + + # operation currently works within main method due to need of inverse op and use of ancillas + # TODO: refactor this into a full subroutine once complex Hamiltonians for phase estimation are supported + # rationale: simple evolution can be represented with negative time values, + # but static Hamiltonians require explicitly implementing the inverse operation + + Phase = sys.import_library(PhaseEstimationLibrary) + # Import the root Phase Estimation library for local application + + anc_q = sys.claim_qubits(1) + # Allocate one ancilla qubit + + anc_c = sys.claim_clbits(1) + # Allocate one classical bit for measurement result storage + + Phase.phase_estimation(b,clock,a) + # Apply the phase estimation routine with registers (b, clock, a) + + for i in range(len(clock)-1): + # Apply controlled rotations depending on clock qubits + # Controlled rotation around Y-axis by angle pi/(2^{i+1}), where i is the clock qubit index + self.controlled_op("ry", (anc_q[0], clock[i], f'pi/(2^{i+1})')) + # Controlled rotation around Y-axis, scaling by power of 2 (pi / 2^(i+1)) + + Phase.inverse_op(b,clock,a) + # Apply the inverse of phase estimation to uncompute and restore registers + + self.measure(anc_q,anc_c) + # Measure the ancilla qubit and store result in the classical bit diff --git a/qbraid_algorithms/qft/__init__.py b/qbraid_algorithms/qft/__init__.py index a5c48e9..fc5bf16 100644 --- a/qbraid_algorithms/qft/__init__.py +++ b/qbraid_algorithms/qft/__init__.py @@ -23,12 +23,15 @@ load_program generate_subroutine + QFTLibrary """ from .qft import generate_subroutine, load_program +from .qft_lib import QFTLibrary __all__ = [ "load_program", "generate_subroutine", + "QFTLibrary" ] diff --git a/qbraid_algorithms/qft/qft.py b/qbraid_algorithms/qft/qft.py index 1b822f8..e83ede9 100644 --- a/qbraid_algorithms/qft/qft.py +++ b/qbraid_algorithms/qft/qft.py @@ -18,14 +18,16 @@ """ import os import shutil -import tempfile from pathlib import Path import pyqasm from pyqasm.modules.base import QasmModule +from qbraid_algorithms.qtran import QasmBuilder from qbraid_algorithms.utils import _prep_qasm_file +from .qft_lib import QFTLibrary + def load_program(num_qubits: int) -> QasmModule: """ @@ -36,29 +38,16 @@ def load_program(num_qubits: int) -> QasmModule: Returns: (PyQasm Module) pyqasm module containing the QFT circuit """ - # Load the QFT QASM files into a staging directory - temp_dir = tempfile.mkdtemp() - qft_src = Path(__file__).parent / "qft.qasm" - qft_sub_src = Path(__file__).parent / "qft_subroutine.qasm" - qft_dst = os.path.join(temp_dir, "qft.qasm") - qft_sub_dst = os.path.join(temp_dir, "qft_subroutine.qasm") - shutil.copy(qft_src, qft_dst) - shutil.copy(qft_sub_src, qft_sub_dst) - - # Replace variable placeholders with user-defined parameters - replacements = {"QFT_SIZE": str(num_qubits)} - _prep_qasm_file(qft_sub_dst, replacements) - _prep_qasm_file(qft_dst, replacements) # Load the algorithm - module = pyqasm.load(qft_dst) + sys = QasmBuilder(qubits=num_qubits) + qft = sys.import_library(QFTLibrary) + qft.QFT([*range(num_qubits)]) + module = pyqasm.loads(sys.build()) - # Delete the created files - shutil.rmtree(temp_dir) return module - def generate_subroutine( num_qubits: int, quiet: bool = False, path: str | None = None ) -> None: diff --git a/qbraid_algorithms/qft/qft_lib.py b/qbraid_algorithms/qft/qft_lib.py new file mode 100644 index 0000000..d93b856 --- /dev/null +++ b/qbraid_algorithms/qft/qft_lib.py @@ -0,0 +1,100 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 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) +""" + +# from QasmBuilder import FileBuilder, QasmBuilder, GateBuilder +import string + +# pylint: disable=invalid-name +# mypy: disable_error_code="call-arg" +from qbraid_algorithms.qtran import GateBuilder, GateLibrary, std_gates + + +class QFTLibrary(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): + """ + Initialize the QFTLibrary instance. + + Args: + *args: Variable length argument list for parent GateLibrary. + **kwargs: Arbitrary keyword arguments for parent GateLibrary. + """ + super().__init__(*args,**kwargs) + # self.call_space = "{}" + + def QFT(self, qubits:list, swap=True): + """ + Constructs a Quantum Fourier Transform (QFT) gate for the specified qubits. + + Parameters: + qubits (list of int): List of qubit indices (as integers) to apply the QFT on. + swap (bool, optional): If True, applies swap gates at the end to reverse qubit order. Defaults to True. + + Behavior: + - Builds and registers a QFT gate with optional swaps. + - Calls the constructed gate on the provided qubits. + """ + name = f'QFT{len(qubits)}{"S" if swap else ""}' + if name in self.gate_ref: + 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))] + + 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)}") + if swap: + for i in range(len(qubits)//2): + std.call_gate("swap", qargs[i], qargs[-i-1]) + + std.end_gate() + + # std.begin_gate(name,qargs) + # std.begin_loop(len(qubits)) + # std.h("i") + # std.begin_loop(f"j in [i+1:{len(qubits)}]") + # std.call_gate("cp","j",controls="i",phases="pi>>(j-i)") + # std.end_loop() + # std.end_loop() + # std.end_gate() + + # std.begin_subroutine(name,[f'qubit[{len(qubits)}] a']) + # std.begin_loop(len(qubits)) + # std.h("i") + # std.begin_loop(f"j in [i+1:{len(qubits)}]") + # std.call_gate("cp","j",controls="i",phases="pi>>(j-i)") + # std.end_loop() + # std.end_loop() + # std.end_subroutine() + + 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 a413b1f..996e846 100644 --- a/qbraid_algorithms/qpe/__init__.py +++ b/qbraid_algorithms/qpe/__init__.py @@ -28,6 +28,7 @@ """ +from .phase_est import PhaseEstimationLibrary from .qpe import generate_subroutine, get_result, load_program -__all__ = ["load_program", "generate_subroutine", "get_result"] +__all__ = ["load_program", "generate_subroutine", "get_result",'PhaseEstimationLibrary'] diff --git a/qbraid_algorithms/qpe/phase_est.py b/qbraid_algorithms/qpe/phase_est.py new file mode 100644 index 0000000..01e38aa --- /dev/null +++ b/qbraid_algorithms/qpe/phase_est.py @@ -0,0 +1,124 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +""" +PhaseEstLibrary + +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 + +from qbraid_algorithms.qft import QFTLibrary + +# TODO: regularize application of inverse op to another name for abstract hamiltonian +# or let this be acceptable behavior +# pylint: disable=arguments-differ +# mypy: disable_error_code="override,call-arg" +# from GateLibrary import GateLibrary, std_gates +from qbraid_algorithms.qtran import GateBuilder, GateLibrary, std_gates + + +class PhaseEstimationLibrary(GateLibrary): + ''' + Library to implement phase estimation circuits directly related to classical + phase estimation algorithms. Iterative phase estimation will be supported via + the Rodeo package. This library supports both static and time-dependent Hamiltonians. + ''' + def __init__(self,*args,**kwargs): + super().__init__(*args,**kwargs) + + def phase_estimation(self, qubits:list,spectra:list,hamiltonian, evolution=None): + """ + Implements the quantum phase estimation algorithm using the provided qubits, + ancilla clock register (spectra), and Hamiltonian. + + Parameters: + qubits (list): List of qubits representing the input state. + spectra (list): List of ancilla qubits used as the clock register. + hamiltonian: Hamiltonian operator to be applied. + evolution (optional): Time evolution parameter for time-dependent Hamiltonians. + + Returns: + str: The name of the generated phase estimation gate. + """ + # Implementation notes: + # The current implementation requires gate call results for the Hamiltonian application to keep gate scope. + # Phase estimation is currently at a limbo being gate level to support a much simpler form of HHL + # application with an inverse gate call. The ideal procedure for an inverse controlled operation is not + # 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}' + if name in self.gate_ref: + self.call_gate(name,spectra[-1],qubits+spectra[:-1]) + return name + sys = GateBuilder() + std = sys.import_library(std_gates) + ham = sys.import_library(hamiltonian) + 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))] + # std.begin_gate(name,[f"qubit[{len(qubits)}] a",f"qubit[{len(spectra)}] b"]) + 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]) + else: + for _ in range(2**i): + 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]) + return name + + 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}' + if name in self.gate_ref: + self.call_gate(name,spectra[-1],qubits+spectra[:-1]) + return name + sys = GateBuilder() + std = sys.import_library(std_gates) + ham = sys.import_library(hamiltonian) + 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))] + # 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):],)) + for i in reversed(range(len(spectra))): + if evolution is not None: + 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]) + std.end_gate() + + self.merge(*sys.build(),name) + self.call_gate(name,spectra[-1],qubits+spectra[:-1]) + return name diff --git a/qbraid_algorithms/qtran/__init__.py b/qbraid_algorithms/qtran/__init__.py new file mode 100644 index 0000000..7ccc267 --- /dev/null +++ b/qbraid_algorithms/qtran/__init__.py @@ -0,0 +1,37 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 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 + +""" +# 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'] diff --git a/qbraid_algorithms/qtran/gate_library.py b/qbraid_algorithms/qtran/gate_library.py new file mode 100644 index 0000000..799717b --- /dev/null +++ b/qbraid_algorithms/qtran/gate_library.py @@ -0,0 +1,496 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +""" +QasmBuilder Library - OpenQASM Code Generation Framework + +This library provides a flexible framework for generating OpenQASM code through +a hierarchical builder pattern. It supports different output formats including +complete quantum circuits, gate definitions, and include files. GateLibrary is +a base framework for macroing gate, import, and algorithm generation and is +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 + +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: + """ + BASE GATE LIBRARY + + 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 + + """ + + def __init__(self, gate_import, gate_ref, gate_defs, program_append, builder, annotated=False): + """ + Initialize the gate library with necessary components. + + Args: + gate_import: List of imported gate libraries + gate_ref: List of available gate names + gate_defs: Dictionary of gate definitions + program_append: Function to append code to the program + 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 + + def call_gate(self, gate, target, controls=None, phases=None, prefix=""): + """ + GATE APPLICATION + + Apply a quantum gate with optional controls and phase parameters. + + Format: [prefix][gate]([phases]) [controls...] [target]; + + + Args: + gate: Name of the gate to apply + target: Target qubit index + controls: Control qubit(s) - single int or list + phases: Phase parameter(s) - single value or list + prefix: Optional prefix (e.g., for controlled gates) + """ + # 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") + + # Build gate call string + call = prefix + str(gate) + + # Add phase parameters if provided + if phases is not None: + 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 += " " + # Add control qubits if provided + if controls is not None: + if isinstance(controls, list): + for control in controls: + call += self.call_space.format(control) + "," + + else: + 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): + """ + SUBROUTINE APPLICATION + + Apply a subroutine with parameters and optionally specify a target + variable to return value to + + Format: [capture] = [subroutine](parameters); + + + Args: + subroutine: Name of the gate to apply + 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") + + 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 + + Measure quantum bits and store results in classical bits. + + Format: cb[{clbit_indices}] = measure qb[{qubit_indices}]; + + + Args: + qubits: List of qubit indices to measure + clbits: List of classical bit indices for storing results + """ + # Format classical and quantum bit indices + cindex = "cb[{" + str(clbits)[1:-1] + "}]" + qindex = "qb[{" + str(qubits)[1:-1] + "}]" + call = f"{cindex} = measure {qindex};" + self.program(call) + + def comment(self, line: str): + """ + COMMENTS + + Add comments to the generated code for documentation. + Supports both single-line (//) and multi-line (/* */) comments. + + + Args: + line: Comment text (can contain newlines for multi-line) + """ + call = "" + if "\n" in line: + # Multi-line comment + call += "/*\n" + line + "\n*/" + else: + # Single-line comment + call += "//" + line + self.program(call) + + def begin_if(self, conditional: str): + """ + CONDITIONAL BLOCK + + Start a conditional execution block. + + Format: if (condition) { ... } + + + Args: + conditional: Boolean expression string + """ + call = f"if ({conditional})" + "{" + self.program(call) + self.builder.scope += 1 # Increase indentation level + + 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 + + Args: + iterator: Loop specification (int, tuple, or string) + ident: Loop variable identifier + """ + if isinstance(iterator, int): + # Simple range from 0 to iterator + base = "int" + dom = f"[0:{int(iterator)-1}]" + elif isinstance(iterator, tuple): + if len(iterator) == 2: + if isinstance(iterator[0], str): + # Custom type and domain + base = iterator[0] + dom = iterator[1] + else: + # Range from start to end + base = "int" + dom = f"[{int(iterator[0])}:{int(iterator[1])}]" + else: + # Range with step or custom float range + if isinstance(iterator[1], int): + # Integer range with step + base = "int" + dom = f"[{int(iterator[0])}:{int(iterator[2])}:{int(iterator[1])}]" + else: + # 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] + "}" + elif isinstance(iterator, str): + # Custom loop syntax + call = "for " + iterator + "{" + self.program(call) + self.builder.scope += 1 + return ident + else: + print(f"loop has improper parameterization with: {iterator}") + return None + + call = f"for {base} {ident} in {dom} " + "{" + self.program(call) + self.builder.scope += 1 + return ident + + def begin_gate(self, name, qargs, params=None): + """ + GATE DEFINITION + + Define a custom quantum gate. + + Format: gate name(params) qargs { ... } + + Args: + name: Gate name + qargs: Quantum arguments (qubit parameters) + params: Optional classical parameters + """ + 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)}" +"{" + self.program(call) + self.builder.scope += 1 + + + def begin_subroutine(self, name, parameters: list[str], return_type=None): + """ + SUBROUTINE DEFINITION + + Define a classical subroutine with optional return type. + + Format: def name(parameters) -> return_type { ... } + + + Args: + name: Subroutine name + parameters: List of parameter names + return_type: Optional return type specification + """ + 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 ''}" + "{" + self.program(call) + self.builder.scope += 1 + + def close_scope(self): + """Close the current scope block and decrease indentation level.""" + self.builder.scope -= 1 + self.program("}") + + def end_if(self): + """End conditional block.""" + self.close_scope() + + def end_loop(self): + """End loop block.""" + self.close_scope() + + def end_gate(self): + """End gate definition block.""" + self.close_scope() + + def end_subroutine(self): + """End subroutine definition block.""" + self.close_scope() + + def controlled_op(self, gate_call, params, n=0): + """ + CONTROLLED OPERATIONS + + Apply gates with control qubits using the ctrl modifier. + + Format: ctrl(n) @ gate_operation + + + Args: + gate_call: Gate name (string) or gate function + params: Gate parameters + n: Number of control qubits + """ + 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})'} @ ") + else: + # Gate function - set modifier and call + self.prefix = f"ctrl{'' if n<2 else f'({n})'} @ " + gate_call(*params) + self.prefix = "" + + def inverse_op(self, gate_call, params): + """ + INVERSE OPERATIONS + + Apply inverse of gute using the inv modifier. + + Format: inv @ gate_operation + + + Args: + gate_call: Gate name (string) or gate function + params: Gate parameters + """ + if isinstance(gate_call, str): + # Direct gate name - call with inv prefix + self.call_gate(gate_call, *params, prefix="inv @") + else: + # Gate function - set modifier and call + self.prefix = "inv @ " + gate_call(*params) + self.prefix = "" + + def add_gate(self, name: str, gate_def: str): + """ + Add a custom gate definition to the library. + + Args: + name: Gate name + gate_def: Gate definition string + """ + if name in self.gate_ref: + print(f"warning: gate {name} replacing existing namespace") + self.gate_defs[name] = gate_def + self.gate_ref.append(name) + + 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): + """ + Merges data from a built library/GateBuilder into the current library bases scope + Args: + program: Gate body which is added into definitions + imports: all imports the gate depends on + gate_def: Gate definitions for any child gates/dynamic libraries used + + """ + for imps in imports: + if imps not in self.gate_import: + self.gate_import.append(imps) + + for nem, defs in definitions.items(): + if nem not in self.gate_defs: + self.gate_defs[nem] = defs + self.gate_defs[name] = program + self.gate_ref.append(name) + + +class std_gates(GateLibrary): + """ + STANDARD GATES LIBRARY + + 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) + """ + + # 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 + + def __init__(self, *args, **kwargs): + """Initialize standard gates library and register all gates.""" + super().__init__(*args, **kwargs) + # Import standard gates library if not already imported + if std_gates.name not in self.gate_import: + self.gate_import.append(std_gates.name) + + # Register all standard gates + for gate in std_gates.gates: + if gate not in self.gate_ref: + self.gate_ref.append(gate) + + # ═══════════════════════════════════════════════════════════════════════════ + # SINGLE-QUBIT GATES + # ═══════════════════════════════════════════════════════════════════════════ + + def phase(self, theta, targ): + """Apply phase gate: !0⟩>!0⟩, !1⟩>e^(iθ)!1⟩""" + self.call_gate("phase", targ, phases=theta) + + def x(self, targ): + """Apply Pauli-X gate (bit flip): !0⟩> !1⟩, !1⟩> !0⟩""" + self.call_gate('x', targ) + + def y(self, targ): + """Apply Pauli-Y gate: !0⟩>i!1⟩, !1⟩>-i!0⟩""" + self.call_gate('y', targ) + + def z(self, targ): + """Apply Pauli-Z gate (phase flip): !0⟩> !0⟩, !1⟩>-!1⟩""" + self.call_gate('z', targ) + + def h(self, targ): + """Apply Hadamard gate: creates superposition""" + self.call_gate('h', targ) + + def s(self, targ): + """Apply S gate (phase): !1⟩>i!1⟩""" + self.call_gate('s', targ) + + def sdg(self, targ): + """Apply S-dagger gate (inverse phase): !1⟩>-i!1⟩""" + self.call_gate('sdg', targ) + + def sx(self, targ): + """Apply square root of X gate""" + self.call_gate('sx', targ) + + def rx(self,theta,targ): + """Apply rx gate""" + self.call_gate("rx", targ, phases=theta) + + def ry(self,theta,targ): + """Apply ry gate""" + self.call_gate("ry", targ, phases=theta) + + 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 cry(self,theta,control,targ): + """Apply controlled ry gate""" + 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 new file mode 100644 index 0000000..d6e7d26 --- /dev/null +++ b/qbraid_algorithms/qtran/module_loader.py @@ -0,0 +1,72 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +Functions decorated with `qasm_pipe` must: + 1. Accept 'path' and 'quiet' as keyword arguments. + 2. Return a tuple of (file_name, program_string). +The decorator will: + - Write the QASM string to "{file_name}.qasm" in the specified path (or current working directory if not provided). + - Create the output directory if it does not exist. + - Print the file location unless 'quiet' is True. +Typical usage: + @qasm_pipe + def generate_qasm(..., path=None, quiet=False): + ... + return file_name, program_string''' +import os +from functools import wraps +from typing import Callable + + +def qasm_pipe(func: Callable) -> Callable: + """ + Decorator that captures path and quiet arguments from the decorated function, + 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) + + 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) + + # Call the decorated function to get the tuple output + file_name, program_string = func(*args, **kwargs) + + # Determine the full file path + if path is None: + output_path = os.path.join(os.getcwd(), f"{file_name}.qasm") + else: + # Create directory if it doesn't exist + os.makedirs(path, exist_ok=True) + 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: + file.write(program_string) + + if not quiet: + print(f"QASM file created: {output_path}") + + return output_path + + return wrapper diff --git a/qbraid_algorithms/qtran/qasm_builder.py b/qbraid_algorithms/qtran/qasm_builder.py new file mode 100644 index 0000000..28c9e6f --- /dev/null +++ b/qbraid_algorithms/qtran/qasm_builder.py @@ -0,0 +1,361 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +""" +QasmBuilder Library - OpenQASM Code Generation Framework + +This library provides a flexible framework for generating OpenQASM code through +a hierarchical builder pattern. It supports different output formats including +complete quantum circuits, gate definitions, and include files. +Built on top of the the root FileBuilder class which seperates text content from +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 + +Class Extensions: +- GateBuilder +- QasmBuilder +- IncludeBuilder +""" + + +class FileBuilder: + """ + Base class for all OpenQASM code builders. + + Provides core functionality for managing imports, gate definitions, + program content, and scope tracking. This class serves as the foundation + 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 + """ + + 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 + """ + 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): + """ + Import and initialize a quantum gate library. + + This method creates an instance of the specified library class and connects + it to the current builder's data structures. The library gains access to + the builder's import list, gate references, definitions, and program + appending functionality. + + Args: + lib_class: The library class to instantiate (e.g., std_gates) + annotated: Whether to enable annotated syntax mode + + Returns: + Configured library instance ready for use + + Example: + program = builder.import_library(std_gates) + 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 + program_append=self.program_append, # Provide code appending function + builder=self, # Pass reference to this builder + annotated=annotated # Set annotation mode + ) + + def program_append(self, line): + """ + Append a line of code to the program with proper indentation. + + This method handles the formatting of generated code by applying + the appropriate indentation level based on the current scope. + Each scope level adds one tab character for proper nesting. + + Args: + line: The code line to append (without indentation) + + Note: + Indentation is automatically applied based on self.scope. + Each scope level contributes one tab character. + """ + self.program += self.scope * '\t' + line + "\n" + + +class GateBuilder(FileBuilder): + """ + Specialized builder for generating gate definition files. + + This builder is designed to create standalone gate definition files + that can be included in other OpenQASM programs. It focuses on + generating reusable gate definitions without the overhead of + complete circuit structure. + + Use cases: + - 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 + + def build(self): + """ + Generate the final gate definition output. + + Produces a tuple containing the generated program code, + list of required imports, and dictionary of gate definitions. + This format is suitable for creating include files or + embedding in larger programs. + + Returns: + tuple: (program_code, imports_list, gate_definitions_dict) + + Warnings: + 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") + return self.program, self.imports, self.gate_defs + + +class QasmBuilder(FileBuilder): + """ + Complete OpenQASM circuit builder for quantum programs. + + This is the primary builder for creating full quantum circuits with + proper OpenQASM headers, qubit/classical bit declarations, library + imports, and the complete program structure. It automatically manages + 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 + """ + + def __init__(self, qubits, clbits=None, version=3): + """ + Initialize a complete quantum circuit builder. + + Creates a builder configured for generating full OpenQASM programs + with the specified resources and version compatibility. + + Args: + qubits: Number of qubits to allocate initially + clbits: Number of classical bits (defaults to qubit count if None) + version: OpenQASM version number (default: 3) + + The builder automatically generates appropriate headers and + resource declarations based on these parameters. + """ + # Generate OpenQASM version header + self.qasm_header = f"OPENQASM {version};\n" + + # Initialize quantum resource counters + self.qubits = qubits + if clbits is not None: + self.clbits = clbits + else: + # Default classical bits to match qubit count + self.clbits = qubits + + # Initialize base builder functionality + super().__init__() + + def claim_qubits(self, number: int): + """ + Dynamically allocate additional qubits to the circuit. + + This method allows libraries and algorithms to request additional + quantum resources beyond the initial allocation. It returns the + indices of the newly allocated qubits for use in gate operations. + + Args: + number: How many additional qubits to allocate + + Returns: + list: Indices of the newly allocated qubits + + Example: + ancilla_qubits = builder.claim_qubits(3) # Get 3 ancilla qubits + # ancilla_qubits might be [5, 6, 7] if 5 qubits were already allocated + """ + # Generate indices for new qubits starting from current count + indexing = [*range(self.qubits, self.qubits + number)] + # Update total qubit count + self.qubits += number + return indexing + + def claim_clbits(self, number: int): + """ + Dynamically allocate additional classical bits to the circuit. + + Similar to claim_qubits but for classical bit resources used + for measurement results and classical computation. + + Args: + number: How many additional classical bits to allocate + + Returns: + list: Indices of the newly allocated classical bits + + Example: + result_bits = builder.claim_clbits(2) # Get 2 measurement bits + """ + # Generate indices for new classical bits + indexing = [*range(self.clbits, self.clbits + number)] + # Update total classical bit count + self.clbits += number + return indexing + + 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 + + Returns: + str: Complete OpenQASM program ready for execution + + The generated code follows this structure: + ``` + OPENQASM 3; + include "std_gates.inc"; + qubit[10] qb; + bit[10] cb; + // Custom gate definitions + // Main program code + ``` + + Warnings: + 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") + + # 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) + + # Add qubit declaration + circuit_def = f"qubit[{int(self.qubits)}] qb;\n" + + # Add classical bit declaration if needed + if self.clbits > 0: + circuit_def += f"bit[{int(self.clbits)}] cb;\n" + qasm_code += circuit_def + + # Add all custom gate definitions + for gate_def in self.gate_defs.values(): + qasm_code += gate_def + "\n" + + # Add main program content + qasm_code += self.program + + return qasm_code + + +class IncludeBuilder(FileBuilder): + """ + Builder for generating OpenQASM include files. + + Creates include files that can be imported by other OpenQASM programs. + These files typically contain gate definitions, constants, and reusable + 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 + """ + + def build(self): + """ + Generate the include file content. + + Creates a properly formatted include file containing all + imported libraries, gate definitions, and associated code. + The output is suitable for saving as a .inc file and + including in other OpenQASM programs. + + Returns: + str: Complete include file content + + Format: + ``` + include "dependency.inc"; + // Gate definitions + // Utility code + ``` + + Warnings: + 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") + + # 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) + + # Add all gate definitions + for gate_def in self.gate_defs.values(): + qasm_code += gate_def + "\n" + + # Add main program content + qasm_code += self.program + + return qasm_code diff --git a/qbraid_algorithms/rodeo/__init__.py b/qbraid_algorithms/rodeo/__init__.py new file mode 100644 index 0000000..6db6305 --- /dev/null +++ b/qbraid_algorithms/rodeo/__init__.py @@ -0,0 +1,29 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 providing QFT algorithmic primitive implementation. + +Functions +---------- + +.. autosummary:: + :toctree: ../stubs/ + + RodeoLibrary + +""" +from .rodeo import RodeoLibrary + +__all__ = ['RodeoLibrary'] diff --git a/qbraid_algorithms/rodeo/rodeo.py b/qbraid_algorithms/rodeo/rodeo.py new file mode 100644 index 0000000..3309b84 --- /dev/null +++ b/qbraid_algorithms/rodeo/rodeo.py @@ -0,0 +1,168 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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) +''' +import random +import string + +# pylint: disable=too-many-positional-arguments,too-many-locals +# mypy: disable_error_code="call-arg" +from qbraid_algorithms.qtran import GateBuilder, GateLibrary, std_gates + + +class RodeoLibrary(GateLibrary): + """ + A quantum gate library implementing the Rodeo algorithm for quantum state preparation. + + The Rodeo algorithm is a quantum algorithm used for amplitude amplification and + 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): + """ + Implement the Rodeo algorithm with multiple ancilla qubits. + + This method creates a quantum gate that implements the Rodeo algorithm using + a specified number of ancilla qubits (depth). Each ancilla qubit goes through + a Hadamard-controlled operation-phase-Hadamard sequence. + + Args: + qubits: List of qubit indices to operate on. The last qubit is treated specially. + t: Time evolution parameter for the phase gates + depth: Number of ancilla qubits to use (also determines algorithm depth) + hamiltonian: Hamiltonian object defining the controlled evolution + evolution: Optional parameter to control evolution behavior + + Returns: + str: Name of the created gate for potential reuse + """ + 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}') + if name in self.gate_ref: + 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) + std.call_space = " {}" + 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)] + + 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]) + else: + 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.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): + """ + Implement the Rodeo algorithm with mid-circuit measurements (MCM). + + This is an optimized version that uses only one ancilla qubit but repeats + the process multiple times with mid-circuit measurements. The algorithm + breaks early if a successful measurement is obtained. + + Args: + qubits: List of qubit indices to operate on. The last qubit is treated specially. + t: Time evolution parameter for the phase gates + depth: Number of iterations to perform + hamiltonian: Hamiltonian object defining the controlled evolution + evolution: Optional parameter to control evolution behavior + + Returns: + str: Name of the created gate for potential reuse + """ + 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)] + # 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( + # f"R{len(qubits)}_{hamiltonian.name}", + # "{"+" ,".join(s)+"}", + # type=f"array[float[32],{depth}]" + # ) + 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.begin_if(f"cb{anc_c} == true") + self.program("break;") + self.end_if() + self.end_loop() + return name + + sys = GateBuilder() + std = sys.import_library(std_gates) + std.call_space = " {}" + 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') + 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]) + else: + 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.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.begin_if(f"cb{anc_c} == true") + self.program("break;") + self.end_if() + self.end_loop() + return name diff --git a/qbraid_algorithms/todo.txt b/qbraid_algorithms/todo.txt new file mode 100644 index 0000000..2366a04 --- /dev/null +++ b/qbraid_algorithms/todo.txt @@ -0,0 +1,24 @@ + +QasmBuilder: +finish scoping and validate building import file generation + +GateLibrary: +look into better controlled application +- ie some static std_gate calls only accept a target. Current workaround is to directly call the gate name. +- - look at either adding args/kwargs to static gate passthrough or having behavior returning gate name on call with null +- - a decorator might be another way +reformalize ancilla claiming +- probable temp practice is that any function that can claim ancilla must work within root file (base builder) +scope as a subroutine rather than gate, means that only update would be safety checking certain calls when not established +in header (ie defined within body so ordering of definitions may be wrong) +- would also mean updating qpe, select/prep as they are in gate formalism currently due to lack of rendering support for subroutines + +Ambiguous: +pragma annotations in general +-specifically one for 0 state ancilla postselection? + +Testing: +direct pyqasm validation tests within builder_algorithsm have been suspended due to lack of controlled op and +subroutine scope support in pyqasm module +- once those are fixed and released in new pyqasm version, "assert is_valid" checks need to be uncommented +remove many file level lint skips caused by naming and unused test variables \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 7ae2bf4..f2a0280 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ -qbraid==0.9.9.dev20250821232849 -pyqasm>=0.5.0,<0.6.0 \ No newline at end of file +qbraid==0.9.9.dev20250814175257 +pyqasm>=0.5.0,<0.6.0 +sympy>=1.14.0 +scipy>=1.16.0 +numpy>=2.3.1 diff --git a/ruff.toml b/ruff.toml index 3b90da3..817b2b0 100644 --- a/ruff.toml +++ b/ruff.toml @@ -28,7 +28,7 @@ exclude = [ "venv", ] -line-length = 100 +line-length = 120 indent-width = 4 extend-include = ["*.ipynb"] diff --git a/tests/test_bells_inequality.py b/tests/test_bells_inequality.py new file mode 100644 index 0000000..c279afd --- /dev/null +++ b/tests/test_bells_inequality.py @@ -0,0 +1,28 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +""" +Tests for Bell's inequality module. +""" + +from pyqasm.modules.base import QasmModule + +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() + # Check that it returns a valid Qasm# module module + assert isinstance(circuit, QasmModule), f"Expected QasmModule, got {type(circuit)}" diff --git a/tests/test_builder_algorithms.py b/tests/test_builder_algorithms.py new file mode 100644 index 0000000..fc1d37a --- /dev/null +++ b/tests/test_builder_algorithms.py @@ -0,0 +1,889 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +""" +Test Algorithms - Semantic Validation + +This module tests the quantum algorithm implementations (GQSP, Trotter, PrepSel) +using the enhanced Hamiltonian test suite. Validates that generated QASM code +is syntactically correct using pyqasm validation. + +Tests include: +1. GQSP algorithm with various Hamiltonians and depths +2. Trotter decomposition with multiple Hamiltonian pairs +3. Preparation-Selection library functionality +4. Algorithm parameter validation and edge cases +""" +import string + +#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 +from itertools import combinations + +import numpy as np +import pytest + +from qbraid_algorithms.amplitude_amplification import AALibrary +from qbraid_algorithms.embedding import PauliOperator, Prep, PrepSelLibrary, Select +from qbraid_algorithms.evolution import GQSP, Trotter, create_test_hamiltonians +from qbraid_algorithms.qpe import PhaseEstimationLibrary + +# Import modules +from qbraid_algorithms.qtran import GateBuilder, GateLibrary, QasmBuilder, std_gates + +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.""" + + def setup_method(self): + self.test_hamiltonians = create_test_hamiltonians(reg_size=3) + self.test_qubits = [*range(3)] + + def test_phase_estimation_basic_functionality(self): + """Test Phase Estimation with basic parameters.""" + for ham_name, hamiltonian in self.test_hamiltonians.items(): + builder = QasmBuilder(3) + anc_q = builder.claim_qubits(3) + 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) + + def controlled(self, *args, **kwargs): + super().controlled(0.1, *args, **kwargs) + + try: + pe.phase_estimation(self.test_qubits, anc_q, Ham) + std.measure(anc_q, anc_c) + + program = builder.build() + + # Validate structure + assert isinstance(program, str) + assert len(program) > 0 + + # Should contain Phase Estimation-specific elements + 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)}") + + def test_phase_estimation_evolution(self): + """Test Phase Estimation with circuit evolution.""" + for ham_name, hamiltonian in self.test_hamiltonians.items(): + builder = QasmBuilder(3) + anc_q = builder.claim_qubits(3) + anc_c = builder.claim_clbits(3) + std = builder.import_library(std_gates) + pe = builder.import_library(PhaseEstimationLibrary) + + try: + pe.phase_estimation(self.test_qubits, anc_q, hamiltonian, evolution=0.1) + std.measure(anc_q, anc_c) + + program = builder.build() + + # Validate structure + assert isinstance(program, str) + assert len(program) > 0 + + # Should contain Phase Estimation-specific elements + 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)}") + +class TestGQSPAlgorithm: + """Test Generalized Quantum Signal Processing algorithm.""" + + def setup_method(self): + """Set up test environment.""" + self.test_hamiltonians = create_test_hamiltonians(reg_size=3) + self.test_qubits = [*range(3)] + self.test_phases = [0.1, 0.2, 0.3, 0.15, 0.25, 0.35, 0.05] # 2*depth + 1 + + def test_gqsp_basic_functionality(self): + """Test GQSP with basic parameters.""" + for ham_name, hamiltonian in self.test_hamiltonians.items(): + 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 controlled(self,*args,**kwargs): + super().controlled(.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) + + program = builder.build() + + # Validate structure + assert isinstance(program, str) + assert len(program) > 0 + + # Should contain GQSP-specific elements + assert 'GQSP' in program or 'gqsp' in program.lower() + + # Validate with pyqasm + # is_valid, error_msg = self._validate_qasm_with_pyqasm(program) + # assert is_valid, f"GQSP failed for {ham_name}: {error_msg}\nQASM:\n{program}" + + except Exception as e: + pytest.fail(f"GQSP basic test failed for {ham_name}: {str(e)}") + + 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 controlled(self,*args,**kwargs): + super().controlled(.1,*args,**kwargs) + for depth in depths: + builder = QasmBuilder(3) + std = builder.import_library(std_gates) + gqsp = builder.import_library(GQSP) + + # Generate appropriate number of phases + phases = [0.1 * (i + 1) for i in range(2 * depth + 1)] + + try: + gqsp.GQSP(self.test_qubits, phases, Ham, depth=depth) + std.measure(self.test_qubits,self.test_qubits) + + full_qasm = builder.build() + + # Validate QASM + # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # 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() + + except Exception as e: + pytest.fail(f"GQSP depth {depth} test failed: {str(e)}") + + def test_gqsp_parameter_optimization(self): + """Test GQSP parameter generation and optimization methods.""" + # gqsp_instance = GQSP() + + # Test cost function generation + try: + cost_func, param_names = GQSP.gen_cost(depth=2, t=0.5) + + # Cost function should be callable + assert callable(cost_func) + + # Should accept parameter array + test_params = np.ones(5) # 2*2 + 1 parameters + cost_value = cost_func(test_params) + + # Cost should be numeric + assert isinstance(cost_value, (int, float)) + assert cost_value >= 0 # Cost should be non-negative + + # Parameter names should be reasonable + assert isinstance(param_names, list) + assert len(param_names) > 0 + + except Exception as e: + pytest.fail(f"GQSP parameter optimization test failed: {str(e)}") + + def test_gqsp_spectrum_finding(self): + """Test GQSP spectrum optimization (simplified).""" + # gqsp_instance = GQSP() + + # Test with small depth to keep test fast + depth = 1 + + try: + # This might take time, so we'll just test it doesn't crash + fits, time_points = GQSP.find_gqsp_spectrum(depth) + + # Should return reasonable results + assert isinstance(fits, list) + assert isinstance(time_points, np.ndarray) + assert len(fits) == len(time_points) + + # Each fit should have correct number of parameters + expected_params = 2 * depth + 1 + for fit in fits: + assert len(fit) == expected_params + + except Exception as e: + # Optimization might fail - that's OK for semantic tests + if "optimization" not in str(e).lower(): + pytest.fail(f"GQSP spectrum finding failed unexpectedly: {str(e)}") + + def _validate_qasm_with_pyqasm(self, qasm_string): + """Helper method to validate QASM using pyqasm.""" + if not PYQASM_AVAILABLE: + return True, "pyqasm not available - skipping validation" + + try: + # Try to parse with pyqasm + program = pq.loads(qasm_string) + program.validate() + return True, None + except Exception as e: + return False, str(e) + +class TestTrotterAlgorithm: + """Test Trotter decomposition algorithm.""" + + def setup_method(self): + """Set up test environment.""" + self.test_hamiltonians = create_test_hamiltonians(reg_size=3) + self.test_qubits = [*range(3)] + + 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 + + for (name1, ham1), (name2, ham2) in ham_pairs: + builder = QasmBuilder(3) + std = builder.import_library(std_gates) + trotter = builder.import_library(Trotter) + + 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) + + full_qasm = builder.build() + # print(program) + # Validate structure + 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) + # assert is_valid, f"Trotter failed for {name1}+{name2}: {error_msg}" + + except Exception as e: + pytest.fail(f"Trotter basic test failed for {name1}+{name2}: {str(e)}") + + def test_trotter_different_depths(self): + """Test Trotter with various recursion depths.""" + depths = [1, 2, 3] + ham1, ham2 = list(self.test_hamiltonians.values())[:2] + + for depth in depths: + builder = QasmBuilder(3) + std = builder.import_library(std_gates) + trotter = builder.import_library(Trotter) + + try: + trotter.trot_suz(self.test_qubits, "0.3", ham1, ham2, depth=depth) + std.measure(self.test_qubits,self.test_qubits) + + # full_qasm = builder.build() + + # Validate QASM + # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # assert is_valid, f"Trotter depth {depth} invalid: {error_msg}" + + except Exception as e: + pytest.fail(f"Trotter depth {depth} test failed: {str(e)}") + + def test_trotter_multi_hamiltonian(self): + """Test Trotter with multiple Hamiltonians.""" + hamiltonians = list(self.test_hamiltonians.values())[:3] # Test with 3 Hamiltonians + + builder = QasmBuilder(3) + std = builder.import_library(std_gates) + trotter = builder.import_library(Trotter) + + 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) + + # full_qasm = builder.build() + + # Validate QASM + # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # assert is_valid, f"Multi-Hamiltonian Trotter invalid: {error_msg}" + + except Exception as e: + pytest.fail(f"Multi-Hamiltonian Trotter test failed: {str(e)}") + + def test_trotter_linear_decomposition(self): + """Test linear (first-order) Trotter decomposition.""" + hamiltonians = list(self.test_hamiltonians.values())[:2] + + builder = QasmBuilder(3) + std = builder.import_library(std_gates) + trotter = builder.import_library(Trotter) + + try: + # Test linear Trotter + trotter.trot_linear(self.test_qubits, "0.2", hamiltonians, steps=4) + std.measure(self.test_qubits,self.test_qubits) + + # full_qasm = builder.build() + + # Validate QASM + # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # assert is_valid, f"Linear Trotter invalid: {error_msg}" + + except Exception as e: + pytest.fail(f"Linear Trotter test failed: {str(e)}") + + def test_trotter_time_parameters(self): + """Test Trotter with different time parameter formats.""" + time_params = ["0.1", "pi/4", "2.5", "0.01"] + ham1, ham2 = list(self.test_hamiltonians.values())[:2] + + for time_param in time_params: + builder = QasmBuilder(3) + std = builder.import_library(std_gates) + trotter = builder.import_library(Trotter) + + try: + trotter.trot_suz(self.test_qubits, time_param, ham1, ham2, depth=1) + std.measure(self.test_qubits,self.test_qubits) + + # full_qasm = builder.build() + + # Basic validation + # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # 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)}") + + def _validate_qasm_with_pyqasm(self, qasm_string): + """Helper method to validate QASM using pyqasm.""" + if not PYQASM_AVAILABLE: + return True, "pyqasm not available - skipping validation" + + try: + # Try to parse with pyqasm + program = pq.loads(qasm_string) + program.validate() + return True, None + 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)] + + 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 + ] + + for i, matrix in enumerate(test_matrices): + builder = QasmBuilder(3) + std = builder.import_library(std_gates) + prep_sel = builder.import_library(PrepSelLibrary) + + # std.qubit(6) # Need extra qubits for ancillas + # std.bit(6) + + 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) + + full_qasm = builder.build() + + # Should contain prep-select elements + assert 'PS_' in full_qasm or 'prep' in full_qasm.lower() + + # Validate QASM + # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # assert is_valid, f"PrepSel matrix {i} invalid: {error_msg}" + + except Exception as e: + pytest.fail(f"PrepSel matrix test {i} failed: {str(e)}") + + def test_prep_select_with_operator_chain(self): + """Test prep-select with pre-computed operator chain.""" + # Test with Pauli string representations + 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)] + ] + + for i, chain in enumerate(test_chains): + builder = QasmBuilder(3) + std = builder.import_library(std_gates) + prep_sel = builder.import_library(PrepSelLibrary) + + # std.qubit(8) # Extra qubits for larger chains + # std.bit(8) + + try: + prep_sel.prep_select(self.test_qubits, chain) + std.measure(self.test_qubits,self.test_qubits) + + # full_qasm = builder.build() + + # Validate QASM + # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # assert is_valid, f"PrepSel chain {i} invalid: {error_msg}" + + except Exception as e: + pytest.fail(f"PrepSel operator chain test {i} failed: {str(e)}") + + def test_preparation_library(self): + """Test standalone Preparation library.""" + # Test with different probability distributions + test_distributions = [ + [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] + ] + + for i, dist in enumerate(test_distributions): + builder = QasmBuilder(3) + 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)))))] + + # std.qubit(len(qubits) + 1) + # std.bit(len(qubits) + 1) + + try: + prep.prep(qubits, dist) + 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() + + # Validate QASM + # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # assert is_valid, f"Preparation dist {i} invalid: {error_msg}" + + except Exception as e: + pytest.fail(f"Preparation test {i} failed: {str(e)}") + + def test_selection_library(self): + """Test standalone Selection library.""" + operators = ["X", "Y", "Z", "XX"] + mapping = {0: 0, 1: 1, 2: 2, 3: 3} + + builder = QasmBuilder(6) + std = builder.import_library(std_gates) + select = builder.import_library(Select) + + # std.qubit(6) + # std.bit(6) + + try: + 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) + + full_qasm = builder.build() + + # Should contain selection elements + assert 'SEL_' in full_qasm or 'select' in full_qasm.lower() + + # Validate QASM + # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # assert is_valid, f"Selection invalid: {error_msg}" + + except Exception as e: + pytest.fail(f"Selection test failed: {str(e)}") + + def test_pauli_operator_library(self): + """Test Pauli operator string processing.""" + + pauli_strings = ["X", "Y", "Z", "XX", "XY", "XZ", "XYZI", "IXYZ"] + + for pauli_str in pauli_strings: + builder = QasmBuilder(3) + std = builder.import_library(std_gates) + pauli = builder.import_library(PauliOperator) + + 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) + + 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']) + + # Validate QASM + # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # assert is_valid, f"Pauli {pauli_str} invalid: {error_msg}" + + except Exception as e: + pytest.fail(f"Pauli operator {pauli_str} test failed: {str(e)}") + + def _validate_qasm_with_pyqasm(self, qasm_string): + """Helper method to validate QASM using pyqasm.""" + if not PYQASM_AVAILABLE: + return True, "pyqasm not available - skipping validation" + try: + program = pq.loads(qasm_string) + program.validate() + return True, None + 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)] + + def test_gqsp_with_all_hamiltonians(self): + """Test GQSP works with all Hamiltonian types.""" + phases = [0.1, 0.2, 0.3] # depth=1 + + for ham_name, hamiltonian in self.test_hamiltonians.items(): + 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 controlled(self,*args,**kwargs): + super().controlled(.1,*args,**kwargs) + + try: + gqsp.GQSP(self.test_qubits, phases, Ham, depth=1) + std.measure(self.test_qubits,self.test_qubits) + + # full_qasm = builder.build() + + # Validate QASM + # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # assert is_valid, f"GQSP+{ham_name} invalid: {error_msg}" + + except Exception as e: + pytest.fail(f"GQSP integration with {ham_name} failed: {str(e)}") + + def test_trotter_with_all_hamiltonian_pairs(self): + """Test Trotter works with all Hamiltonian pair combinations.""" + ham_items = list(self.test_hamiltonians.items()) + + for i in range(len(ham_items) - 1): + name1, ham1 = ham_items[i] + name2, ham2 = ham_items[i + 1] + + builder = QasmBuilder(3) + std = builder.import_library(std_gates) + trotter = builder.import_library(Trotter) + + try: + trotter.trot_suz(self.test_qubits, "0.1", ham1, ham2, depth=1) + std.measure(self.test_qubits,self.test_qubits) + + # full_qasm = builder.build() + + # Validate QASM + # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # 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)}") + + def test_algorithm_parameter_edge_cases(self): + """Test algorithms with edge case parameters.""" + + # Test very small times + small_times = ["1e-6", "0.001", "0.01"] + for time in small_times: + builder = QasmBuilder(3) + std = builder.import_library(std_gates) + trotter = builder.import_library(Trotter) + + 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) + + full_qasm = builder.build() + + # Should still be valid QASM + is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # assert is_valid, f"Small time {time} invalid: {error_msg}" + + except Exception as e: + # Very small times might cause issues - that's OK + if "time" not in str(e).lower() and "parameter" not in str(e).lower(): + pytest.fail(f"Unexpected error with small time {time}: {str(e)}") + + def test_algorithm_qubit_scaling(self): + """Test algorithms with different qubit counts.""" + qubit_counts = [2, 3, 4, 5] + + 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)] + + # Test GQSP scaling + builder = QasmBuilder(3) + std = builder.import_library(std_gates) + gqsp = builder.import_library(GQSP) + + # std.qubit(n_qubits + 1) # +1 for ancilla + # std.bit(n_qubits + 1) + + 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 controlled(self,*args,**kwargs): + super().controlled(.1,*args,**kwargs) + gqsp.GQSP(qubits, phases, Ham, depth=1) + std.measure(self.test_qubits,self.test_qubits) + + # full_qasm = builder.build() + + # Validate QASM + # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # assert is_valid, f"GQSP {n_qubits}-qubit scaling invalid: {error_msg}" + + except Exception as e: + pytest.fail(f"GQSP {n_qubits}-qubit scaling failed: {str(e)}") + + def _validate_qasm_with_pyqasm(self, qasm_string): + """Helper method to validate QASM using pyqasm.""" + if not PYQASM_AVAILABLE: + return True, "pyqasm not available - skipping validation" + + try: + # Try to parse with pyqasm + program = pq.loads(qasm_string) + program.validate() + return True, None + except Exception as e: + return False, str(e) + +class TestAlgorithmStressTests: + """Stress tests for algorithm robustness.""" + + def test_complex_algorithm_combinations(self): + """Test combining multiple algorithms in sequence.""" + hamiltonians = create_test_hamiltonians(reg_size=3) + ham_list = list(hamiltonians.values())[:2] + + builder = QasmBuilder(8) + qubits = [*range(8)] + std = builder.import_library(std_gates) + gqsp = builder.import_library(GQSP) + trotter = builder.import_library(Trotter) + prep_sel = builder.import_library(PrepSelLibrary) + + class H1(ham_list[0]): + def apply(self,*args,**kwargs): + super().apply(.1,*args,**kwargs) + + def controlled(self,*args,**kwargs): + super().controlled(.1,*args,**kwargs) + + try: + # Apply Trotter decomposition + trotter.trot_suz(qubits[:3], "0.1", ham_list[0], ham_list[1], depth=1) + + # Apply GQSP + gqsp.GQSP(qubits[3:6], [0.1, 0.2, 0.3], H1, depth=1) + + # Apply prep-select + test_matrix = np.array([[1, 0], [0, -1]]) + prep_sel.prep_select(qubits[6:], test_matrix) + + std.measure(qubits,qubits) + + # full_qasm = builder.build() + + # Validate combined QASM + # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # assert is_valid, f"Combined algorithms invalid: {error_msg}" + + except Exception as e: + pytest.fail(f"Complex algorithm combination failed: {str(e)}") + + def test_resource_intensive_algorithms(self): + """Test algorithms with resource-intensive parameters.""" + hamiltonians = create_test_hamiltonians(reg_size=2) # Keep small for speed + hamiltonian = list(hamiltonians.values())[0] + + # Test higher depth GQSP (but not too high for test speed) + builder = QasmBuilder(3) + reg = [*range(3)] + std = builder.import_library(std_gates) + gqsp = builder.import_library(GQSP) + + class H1(hamiltonian): + def apply(self,*args,**kwargs): + super().apply(.1,*args,**kwargs) + + def controlled(self,*args,**kwargs): + super().controlled(.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) + + # full_qasm = builder.build() + + # Should still be valid + # is_valid, error_msg = self._validate_qasm_with_pyqasm(full_qasm) + # assert is_valid, f"Resource-intensive GQSP invalid: {error_msg}" + + except Exception as e: + pytest.fail(f"Resource-intensive algorithm test failed: {str(e)}") + + def _validate_qasm_with_pyqasm(self, qasm_string): + """Helper method to validate QASM using pyqasm.""" + if not PYQASM_AVAILABLE: + return True, "pyqasm not available - skipping validation" + try: + program = pq.loads(qasm_string) + program.validate() + return True, None + except Exception as e: + return False, str(e) + + +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) + + 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)) + ] + + sys = GateBuilder() + std = sys.import_library(std_gates) + std.call_space = " {}" + + ind = dict(zip(range(len(self.reg)), qargs)) + ind.pop(2) + + # Gate definition + std.begin_gate(self.name, qargs) + std.x(qargs[2]) + std.controlled_op("z", (qargs[2], list(ind.values())), n=len(self.reg) - 1) + std.x(qargs[2]) + std.end_gate() + + # Collect gate definitions and imports + p, i, d = sys.build() + for imps in i: + if imps not in self.gate_import: + self.gate_import.append(imps) + + for defs in d: + if defs[0] not in self.gate_defs: + self.gate_defs[defs[0]] = defs[1] + + self.gate_defs[self.name] = p + self.gate_ref.append(self.name) + + def apply(self, qubits): + """Apply the custom gate to a set of qubits.""" + self.call_gate(self.name, qubits[-1], qubits[:-1]) + + def controlled(self, qubits, control): + """Controlled version of the custom gate.""" + self.controlled_op(self.name, (qubits[-1], [control] + qubits[:-1])) + + def test_full_algorithm_builds(self): + """Ensure full algorithm builds and pq.loads() runs.""" + + # Build algorithm with 3 qubits + alg = QasmBuilder(3, 0, version="3") + reg = list(range(3)) + + # Import standard gates and Grover + program = alg.import_library(std_gates) + ampl = alg.import_library(AALibrary) + + # Add Grover with custom gate + ampl.grover(TestAmplitude.Za, reg, 3) + + # Build OpenQASM code + prog = alg.build() + + # Parse into pq object + res = pq.loads(prog) + + # Basic assertion + assert res is not None + + # 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 new file mode 100644 index 0000000..f81fb85 --- /dev/null +++ b/tests/test_builder_statics.py @@ -0,0 +1,207 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +""" +Test Algorithms - Semantic Validation + +This module tests the implementations of several semi static algorithms which +dont accept a arbitrary oracle/hamiltonian. +Tests include: +1. Grovers +2. Toeplitz +3. HHL +""" + +# TODO: remove unused variable lint once namespace (ie imports and defs) tests are implemented +# ruff: noqa: F841 +# pylint: disable=C0303,unused-variable,missing-class-docstring +# pylint: disable=missing-function-docstring,too-many-locals,duplicate-code +import string + +import numpy as np +import pyqasm as pq + +from qbraid_algorithms.amplitude_amplification import AALibrary +from qbraid_algorithms.embedding import Toeplitz + +#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)) + ] + + sys = GateBuilder() + std = sys.import_library(std_gates) + std.call_space = " {}" + + ind = dict(zip(range(len(self.reg)), qargs)) + ind.pop(2) + + # Gate definition + std.begin_gate(self.name, qargs) + std.x(qargs[2]) + std.controlled_op("z", (qargs[2], list(ind.values())), n=len(self.reg) - 1) + std.x(qargs[2]) + std.end_gate() + + # Collect gate definitions and imports + p, i, d = sys.build() + for imps in i: + if imps not in self.gate_import: + self.gate_import.append(imps) + + for defs in d: + if defs[0] not in self.gate_defs: + self.gate_defs[defs[0]] = defs[1] + + self.gate_defs[self.name] = p + self.gate_ref.append(self.name) + + def apply(self, qubits): + """Apply the custom gate to a set of qubits.""" + self.call_gate(self.name, qubits[-1], qubits[:-1]) + + 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.""" + + # Build algorithm with 3 qubits + alg = QasmBuilder(3, 0, version="3") + reg = list(range(3)) + + # Import standard gates and Grover + program = alg.import_library(std_gates) + ampl = alg.import_library(AALibrary) + + # Add Grover with custom gate + ampl.grover(Za, reg, 3) + + # Build OpenQASM code + prog = alg.build() + + # Parse into pq object + res = pq.loads(prog) + + # Basic assertion + assert res is not None + + # Validation (commented for now) + # res.validate() + +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 + + # Build algorithm with 3 qubits + alg = QasmBuilder(3, 0, version="3") + reg = list(range(3)) + + # Import standard gates and Toeplitz + program = alg.import_library(std_gates) + toeplitz_lib = alg.import_library(Toeplitz) + + # Add Toeplitz operator + toeplitz_lib.real_toeplitz(reg,f) + + # Build OpenQASM code + prog = alg.build() + + # Parse into pq object + res = pq.loads(prog) + + # Basic assertion + assert res is not None + + # Validation (commented for now) + # res.validate() + +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) + f = np.sin(t) / t + + # Build algorithm with 3 qubits + alg = QasmBuilder(3, 0, version="3") + reg = list(range(3)) + + # Import standard gates and Rodeo + program = alg.import_library(std_gates) + rodeo_lib = alg.import_library(RodeoLibrary) + + # Add Rodeo operator + rodeo_lib.rodeo_mcm(reg, 1, 3, Za) + # make sure it doesn't redefine + rodeo_lib.rodeo_mcm(reg, 1, 3, Za) + + # Build OpenQASM code + prog = alg.build() + + # Parse into pq object + res = pq.loads(prog) + + # Basic assertion + assert res is not None + + # Validation (commented for now) + # res.validate() + + def test_ancilla_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 + + # Build algorithm with 3 qubits + alg = QasmBuilder(3, 0, version="3") + reg = list(range(3)) + + # Import standard gates and Rodeo + program = alg.import_library(std_gates) + rodeo_lib = alg.import_library(RodeoLibrary) + + # Add Rodeo operator + rodeo_lib.rodeo(reg, 1, 3, Za) + # make sure it doesn't redefine + rodeo_lib.rodeo(reg, 1, 3, Za) + + # Build OpenQASM code + prog = alg.build() + + # Parse into pq object + res = pq.loads(prog) + + # Basic assertion + assert res is not None + + # Validation (commented for now) + # res.validate() diff --git a/tests/test_qasmbuilder.py b/tests/test_qasmbuilder.py new file mode 100644 index 0000000..3320966 --- /dev/null +++ b/tests/test_qasmbuilder.py @@ -0,0 +1,405 @@ +# Copyright 2025 qBraid +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +""" +Test QASM Builder - Semantic Tests + +This module tests the QASMBuilder functionality with exact string matching +to ensure stability and correctness of QASM code generation. + +Tests include: +1. Basic gate operations and exact QASM string validation +2. Parameterized gate definitions +3. Subroutine generation +4. Hamiltonian interface validation using pyqasm +""" + +# ruff: noqa: F841 +# pylint: disable=broad-exception-caught,missing-class-docstring,invalid-name,missing-function-docstring, attribute-defined-outside-init +# TODO: remove unused variable lint once namespace (ie imports and defs) tests are implemented +# pylint: disable=unused-variable +import os +import tempfile + +import pytest + +from qbraid_algorithms.evolution import create_test_hamiltonians + +# Import your modules (adjust paths as needed) +from qbraid_algorithms.qtran import GateBuilder, QasmBuilder, std_gates + +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) + std = builder.import_library(std_gates) + qubits = [*range(n)] + + # Apply some basic gates + std.h(qubits[0]) + std.cnot(qubits[0], qubits[1]) + std.x(qubits[2]) + std.measure(qubits,qubits) + + program = builder.build() + + # Expected QASM output (adjust based on your actual format) + expected_lines = [ + "OPENQASM 3;", + "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}];" + ] + + # 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()] + 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) + + stable = True + try: + prog = pq.loads(program) + prog.validate() + except Exception as e: + print("validation failed with error:", e) + stable = False + assert stable + + def test_parameterized_gate_definition(self): + """Test exact QASM output for parameterized gate definitions.""" + builder = GateBuilder() + std = builder.import_library(std_gates) + + gate_name = "test_rotation" + qargs = ['a', 'b'] + params = ['theta', 'phi'] + + std.begin_gate(gate_name, qargs, params=params) + std.rx(params[0], qargs[0]) + std.ry(params[1], qargs[1]) + std.cnot(qargs[0], qargs[1]) + std.end_gate() + + program, imports, defs = builder.build() + + # Validate gate definition exists + assert gate_name in program + gate_def = program + + # Check gate definition structure + assert isinstance(gate_def, str) + assert gate_name in gate_def + assert all(param in gate_def for param in params) + 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 + + def test_subroutine_generation(self): + """Test QASM subroutine generation.""" + builder = GateBuilder() + std = builder.import_library(std_gates) + + subroutine_name = "test_subroutine" + params = ['qubit[3] qb', 'float time', 'int depth'] + + std.begin_subroutine(subroutine_name, params) + std.begin_if("depth > 0") + std.ry("time", "qb[0]") + std.call_subroutine(subroutine_name, ["qb", "time/2", "depth-1"]) + std.end_if() + std.end_subroutine() + + program, imports, defs = builder.build() + + # Validate subroutine structure + assert subroutine_name in program + subroutine_def = program + + 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): + """Test QASM conditional statements and loops.""" + builder = GateBuilder() + std = builder.import_library(std_gates) + + # Test conditional + std.begin_if("c[0] == 1") + std.x("q[1]") + std.end_if() + + # Test for loop + std.begin_loop(3) + std.h("q[i]") + std.end_loop() + + program, imports, defs = builder.build() + + # Check for control flow structures + assert 'if' in program + assert 'for' in program + assert 'h' in program + + def test_ancilla_claiming(self): + sys = QasmBuilder(3) + std = sys.import_library(std_gates) + anc_q = sys.claim_qubits(5) + anc_c = sys.claim_clbits(5) + assert len(anc_q) == 5 + assert len(anc_c) == 5 + std.x(0) + program = sys.build() + assert "qubit[8]" in program + assert "bit[8]" in program + + def validate_qasm_with_pyqasm(self, qasm_string): + """Helper method to validate QASM using pyqasm.""" + if not PYQASM_AVAILABLE: + pytest.skip("pyqasm not available for validation") + + try: + # Create temporary file for pyqasm validation + with tempfile.NamedTemporaryFile(mode='w', suffix='.qasm', delete=False) as f: + f.write(qasm_string) + f.flush() + temp_path = f.name + + # Validate using pyqasm + try: + program = pq.loads(qasm_string) + program.validate() + validation_result = True + error_msg = None + except Exception as e: + validation_result = False + error_msg = str(e) + finally: + # Clean up temporary file + os.unlink(temp_path) + + return validation_result, error_msg + + except Exception as e: + return False, f"Validation setup failed: {str(e)}" + +class TestHamiltonianInterface: + """Test Hamiltonian interface for correct QASM generation.""" + + def setup_method(self): + """Set up test Hamiltonians.""" + self.test_hamiltonians = create_test_hamiltonians(reg_size=4) + self.test_qubits = [*range(4)] + + def test_hamiltonian_initialization(self): + """Test that all Hamiltonians initialize correctly.""" + for name, ham in self.test_hamiltonians.items(): + # 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') + + # Check name is reasonable + assert isinstance(H.name, str) + assert len(H.name) > 0 + + # Check gate definitions were created + # assert len(ham.gate_defs) > 0 + # assert ham.name in ham.gate_ref + + def test_hamiltonian_apply_method(self): + """Test that apply method generates valid QASM.""" + for name, ham in self.test_hamiltonians.items(): + # Create fresh builder for each test + builder = QasmBuilder(len(self.test_qubits)) + std = builder.import_library(std_gates) + ham_lib = builder.import_library(ham) + + # Test apply method + try: + ham_lib.apply("0.5", self.test_qubits) + std.measure(self.test_qubits,self.test_qubits) + + program= builder.build() + + # Validate basic structure + assert isinstance(program, str) + assert len(program) > 0 + # assert ham.name in defs or any(ham.name in gate_def for gate_def in defs.values()) + + # 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}" + + except Exception as e: + pytest.fail(f"Failed to apply Hamiltonian {name}: {str(e)}") + + def test_hamiltonian_controlled_method(self): + """Test that controlled method generates valid QASM.""" + builder = GateBuilder() + + for name, ham in self.test_hamiltonians.items(): + # Create fresh builder for each test + builder = QasmBuilder(len(self.test_qubits)) + std = builder.import_library(std_gates) + ham_lib = builder.import_library(ham) + + anc_q = builder.claim_qubits(1) # Need extra qubit for control + anc_c = builder.claim_clbits(1) + + # Test controlled method + try: + control_qubit = anc_q[0] + 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) + + program = builder.build() + # Validate structure + assert isinstance(program, str) + assert len(program) > 0 + # Test QASM validity + 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}" + except Exception as e: + pytest.fail(f"Failed to apply controlled Hamiltonian {name}: {str(e)}") + + def test_hamiltonian_parameter_types(self): + """Test Hamiltonians with different parameter types.""" + test_times = ["0.1", "pi/4", "theta", "2*pi/3"] + + for time_param in test_times: + for name, ham in self.test_hamiltonians.items(): + builder = QasmBuilder(len(self.test_qubits)) + std = builder.import_library(std_gates) + ham_lib = builder.import_library(ham) + try: + ham_lib.apply(time_param, self.test_qubits) + std.measure(self.test_qubits,self.test_qubits) + + program = builder.build() + + # Check that parameter appears in the program + full_qasm = program + + # Basic validation - parameter should appear somewhere + 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) + 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}") + + def _validate_qasm_with_pyqasm(self, qasm_string): + """Helper method to validate QASM using pyqasm.""" + if not PYQASM_AVAILABLE: + return True, "pyqasm not available - skipping validation" + + try: + # Try to parse with pyqasm + program = pq.loads(qasm_string) + # TODO: re-enable validation once pyqasm controlled operations are supported + # program.validate() + return True, None + 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.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 + first_result = results[0] + for i, result in enumerate(results[1:], 1): + assert result[0] == first_result[0], f"Program differs at run {i}" + assert result[1] == first_result[1], f"Imports differ at run {i}" + assert result[2] == first_result[2], f"Definitions differ at run {i}" + + def test_hamiltonian_stability(self): + """Test that Hamiltonian QASM generation is stable.""" + hamiltonians = create_test_hamiltonians(reg_size=3) + reg= [*range(3)] + # Test each Hamiltonian multiple times + for name, ham_class in hamiltonians.items(): + results = [] + + for _ in range(3): + # 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) + + 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}" + # Gate definitions should be the same + assert result[2] == first_result[2], f"Hamiltonian {name} definitions differ at run {i}" + +if __name__ == "__main__": + # Run tests if executed directly + pytest.main([__file__, "-v"])