From 647a7b4ccda6a444e68dd3c84e025ec0d705fcb9 Mon Sep 17 00:00:00 2001 From: seitzdom <39090287+dominikandreasseitz@users.noreply.github.com> Date: Mon, 30 Oct 2023 12:08:41 +0100 Subject: [PATCH] Release of pyqtorch v1.0.0: Adjoint Differentiation Support (#122) Full remake of pyqtorch using a slim version of the former modules API --- README.md | 14 +- docs/QAOA.ipynb | 239 ------- docs/apply_gate.md | 1 - docs/circuit.md | 1 - docs/deprecated/QAOA.ipynb | 249 ------- docs/deprecated/bench.py | 451 ------------- docs/deprecated/fit_function.ipynb | 489 -------------- docs/deprecated/getting_started.ipynb | 490 -------------- docs/deprecated/ham_evol_comparison.ipynb | 612 ----------------- docs/deprecated/state_evolution.ipynb | 264 -------- docs/docsutils.py | 11 + docs/essentials.ipynb | 259 -------- docs/fit_function.ipynb | 485 -------------- docs/gate_composition.ipynb | 129 ---- docs/hamevo.md | 115 ---- docs/index.md | 251 ++++++- docs/matrices.md | 1 - docs/parametric.md | 1 - docs/primitive.md | 1 - docs/utils.md | 1 - mkdocs.yml | 20 +- pyproject.toml | 6 +- pyqtorch/__init__.py | 56 +- pyqtorch/abstract.py | 57 ++ pyqtorch/adjoint.py | 46 ++ pyqtorch/analog.py | 63 ++ pyqtorch/ansatz.py | 100 --- pyqtorch/apply.py | 39 ++ pyqtorch/circuit.py | 88 +++ pyqtorch/core/__init__.py | 13 - pyqtorch/core/batched_operation.py | 603 ----------------- pyqtorch/core/circuit.py | 47 -- pyqtorch/core/measurement.py | 32 - pyqtorch/core/operation.py | 624 ------------------ pyqtorch/core/utils.py | 153 ----- pyqtorch/embedding.py | 29 - pyqtorch/matrices.py | 159 +---- pyqtorch/matrices_sparse.py | 194 ------ pyqtorch/modules/__init__.py | 38 -- pyqtorch/modules/abstract.py | 55 -- pyqtorch/modules/circuit.py | 196 ------ pyqtorch/modules/hamevo.py | 345 ---------- pyqtorch/modules/ops.py | 53 -- pyqtorch/modules/parametric.py | 483 -------------- pyqtorch/modules/primitive.py | 493 -------------- pyqtorch/modules/utils.py | 204 ------ pyqtorch/parametric.py | 251 +++++++ pyqtorch/primitive.py | 135 ++++ pyqtorch/utils.py | 114 ++++ tests/conftest.py | 100 --- .../{test_module_hamevo.py => test_analog.py} | 115 +--- tests/test_batched_operations.py | 40 -- tests/test_circuit.py | 80 +++ tests/test_digital.py | 214 ++++++ tests/test_modules.py | 354 ---------- tests/test_notebooks.py | 65 -- tests/test_operations.py | 179 ----- tests/test_operations_hamevo.py | 186 ------ 58 files changed, 1440 insertions(+), 8653 deletions(-) delete mode 100644 docs/QAOA.ipynb delete mode 100644 docs/apply_gate.md delete mode 100644 docs/circuit.md delete mode 100644 docs/deprecated/QAOA.ipynb delete mode 100644 docs/deprecated/bench.py delete mode 100644 docs/deprecated/fit_function.ipynb delete mode 100644 docs/deprecated/getting_started.ipynb delete mode 100644 docs/deprecated/ham_evol_comparison.ipynb delete mode 100644 docs/deprecated/state_evolution.ipynb create mode 100644 docs/docsutils.py delete mode 100644 docs/essentials.ipynb delete mode 100644 docs/fit_function.ipynb delete mode 100644 docs/gate_composition.ipynb delete mode 100644 docs/hamevo.md delete mode 100644 docs/matrices.md delete mode 100644 docs/parametric.md delete mode 100644 docs/primitive.md delete mode 100644 docs/utils.md create mode 100644 pyqtorch/abstract.py create mode 100644 pyqtorch/adjoint.py create mode 100644 pyqtorch/analog.py delete mode 100644 pyqtorch/ansatz.py create mode 100644 pyqtorch/apply.py create mode 100644 pyqtorch/circuit.py delete mode 100644 pyqtorch/core/__init__.py delete mode 100644 pyqtorch/core/batched_operation.py delete mode 100644 pyqtorch/core/circuit.py delete mode 100644 pyqtorch/core/measurement.py delete mode 100644 pyqtorch/core/operation.py delete mode 100644 pyqtorch/core/utils.py delete mode 100644 pyqtorch/embedding.py delete mode 100644 pyqtorch/matrices_sparse.py delete mode 100644 pyqtorch/modules/__init__.py delete mode 100644 pyqtorch/modules/abstract.py delete mode 100644 pyqtorch/modules/circuit.py delete mode 100644 pyqtorch/modules/hamevo.py delete mode 100644 pyqtorch/modules/ops.py delete mode 100644 pyqtorch/modules/parametric.py delete mode 100644 pyqtorch/modules/primitive.py delete mode 100644 pyqtorch/modules/utils.py create mode 100644 pyqtorch/parametric.py create mode 100644 pyqtorch/primitive.py create mode 100644 pyqtorch/utils.py delete mode 100644 tests/conftest.py rename tests/{test_module_hamevo.py => test_analog.py} (56%) delete mode 100644 tests/test_batched_operations.py create mode 100644 tests/test_circuit.py create mode 100644 tests/test_digital.py delete mode 100644 tests/test_modules.py delete mode 100644 tests/test_notebooks.py delete mode 100644 tests/test_operations.py delete mode 100644 tests/test_operations_hamevo.py diff --git a/README.md b/README.md index 2adcca12..0d7961d8 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,12 @@ python -m hatch shell python -m hatch run python my_script.py ``` -!!! warning - `hatch` will not combine nicely with other environment managers such Conda. If you want to use Conda, - install it from source using `pip`: - - ```bash - # within the Conda environment - python -m pip install -e . - ``` +Please note that `hatch` will not combine nicely with other environment managers such Conda. If you want to use Conda, install `pyqtorch` from source using `pip`: + +```bash +# within the Conda environment +python -m pip install -e . +``` ## Contributing diff --git a/docs/QAOA.ipynb b/docs/QAOA.ipynb deleted file mode 100644 index 3e1bd3cf..00000000 --- a/docs/QAOA.ipynb +++ /dev/null @@ -1,239 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 45, - "id": "a6f294e0", - "metadata": {}, - "outputs": [], - "source": [ - "import torch\n", - "import torch.nn as nn\n", - "import torch.nn.functional as F\n", - "import torch.nn.init as init\n", - "\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import networkx as nx\n", - "\n", - "import pyqtorch.modules as pyq" - ] - }, - { - "cell_type": "markdown", - "id": "eb4585dc", - "metadata": {}, - "source": [ - "## Solve MIS for QAOA" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "3c4e17c3", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "np.random.seed(0)\n", - "n_nodes = 10\n", - "\n", - "graph = nx.gnp_random_graph(n_nodes, .25, seed=42)\n", - "nx.draw(graph, with_labels=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "a1b223ab", - "metadata": {}, - "outputs": [], - "source": [ - "from pyqtorch.matrices import generate_ising_from_graph, sum_N\n", - "\n", - "ising_matrix = generate_ising_from_graph(graph, type_ising='N')\n", - "ising_cost = 1.2*ising_matrix - sum_N(n_nodes)\n", - "\n", - "ising_matrix = ising_matrix.reshape([2] * n_nodes + [1])\n", - "ising_cost = ising_cost.reshape([2] * n_nodes + [1])\n", - "\n", - "\n", - "class MIS(nn.Module):\n", - " def __init__(self, n_qubits, n_layers):\n", - " super().__init__()\n", - " self.n_qubits = n_qubits\n", - " self.gamma = nn.Parameter(torch.empty(n_layers,))\n", - " self.ansatz = pyq.VariationalLayer(n_qubits, pyq.RX)\n", - " self.reset_parameters()\n", - " \n", - " def reset_parameters(self):\n", - " init.uniform_(self.gamma, -2 * np.pi, 2 * np.pi)\n", - " self.ansatz.reset_parameters()\n", - " \n", - " def forward(self, return_cost=False):\n", - " state = pyq.uniform_state(self.n_qubits)\n", - " for g in self.gamma:\n", - " state = state * torch.exp(-1j * g * ising_matrix)\n", - " state = self.ansatz(state)\n", - " if return_cost:\n", - " return torch.real(torch.sum(torch.abs(state)**2 * ising_cost))\n", - " else:\n", - " state = state.reshape((2**self.n_qubits,))\n", - " return torch.abs(state)**2" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "c84f30d0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Epoch 0 | Loss 1.4900727366440991\n", - "Epoch 50 | Loss 1.1838491896514012\n" - ] - } - ], - "source": [ - "model = MIS(n_nodes, 20)\n", - "\n", - "optimizer = torch.optim.Adam(model.parameters(), lr=.02)\n", - "epochs = 100\n", - "\n", - "\n", - "for epoch in range(epochs):\n", - " optimizer.zero_grad()\n", - " loss = model(True)\n", - " loss.backward()\n", - " optimizer.step()\n", - " if epoch%50 == 0:\n", - " print(f\"Epoch {epoch} | Loss {loss}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "id": "4f7a6f49", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'1111101000'" - ] - }, - "execution_count": 49, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "prob = model()\n", - "mis = torch.argmax(prob)\n", - "format(mis, '010b')" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "id": "7bf6d8b0", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "tensor(1000)" - ] - }, - "execution_count": 50, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mis" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "id": "bfd64d73", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'1'" - ] - }, - "execution_count": 51, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "\"{0:b}\".format(1)" - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "id": "15ce4608", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "tensor(6.+0.j, dtype=torch.complex128)" - ] - }, - "execution_count": 52, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sum_N(n_nodes)[mis]" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3.10.4 ('pynn')", - "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.10.4" - }, - "vscode": { - "interpreter": { - "hash": "6f77e4d31a6b2b6cebe1094e9c25b1f29bee03e4b42142e04de4cbf5445e2748" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/apply_gate.md b/docs/apply_gate.md deleted file mode 100644 index 86d0ed95..00000000 --- a/docs/apply_gate.md +++ /dev/null @@ -1 +0,0 @@ -::: pyqtorch.modules.ops diff --git a/docs/circuit.md b/docs/circuit.md deleted file mode 100644 index 9c3e7463..00000000 --- a/docs/circuit.md +++ /dev/null @@ -1 +0,0 @@ -::: pyqtorch.modules.circuit diff --git a/docs/deprecated/QAOA.ipynb b/docs/deprecated/QAOA.ipynb deleted file mode 100644 index 3ce51800..00000000 --- a/docs/deprecated/QAOA.ipynb +++ /dev/null @@ -1,249 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 23, - "id": "a6f294e0", - "metadata": {}, - "outputs": [], - "source": [ - "import torch\n", - "import torch.nn as nn\n", - "import torch.nn.functional as F\n", - "import torch.nn.init as init\n", - "\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import networkx as nx\n", - "\n", - "from pyqtorch.core.circuit import QuantumCircuit\n", - "from pyqtorch.ansatz import AlternateLayerAnsatz\n", - "from pyqtorch.embedding import SingleLayerEncoding\n", - "from pyqtorch.core.operation import Z, RX" - ] - }, - { - "cell_type": "markdown", - "id": "eb4585dc", - "metadata": {}, - "source": [ - "## Solve MIS for QAOA" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "3c4e17c3", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "np.random.seed(0)\n", - "n_nodes = 5\n", - "\n", - "graph = nx.gnp_random_graph(n_nodes, .25, seed=42)\n", - "nx.draw(graph, with_labels=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "a1b223ab", - "metadata": {}, - "outputs": [], - "source": [ - "from pyqtorch.matrices import generate_ising_from_graph, sum_N\n", - "ising_matrix = generate_ising_from_graph(graph, type_ising='N')\n", - "ising_cost = 1.2*ising_matrix - sum_N(n_nodes)\n", - "\n", - "ising_matrix = ising_matrix.reshape([2] * n_nodes + [1])\n", - "ising_cost = ising_cost.reshape([2] * n_nodes + [1])\n", - "\n", - "\n", - "class MIS(QuantumCircuit):\n", - " def __init__(self, n_qubits, n_layers):\n", - " super().__init__(n_qubits)\n", - " self.beta = nn.Parameter(torch.empty(n_layers,))\n", - " self.gamma = nn.Parameter(torch.empty(n_layers,))\n", - " self.reset_parameters()\n", - " \n", - " def reset_parameters(self):\n", - " init.uniform_(self.beta, -2 * np.pi, 2 * np.pi)\n", - " init.uniform_(self.gamma, -2 * np.pi, 2 * np.pi)\n", - " \n", - " def forward(self, return_cost=False):\n", - " state = self.uniform_state()\n", - " for b, g in zip(self.beta, self.gamma):\n", - " state = state * torch.exp(-1j * g * ising_matrix)\n", - " for i in range(self.n_qubits):\n", - " state = RX(b, state, [i], self.n_qubits)\n", - " if return_cost:\n", - " return torch.real(torch.sum(torch.abs(state)**2 * ising_cost))\n", - " else:\n", - " state = state.reshape((2**self.n_qubits,))\n", - " return torch.abs(state)**2" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "c84f30d0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Epoch 0 | Loss -0.5000000000000002\n", - "Epoch 50 | Loss -0.5000000000000002\n" - ] - } - ], - "source": [ - "model = MIS(n_nodes, 20)\n", - "\n", - "optimizer = torch.optim.Adam(model.parameters(), lr=.02)\n", - "epochs = 100\n", - "\n", - "\n", - "for epoch in range(epochs):\n", - " optimizer.zero_grad()\n", - " loss = model(True)\n", - " loss.backward()\n", - " optimizer.step()\n", - " if epoch%50 == 0:\n", - " print(f\"Epoch {epoch} | Loss {loss}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "4f7a6f49", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0011111001'" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "prob = model()\n", - "mis = torch.argmax(prob)\n", - "format(mis, '010b')" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "7bf6d8b0", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "tensor(249)" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mis" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "bfd64d73", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'1'" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "\"{0:b}\".format(1)" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "15ce4608", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "tensor(6.+0.j, dtype=torch.complex128)" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sum_N(n_nodes)[mis]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8cb69112", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3.10.4 ('pynn')", - "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.10.4" - }, - "vscode": { - "interpreter": { - "hash": "6f77e4d31a6b2b6cebe1094e9c25b1f29bee03e4b42142e04de4cbf5445e2748" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/deprecated/bench.py b/docs/deprecated/bench.py deleted file mode 100644 index 368173b8..00000000 --- a/docs/deprecated/bench.py +++ /dev/null @@ -1,451 +0,0 @@ -from __future__ import annotations - -import time - -import torch - -from pyqtorch.core.batched_operation import batchedRX -from pyqtorch.modules import RX, QuantumCircuit, zero_state -from pyqtorch.modules.parametric import RotationGate -from pyqtorch.modules.primitive import ControlledOperationGate - - -def timeit(f, *args, niters=100): - t = 0 - for _ in range(niters): - t0 = time.time() - out = f(*args) - t1 = time.time() - t += t1 - t0 - return t / niters, out - - -dtype = torch.cdouble -device = "cuda" -batch_size = 1000 -qubits = [0] -n_qubits = 10 - -state = zero_state(n_qubits, batch_size=batch_size, device=device, dtype=dtype) -phi = torch.rand(batch_size, device=device, dtype=dtype) -thetas = {"phi": phi} - -func_time, func_out = timeit(batchedRX, phi, state, qubits, n_qubits) - -gate = RX(qubits, n_qubits, "phi").to(device=device, dtype=dtype) -mod_time, mod_out = timeit(gate, thetas, state) - -print(f"Functional pyq: {func_time}") -print(f"Module pyq: {mod_time}") -print(f"Same results: {torch.allclose(func_out, mod_out)}") - - -circ = QuantumCircuit( - n_qubits, - [ - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - RotationGate("Y", (0,), 10, "phi"), - RotationGate("Y", (1,), 10, "phi"), - RotationGate("Y", (2,), 10, "phi"), - RotationGate("Y", (3,), 10, "phi"), - RotationGate("Y", (4,), 10, "phi"), - RotationGate("Y", (5,), 10, "phi"), - RotationGate("Y", (6,), 10, "phi"), - RotationGate("Y", (7,), 10, "phi"), - RotationGate("Y", (8,), 10, "phi"), - RotationGate("Y", (9,), 10, "phi"), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - ControlledOperationGate("X", (0, 1), 10), - ControlledOperationGate("X", (2, 3), 10), - ControlledOperationGate("X", (4, 5), 10), - ControlledOperationGate("X", (6, 7), 10), - ControlledOperationGate("X", (8, 9), 10), - ControlledOperationGate("X", (1, 2), 10), - ControlledOperationGate("X", (3, 4), 10), - ControlledOperationGate("X", (5, 6), 10), - ControlledOperationGate("X", (7, 8), 10), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - RotationGate("Y", (0,), 10, "phi"), - RotationGate("Y", (1,), 10, "phi"), - RotationGate("Y", (2,), 10, "phi"), - RotationGate("Y", (3,), 10, "phi"), - RotationGate("Y", (4,), 10, "phi"), - RotationGate("Y", (5,), 10, "phi"), - RotationGate("Y", (6,), 10, "phi"), - RotationGate("Y", (7,), 10, "phi"), - RotationGate("Y", (8,), 10, "phi"), - RotationGate("Y", (9,), 10, "phi"), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - ControlledOperationGate("X", (0, 1), 10), - ControlledOperationGate("X", (2, 3), 10), - ControlledOperationGate("X", (4, 5), 10), - ControlledOperationGate("X", (6, 7), 10), - ControlledOperationGate("X", (8, 9), 10), - ControlledOperationGate("X", (1, 2), 10), - ControlledOperationGate("X", (3, 4), 10), - ControlledOperationGate("X", (5, 6), 10), - ControlledOperationGate("X", (7, 8), 10), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - RotationGate("Y", (0,), 10, "phi"), - RotationGate("Y", (1,), 10, "phi"), - RotationGate("Y", (2,), 10, "phi"), - RotationGate("Y", (3,), 10, "phi"), - RotationGate("Y", (4,), 10, "phi"), - RotationGate("Y", (5,), 10, "phi"), - RotationGate("Y", (6,), 10, "phi"), - RotationGate("Y", (7,), 10, "phi"), - RotationGate("Y", (8,), 10, "phi"), - RotationGate("Y", (9,), 10, "phi"), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - ControlledOperationGate("X", (0, 1), 10), - ControlledOperationGate("X", (2, 3), 10), - ControlledOperationGate("X", (4, 5), 10), - ControlledOperationGate("X", (6, 7), 10), - ControlledOperationGate("X", (8, 9), 10), - ControlledOperationGate("X", (1, 2), 10), - ControlledOperationGate("X", (3, 4), 10), - ControlledOperationGate("X", (5, 6), 10), - ControlledOperationGate("X", (7, 8), 10), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - RotationGate("Y", (0,), 10, "phi"), - RotationGate("Y", (1,), 10, "phi"), - RotationGate("Y", (2,), 10, "phi"), - RotationGate("Y", (3,), 10, "phi"), - RotationGate("Y", (4,), 10, "phi"), - RotationGate("Y", (5,), 10, "phi"), - RotationGate("Y", (6,), 10, "phi"), - RotationGate("Y", (7,), 10, "phi"), - RotationGate("Y", (8,), 10, "phi"), - RotationGate("Y", (9,), 10, "phi"), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - ControlledOperationGate("X", (0, 1), 10), - ControlledOperationGate("X", (2, 3), 10), - ControlledOperationGate("X", (4, 5), 10), - ControlledOperationGate("X", (6, 7), 10), - ControlledOperationGate("X", (8, 9), 10), - ControlledOperationGate("X", (1, 2), 10), - ControlledOperationGate("X", (3, 4), 10), - ControlledOperationGate("X", (5, 6), 10), - ControlledOperationGate("X", (7, 8), 10), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - RotationGate("Y", (0,), 10, "phi"), - RotationGate("Y", (1,), 10, "phi"), - RotationGate("Y", (2,), 10, "phi"), - RotationGate("Y", (3,), 10, "phi"), - RotationGate("Y", (4,), 10, "phi"), - RotationGate("Y", (5,), 10, "phi"), - RotationGate("Y", (6,), 10, "phi"), - RotationGate("Y", (7,), 10, "phi"), - RotationGate("Y", (8,), 10, "phi"), - RotationGate("Y", (9,), 10, "phi"), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - ControlledOperationGate("X", (0, 1), 10), - ControlledOperationGate("X", (2, 3), 10), - ControlledOperationGate("X", (4, 5), 10), - ControlledOperationGate("X", (6, 7), 10), - ControlledOperationGate("X", (8, 9), 10), - ControlledOperationGate("X", (1, 2), 10), - ControlledOperationGate("X", (3, 4), 10), - ControlledOperationGate("X", (5, 6), 10), - ControlledOperationGate("X", (7, 8), 10), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - RotationGate("Y", (0,), 10, "phi"), - RotationGate("Y", (1,), 10, "phi"), - RotationGate("Y", (2,), 10, "phi"), - RotationGate("Y", (3,), 10, "phi"), - RotationGate("Y", (4,), 10, "phi"), - RotationGate("Y", (5,), 10, "phi"), - RotationGate("Y", (6,), 10, "phi"), - RotationGate("Y", (7,), 10, "phi"), - RotationGate("Y", (8,), 10, "phi"), - RotationGate("Y", (9,), 10, "phi"), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - ControlledOperationGate("X", (0, 1), 10), - ControlledOperationGate("X", (2, 3), 10), - ControlledOperationGate("X", (4, 5), 10), - ControlledOperationGate("X", (6, 7), 10), - ControlledOperationGate("X", (8, 9), 10), - ControlledOperationGate("X", (1, 2), 10), - ControlledOperationGate("X", (3, 4), 10), - ControlledOperationGate("X", (5, 6), 10), - ControlledOperationGate("X", (7, 8), 10), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - RotationGate("Y", (0,), 10, "phi"), - RotationGate("Y", (1,), 10, "phi"), - RotationGate("Y", (2,), 10, "phi"), - RotationGate("Y", (3,), 10, "phi"), - RotationGate("Y", (4,), 10, "phi"), - RotationGate("Y", (5,), 10, "phi"), - RotationGate("Y", (6,), 10, "phi"), - RotationGate("Y", (7,), 10, "phi"), - RotationGate("Y", (8,), 10, "phi"), - RotationGate("Y", (9,), 10, "phi"), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - ControlledOperationGate("X", (0, 1), 10), - ControlledOperationGate("X", (2, 3), 10), - ControlledOperationGate("X", (4, 5), 10), - ControlledOperationGate("X", (6, 7), 10), - ControlledOperationGate("X", (8, 9), 10), - ControlledOperationGate("X", (1, 2), 10), - ControlledOperationGate("X", (3, 4), 10), - ControlledOperationGate("X", (5, 6), 10), - ControlledOperationGate("X", (7, 8), 10), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - RotationGate("Y", (0,), 10, "phi"), - RotationGate("Y", (1,), 10, "phi"), - RotationGate("Y", (2,), 10, "phi"), - RotationGate("Y", (3,), 10, "phi"), - RotationGate("Y", (4,), 10, "phi"), - RotationGate("Y", (5,), 10, "phi"), - RotationGate("Y", (6,), 10, "phi"), - RotationGate("Y", (7,), 10, "phi"), - RotationGate("Y", (8,), 10, "phi"), - RotationGate("Y", (9,), 10, "phi"), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - ControlledOperationGate("X", (0, 1), 10), - ControlledOperationGate("X", (2, 3), 10), - ControlledOperationGate("X", (4, 5), 10), - ControlledOperationGate("X", (6, 7), 10), - ControlledOperationGate("X", (8, 9), 10), - ControlledOperationGate("X", (1, 2), 10), - ControlledOperationGate("X", (3, 4), 10), - ControlledOperationGate("X", (5, 6), 10), - ControlledOperationGate("X", (7, 8), 10), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - RotationGate("Y", (0,), 10, "phi"), - RotationGate("Y", (1,), 10, "phi"), - RotationGate("Y", (2,), 10, "phi"), - RotationGate("Y", (3,), 10, "phi"), - RotationGate("Y", (4,), 10, "phi"), - RotationGate("Y", (5,), 10, "phi"), - RotationGate("Y", (6,), 10, "phi"), - RotationGate("Y", (7,), 10, "phi"), - RotationGate("Y", (8,), 10, "phi"), - RotationGate("Y", (9,), 10, "phi"), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - ControlledOperationGate("X", (0, 1), 10), - ControlledOperationGate("X", (2, 3), 10), - ControlledOperationGate("X", (4, 5), 10), - ControlledOperationGate("X", (6, 7), 10), - ControlledOperationGate("X", (8, 9), 10), - ControlledOperationGate("X", (1, 2), 10), - ControlledOperationGate("X", (3, 4), 10), - ControlledOperationGate("X", (5, 6), 10), - ControlledOperationGate("X", (7, 8), 10), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - RotationGate("Y", (0,), 10, "phi"), - RotationGate("Y", (1,), 10, "phi"), - RotationGate("Y", (2,), 10, "phi"), - RotationGate("Y", (3,), 10, "phi"), - RotationGate("Y", (4,), 10, "phi"), - RotationGate("Y", (5,), 10, "phi"), - RotationGate("Y", (6,), 10, "phi"), - RotationGate("Y", (7,), 10, "phi"), - RotationGate("Y", (8,), 10, "phi"), - RotationGate("Y", (9,), 10, "phi"), - RotationGate("X", (0,), 10, "phi"), - RotationGate("X", (1,), 10, "phi"), - RotationGate("X", (2,), 10, "phi"), - RotationGate("X", (3,), 10, "phi"), - RotationGate("X", (4,), 10, "phi"), - RotationGate("X", (5,), 10, "phi"), - RotationGate("X", (6,), 10, "phi"), - RotationGate("X", (7,), 10, "phi"), - RotationGate("X", (8,), 10, "phi"), - RotationGate("X", (9,), 10, "phi"), - ControlledOperationGate("X", (0, 1), 10), - ControlledOperationGate("X", (2, 3), 10), - ControlledOperationGate("X", (4, 5), 10), - ControlledOperationGate("X", (6, 7), 10), - ControlledOperationGate("X", (8, 9), 10), - ControlledOperationGate("X", (1, 2), 10), - ControlledOperationGate("X", (3, 4), 10), - ControlledOperationGate("X", (5, 6), 10), - ControlledOperationGate("X", (7, 8), 10), - ], -).to(device=device, dtype=dtype) - -circ(thetas, state) - -print(timeit(circ, thetas, state)) diff --git a/docs/deprecated/fit_function.ipynb b/docs/deprecated/fit_function.ipynb deleted file mode 100644 index 843988d3..00000000 --- a/docs/deprecated/fit_function.ipynb +++ /dev/null @@ -1,489 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 12, - "id": "a6f294e0", - "metadata": {}, - "outputs": [], - "source": [ - "import torch\n", - "import torch.nn as nn\n", - "import torch.nn.functional as F\n", - "import torch.nn.init as init\n", - "\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import networkx as nx\n", - "\n", - "from pyqtorch.core.circuit import QuantumCircuit\n", - "from pyqtorch.ansatz import AlternateLayerAnsatz\n", - "from pyqtorch.embedding import SingleLayerEncoding\n", - "from pyqtorch.core.operation import Z, RX" - ] - }, - { - "cell_type": "markdown", - "id": "134a399c", - "metadata": {}, - "source": [ - "## Fit a target function" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "2612ff0c", - "metadata": {}, - "outputs": [], - "source": [ - "def target_function(x, degree=3):\n", - " result = 0\n", - " for i in range(degree):\n", - " result += torch.cos(i*x) + torch.sin(i*x)\n", - " return .05 * result" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "0ecc511f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "x = torch.tensor(np.linspace(0, 10, 100))\n", - "target_y = target_function(x, 5)\n", - "plt.plot(x.numpy(), target_y.numpy())" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "96c09bf3", - "metadata": {}, - "outputs": [], - "source": [ - "class Model(QuantumCircuit):\n", - " def __init__(self, n_qubits, n_layers):\n", - " super().__init__(n_qubits)\n", - " self.ansatz1 = AlternateLayerAnsatz(n_qubits, n_layers)\n", - " self.embedding = SingleLayerEncoding(n_qubits)\n", - " self.ansatz2 = AlternateLayerAnsatz(n_qubits, n_layers)\n", - " \n", - " \n", - " def forward(self, x):\n", - " batch_size = len(x)\n", - " state = self.init_state(batch_size)\n", - " \n", - " state = self.ansatz1(state)\n", - " state = self.embedding(state, x)\n", - " state = self.ansatz2(state)\n", - " \n", - " new_state = Z(state, [0], self.n_qubits)\n", - " \n", - " state = state.reshape((2**self.n_qubits, batch_size))\n", - " new_state = new_state.reshape((2**self.n_qubits, batch_size))\n", - " \n", - " return torch.real(torch.sum(torch.conj(state) * new_state, axis=0))" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "d059e027", - "metadata": {}, - "outputs": [], - "source": [ - "n_qubits = 5\n", - "n_layers = 3\n", - "\n", - "model = Model(n_qubits, n_layers)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "7eb3eafc", - "metadata": {}, - "outputs": [], - "source": [ - "with torch.no_grad():\n", - " y = model(x)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "ce29120e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "torch.Size([10])" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "model(x[0:10]).shape" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "0b0bee1b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.plot(x.numpy(), y.numpy())" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "8e4076b0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Epoch 1 | Loss 7.976638980490219e-05\n", - "Epoch 2 | Loss 0.00018516320389538174\n", - "Epoch 3 | Loss 0.00015250069418123508\n", - "Epoch 4 | Loss 0.0002954347442288451\n", - "Epoch 5 | Loss 9.96617571595185e-05\n", - "Epoch 6 | Loss 0.00015587379722353136\n", - "Epoch 7 | Loss 0.00014239923158366834\n", - "Epoch 8 | Loss 0.0001117781615256353\n", - "Epoch 9 | Loss 0.00011458096332979135\n", - "Epoch 10 | Loss 8.521301757740507e-05\n", - "Epoch 11 | Loss 9.186455606689106e-05\n", - "Epoch 12 | Loss 0.00011302529934456084\n", - "Epoch 13 | Loss 7.959300999009852e-05\n", - "Epoch 14 | Loss 5.2588442274184146e-05\n", - "Epoch 15 | Loss 6.899284437318874e-05\n", - "Epoch 16 | Loss 7.712150554875724e-05\n", - "Epoch 17 | Loss 6.734313331522322e-05\n", - "Epoch 18 | Loss 6.244483340190226e-05\n", - "Epoch 19 | Loss 5.285628239744807e-05\n", - "Epoch 20 | Loss 4.327889542059836e-05\n", - "Epoch 21 | Loss 5.068718981105365e-05\n", - "Epoch 22 | Loss 5.669043898201916e-05\n", - "Epoch 23 | Loss 4.6521824954857976e-05\n", - "Epoch 24 | Loss 3.854451028245699e-05\n", - "Epoch 25 | Loss 3.95755427944772e-05\n", - "Epoch 26 | Loss 3.800998614927786e-05\n", - "Epoch 27 | Loss 3.6745510730459526e-05\n", - "Epoch 28 | Loss 3.8465455192760104e-05\n", - "Epoch 29 | Loss 3.437600066547346e-05\n", - "Epoch 30 | Loss 2.8812066489541125e-05\n", - "Epoch 31 | Loss 2.9533549586861484e-05\n", - "Epoch 32 | Loss 2.957608565436818e-05\n", - "Epoch 33 | Loss 2.7057626252388196e-05\n", - "Epoch 34 | Loss 2.70324853383119e-05\n", - "Epoch 35 | Loss 2.5590466241145823e-05\n", - "Epoch 36 | Loss 2.2220650462571644e-05\n", - "Epoch 37 | Loss 2.220132989663142e-05\n", - "Epoch 38 | Loss 2.2136436191051248e-05\n", - "Epoch 39 | Loss 2.0002222695958007e-05\n", - "Epoch 40 | Loss 1.9640672124990093e-05\n", - "Epoch 41 | Loss 1.895554494504721e-05\n", - "Epoch 42 | Loss 1.7144345792850507e-05\n", - "Epoch 43 | Loss 1.6844903715821374e-05\n", - "Epoch 44 | Loss 1.5908082788713718e-05\n", - "Epoch 45 | Loss 1.447484953566956e-05\n", - "Epoch 46 | Loss 1.4662099736617871e-05\n", - "Epoch 47 | Loss 1.4145107022944017e-05\n", - "Epoch 48 | Loss 1.2795466674104107e-05\n", - "Epoch 49 | Loss 1.211236915921406e-05\n", - "Epoch 50 | Loss 1.1353350391973016e-05\n", - "Epoch 51 | Loss 1.1081852013021272e-05\n", - "Epoch 52 | Loss 1.100209740501172e-05\n", - "Epoch 53 | Loss 9.869642995512337e-06\n", - "Epoch 54 | Loss 9.007742551158593e-06\n", - "Epoch 55 | Loss 8.874283108322652e-06\n", - "Epoch 56 | Loss 8.52651521296105e-06\n", - "Epoch 57 | Loss 8.12172730360669e-06\n", - "Epoch 58 | Loss 7.459735999860909e-06\n", - "Epoch 59 | Loss 6.827688820842336e-06\n", - "Epoch 60 | Loss 6.785692585776636e-06\n", - "Epoch 61 | Loss 6.45539706286123e-06\n", - "Epoch 62 | Loss 5.894215803868307e-06\n", - "Epoch 63 | Loss 5.56098994120402e-06\n", - "Epoch 64 | Loss 5.209685349211364e-06\n", - "Epoch 65 | Loss 5.027929888167854e-06\n", - "Epoch 66 | Loss 4.704096830116435e-06\n", - "Epoch 67 | Loss 4.300515534936481e-06\n", - "Epoch 68 | Loss 4.150485565538104e-06\n", - "Epoch 69 | Loss 3.877569473355058e-06\n", - "Epoch 70 | Loss 3.6016460974809315e-06\n", - "Epoch 71 | Loss 3.3824982572675673e-06\n", - "Epoch 72 | Loss 3.16966959953517e-06\n", - "Epoch 73 | Loss 3.030399304437687e-06\n", - "Epoch 74 | Loss 2.76945424485514e-06\n", - "Epoch 75 | Loss 2.553054371774435e-06\n", - "Epoch 76 | Loss 2.4448968670808195e-06\n", - "Epoch 77 | Loss 2.2889466512651473e-06\n", - "Epoch 78 | Loss 2.131864339904989e-06\n", - "Epoch 79 | Loss 1.946444993085096e-06\n", - "Epoch 80 | Loss 1.8250938955374872e-06\n", - "Epoch 81 | Loss 1.737781328244668e-06\n", - "Epoch 82 | Loss 1.5917553754898976e-06\n", - "Epoch 83 | Loss 1.4797073287116884e-06\n", - "Epoch 84 | Loss 1.3673760112968568e-06\n", - "Epoch 85 | Loss 1.2841187042706526e-06\n", - "Epoch 86 | Loss 1.1946987781088069e-06\n", - "Epoch 87 | Loss 1.0950192864944728e-06\n", - "Epoch 88 | Loss 1.0252991498133102e-06\n", - "Epoch 89 | Loss 9.441709891350378e-07\n", - "Epoch 90 | Loss 8.802933932194375e-07\n", - "Epoch 91 | Loss 8.132572461557469e-07\n", - "Epoch 92 | Loss 7.505187111568322e-07\n", - "Epoch 93 | Loss 6.966418777239547e-07\n", - "Epoch 94 | Loss 6.404785773511559e-07\n", - "Epoch 95 | Loss 5.988044546804841e-07\n", - "Epoch 96 | Loss 5.489020536184141e-07\n", - "Epoch 97 | Loss 5.069285331770859e-07\n", - "Epoch 98 | Loss 4.6839833041967234e-07\n", - "Epoch 99 | Loss 4.330229784719919e-07\n", - "Epoch 100 | Loss 4.01301779468656e-07\n", - "Epoch 101 | Loss 3.6702312795055337e-07\n", - "Epoch 102 | Loss 3.412788199584224e-07\n", - "Epoch 103 | Loss 3.126745305312052e-07\n", - "Epoch 104 | Loss 2.900542128553373e-07\n", - "Epoch 105 | Loss 2.673624858901556e-07\n", - "Epoch 106 | Loss 2.467128937298071e-07\n", - "Epoch 107 | Loss 2.2656895489334856e-07\n", - "Epoch 108 | Loss 2.085350333653908e-07\n", - "Epoch 109 | Loss 1.9469188536848812e-07\n", - "Epoch 110 | Loss 1.7820164935276283e-07\n", - "Epoch 111 | Loss 1.640496481019208e-07\n", - "Epoch 112 | Loss 1.5106211262608455e-07\n", - "Epoch 113 | Loss 1.4119848223789955e-07\n", - "Epoch 114 | Loss 1.292007696718066e-07\n", - "Epoch 115 | Loss 1.1874412702599467e-07\n", - "Epoch 116 | Loss 1.1027902270851239e-07\n", - "Epoch 117 | Loss 1.0224775106258967e-07\n", - "Epoch 118 | Loss 9.397244913387625e-08\n", - "Epoch 119 | Loss 8.638233314437162e-08\n", - "Epoch 120 | Loss 8.06963340770389e-08\n", - "Epoch 121 | Loss 7.429052919349288e-08\n", - "Epoch 122 | Loss 6.864083280756386e-08\n", - "Epoch 123 | Loss 6.33055696357184e-08\n", - "Epoch 124 | Loss 5.899834870509737e-08\n", - "Epoch 125 | Loss 5.4242624712314306e-08\n", - "Epoch 126 | Loss 5.034641898340614e-08\n", - "Epoch 127 | Loss 4.660330291945879e-08\n", - "Epoch 128 | Loss 4.314035280090745e-08\n", - "Epoch 129 | Loss 3.981389918540132e-08\n", - "Epoch 130 | Loss 3.704318295355837e-08\n", - "Epoch 131 | Loss 3.4305289858396905e-08\n", - "Epoch 132 | Loss 3.1603543001644865e-08\n", - "Epoch 133 | Loss 2.932675616681832e-08\n", - "Epoch 134 | Loss 2.7229185806650127e-08\n", - "Epoch 135 | Loss 2.520537197082856e-08\n", - "Epoch 136 | Loss 2.3212538811910533e-08\n", - "Epoch 137 | Loss 2.1593655612378752e-08\n", - "Epoch 138 | Loss 1.9959724261288564e-08\n", - "Epoch 139 | Loss 1.848102724875526e-08\n", - "Epoch 140 | Loss 1.7027341753179298e-08\n", - "Epoch 141 | Loss 1.5813454030146326e-08\n", - "Epoch 142 | Loss 1.4578917522711054e-08\n", - "Epoch 143 | Loss 1.3489509966222379e-08\n", - "Epoch 144 | Loss 1.2418868714184316e-08\n", - "Epoch 145 | Loss 1.1502672471082855e-08\n", - "Epoch 146 | Loss 1.059197351504494e-08\n", - "Epoch 147 | Loss 9.77656057985274e-09\n", - "Epoch 148 | Loss 8.973389180762976e-09\n", - "Epoch 149 | Loss 8.29561541610071e-09\n", - "Epoch 150 | Loss 7.622592994314037e-09\n", - "Epoch 151 | Loss 7.009538649847957e-09\n", - "Epoch 152 | Loss 6.42317602452753e-09\n", - "Epoch 153 | Loss 5.922748802421972e-09\n", - "Epoch 154 | Loss 5.416817199714565e-09\n", - "Epoch 155 | Loss 4.97056685968615e-09\n", - "Epoch 156 | Loss 4.548607161353243e-09\n", - "Epoch 157 | Loss 4.166262719362401e-09\n", - "Epoch 158 | Loss 3.801337132542815e-09\n", - "Epoch 159 | Loss 3.4831560781203587e-09\n", - "Epoch 160 | Loss 3.169006122372877e-09\n", - "Epoch 161 | Loss 2.890839248064704e-09\n", - "Epoch 162 | Loss 2.631715595460305e-09\n", - "Epoch 163 | Loss 2.40175223653661e-09\n", - "Epoch 164 | Loss 2.1709880287419868e-09\n", - "Epoch 165 | Loss 1.9793050924534993e-09\n", - "Epoch 166 | Loss 1.7947676471210319e-09\n", - "Epoch 167 | Loss 1.6246952978985594e-09\n", - "Epoch 168 | Loss 1.4662527536720128e-09\n", - "Epoch 169 | Loss 1.3339776518865908e-09\n", - "Epoch 170 | Loss 1.198592456232976e-09\n", - "Epoch 171 | Loss 1.0805168769488162e-09\n", - "Epoch 172 | Loss 9.745193630127017e-10\n", - "Epoch 173 | Loss 8.790876549097978e-10\n", - "Epoch 174 | Loss 7.861964944597971e-10\n", - "Epoch 175 | Loss 7.081532756848237e-10\n", - "Epoch 176 | Loss 6.343271518451939e-10\n", - "Epoch 177 | Loss 5.686702897385907e-10\n", - "Epoch 178 | Loss 5.081131012009327e-10\n", - "Epoch 179 | Loss 4.540037138717548e-10\n", - "Epoch 180 | Loss 4.045487719974579e-10\n", - "Epoch 181 | Loss 3.613877312708314e-10\n", - "Epoch 182 | Loss 3.21198860300143e-10\n", - "Epoch 183 | Loss 2.854309803476494e-10\n", - "Epoch 184 | Loss 2.537583324023182e-10\n", - "Epoch 185 | Loss 2.244044223897077e-10\n", - "Epoch 186 | Loss 1.99460459616564e-10\n", - "Epoch 187 | Loss 1.7593777950857055e-10\n", - "Epoch 188 | Loss 1.5583428623061128e-10\n", - "Epoch 189 | Loss 1.3767095584122574e-10\n", - "Epoch 190 | Loss 1.2213353501262156e-10\n", - "Epoch 191 | Loss 1.0617428195852059e-10\n", - "Epoch 192 | Loss 9.563760842007555e-11\n", - "Epoch 193 | Loss 8.258923837690156e-11\n", - "Epoch 194 | Loss 7.227734310396987e-11\n", - "Epoch 195 | Loss 6.424510857200186e-11\n", - "Epoch 196 | Loss 5.537511064809611e-11\n", - "Epoch 197 | Loss 4.8596399538342986e-11\n", - "Epoch 198 | Loss 4.2393518257747763e-11\n", - "Epoch 199 | Loss 3.7616087065908444e-11\n", - "Epoch 200 | Loss 3.165019865970098e-11\n" - ] - } - ], - "source": [ - "optimizer = torch.optim.Adam(model.parameters(), lr=.01)\n", - "epochs = 200\n", - "\n", - "for epoch in range(epochs):\n", - " optimizer.zero_grad()\n", - " y_pred = model(x)\n", - " loss = F.mse_loss(target_y, y_pred)\n", - " loss.backward()\n", - " optimizer.step()\n", - " print(f\"Epoch {epoch+1} | Loss {loss}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "3dd9cb34", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "with torch.no_grad():\n", - " y = model(x)\n", - "\n", - "plt.plot(x.numpy(), target_y.numpy()) \n", - "plt.plot(x.numpy(), y.numpy())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8cb69112", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3.10.4 ('pynn')", - "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.10.4" - }, - "vscode": { - "interpreter": { - "hash": "6f77e4d31a6b2b6cebe1094e9c25b1f29bee03e4b42142e04de4cbf5445e2748" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/deprecated/getting_started.ipynb b/docs/deprecated/getting_started.ipynb deleted file mode 100644 index dabf2296..00000000 --- a/docs/deprecated/getting_started.ipynb +++ /dev/null @@ -1,490 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "id": "134a399c", - "metadata": {}, - "source": [ - "### Fitting a function" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "a6f294e0", - "metadata": {}, - "outputs": [], - "source": [ - "import torch\n", - "import torch.nn as nn\n", - "import torch.nn.functional as F\n", - "import torch.nn.init as init\n", - "\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import networkx as nx\n", - "\n", - "from pyqtorch.core.circuit import QuantumCircuit\n", - "from pyqtorch.ansatz import AlternateLayerAnsatz\n", - "from pyqtorch.embedding import SingleLayerEncoding\n", - "from pyqtorch.core.operation import Z, RX" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "2612ff0c", - "metadata": {}, - "outputs": [], - "source": [ - "def target_function(x, degree=3):\n", - " result = 0\n", - " for i in range(degree):\n", - " result += torch.cos(i*x) + torch.sin(i*x)\n", - " return .05 * result" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "0ecc511f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "x = torch.tensor(np.linspace(0, 10, 100))\n", - "target_y = target_function(x, 5)\n", - "plt.plot(x.numpy(), target_y.numpy())" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "96c09bf3", - "metadata": {}, - "outputs": [], - "source": [ - "class Model(QuantumCircuit):\n", - " def __init__(self, n_qubits, n_layers):\n", - " super().__init__(n_qubits)\n", - " self.ansatz1 = AlternateLayerAnsatz(n_qubits, n_layers)\n", - " self.embedding = SingleLayerEncoding(n_qubits)\n", - " self.ansatz2 = AlternateLayerAnsatz(n_qubits, n_layers)\n", - " \n", - " \n", - " def forward(self, x):\n", - " batch_size = len(x)\n", - " state = self.init_state(batch_size)\n", - " \n", - " state = self.ansatz1(state)\n", - " state = self.embedding(state, x)\n", - " state = self.ansatz2(state)\n", - " \n", - " new_state = Z(state, [0], self.n_qubits)\n", - " \n", - " state = state.reshape((2**self.n_qubits, batch_size))\n", - " new_state = new_state.reshape((2**self.n_qubits, batch_size))\n", - " \n", - " return torch.real(torch.sum(torch.conj(state) * new_state, axis=0))" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "d059e027", - "metadata": {}, - "outputs": [], - "source": [ - "n_qubits = 5\n", - "n_layers = 3\n", - "\n", - "model = Model(n_qubits, n_layers)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "7eb3eafc", - "metadata": {}, - "outputs": [], - "source": [ - "with torch.no_grad():\n", - " y = model(x)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "ce29120e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "torch.Size([10])" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "model(x[0:10]).shape" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "0b0bee1b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.plot(x.numpy(), y.numpy())" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "8e4076b0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Epoch 1 | Loss 7.976638980490219e-05\n", - "Epoch 2 | Loss 0.00018516320389538174\n", - "Epoch 3 | Loss 0.00015250069418123508\n", - "Epoch 4 | Loss 0.0002954347442288451\n", - "Epoch 5 | Loss 9.96617571595185e-05\n", - "Epoch 6 | Loss 0.00015587379722353136\n", - "Epoch 7 | Loss 0.00014239923158366834\n", - "Epoch 8 | Loss 0.0001117781615256353\n", - "Epoch 9 | Loss 0.00011458096332979135\n", - "Epoch 10 | Loss 8.521301757740507e-05\n", - "Epoch 11 | Loss 9.186455606689106e-05\n", - "Epoch 12 | Loss 0.00011302529934456084\n", - "Epoch 13 | Loss 7.959300999009852e-05\n", - "Epoch 14 | Loss 5.2588442274184146e-05\n", - "Epoch 15 | Loss 6.899284437318874e-05\n", - "Epoch 16 | Loss 7.712150554875724e-05\n", - "Epoch 17 | Loss 6.734313331522322e-05\n", - "Epoch 18 | Loss 6.244483340190226e-05\n", - "Epoch 19 | Loss 5.285628239744807e-05\n", - "Epoch 20 | Loss 4.327889542059836e-05\n", - "Epoch 21 | Loss 5.068718981105365e-05\n", - "Epoch 22 | Loss 5.669043898201916e-05\n", - "Epoch 23 | Loss 4.6521824954857976e-05\n", - "Epoch 24 | Loss 3.854451028245699e-05\n", - "Epoch 25 | Loss 3.95755427944772e-05\n", - "Epoch 26 | Loss 3.800998614927786e-05\n", - "Epoch 27 | Loss 3.6745510730459526e-05\n", - "Epoch 28 | Loss 3.8465455192760104e-05\n", - "Epoch 29 | Loss 3.437600066547346e-05\n", - "Epoch 30 | Loss 2.8812066489541125e-05\n", - "Epoch 31 | Loss 2.9533549586861484e-05\n", - "Epoch 32 | Loss 2.957608565436818e-05\n", - "Epoch 33 | Loss 2.7057626252388196e-05\n", - "Epoch 34 | Loss 2.70324853383119e-05\n", - "Epoch 35 | Loss 2.5590466241145823e-05\n", - "Epoch 36 | Loss 2.2220650462571644e-05\n", - "Epoch 37 | Loss 2.220132989663142e-05\n", - "Epoch 38 | Loss 2.2136436191051248e-05\n", - "Epoch 39 | Loss 2.0002222695958007e-05\n", - "Epoch 40 | Loss 1.9640672124990093e-05\n", - "Epoch 41 | Loss 1.895554494504721e-05\n", - "Epoch 42 | Loss 1.7144345792850507e-05\n", - "Epoch 43 | Loss 1.6844903715821374e-05\n", - "Epoch 44 | Loss 1.5908082788713718e-05\n", - "Epoch 45 | Loss 1.447484953566956e-05\n", - "Epoch 46 | Loss 1.4662099736617871e-05\n", - "Epoch 47 | Loss 1.4145107022944017e-05\n", - "Epoch 48 | Loss 1.2795466674104107e-05\n", - "Epoch 49 | Loss 1.211236915921406e-05\n", - "Epoch 50 | Loss 1.1353350391973016e-05\n", - "Epoch 51 | Loss 1.1081852013021272e-05\n", - "Epoch 52 | Loss 1.100209740501172e-05\n", - "Epoch 53 | Loss 9.869642995512337e-06\n", - "Epoch 54 | Loss 9.007742551158593e-06\n", - "Epoch 55 | Loss 8.874283108322652e-06\n", - "Epoch 56 | Loss 8.52651521296105e-06\n", - "Epoch 57 | Loss 8.12172730360669e-06\n", - "Epoch 58 | Loss 7.459735999860909e-06\n", - "Epoch 59 | Loss 6.827688820842336e-06\n", - "Epoch 60 | Loss 6.785692585776636e-06\n", - "Epoch 61 | Loss 6.45539706286123e-06\n", - "Epoch 62 | Loss 5.894215803868307e-06\n", - "Epoch 63 | Loss 5.56098994120402e-06\n", - "Epoch 64 | Loss 5.209685349211364e-06\n", - "Epoch 65 | Loss 5.027929888167854e-06\n", - "Epoch 66 | Loss 4.704096830116435e-06\n", - "Epoch 67 | Loss 4.300515534936481e-06\n", - "Epoch 68 | Loss 4.150485565538104e-06\n", - "Epoch 69 | Loss 3.877569473355058e-06\n", - "Epoch 70 | Loss 3.6016460974809315e-06\n", - "Epoch 71 | Loss 3.3824982572675673e-06\n", - "Epoch 72 | Loss 3.16966959953517e-06\n", - "Epoch 73 | Loss 3.030399304437687e-06\n", - "Epoch 74 | Loss 2.76945424485514e-06\n", - "Epoch 75 | Loss 2.553054371774435e-06\n", - "Epoch 76 | Loss 2.4448968670808195e-06\n", - "Epoch 77 | Loss 2.2889466512651473e-06\n", - "Epoch 78 | Loss 2.131864339904989e-06\n", - "Epoch 79 | Loss 1.946444993085096e-06\n", - "Epoch 80 | Loss 1.8250938955374872e-06\n", - "Epoch 81 | Loss 1.737781328244668e-06\n", - "Epoch 82 | Loss 1.5917553754898976e-06\n", - "Epoch 83 | Loss 1.4797073287116884e-06\n", - "Epoch 84 | Loss 1.3673760112968568e-06\n", - "Epoch 85 | Loss 1.2841187042706526e-06\n", - "Epoch 86 | Loss 1.1946987781088069e-06\n", - "Epoch 87 | Loss 1.0950192864944728e-06\n", - "Epoch 88 | Loss 1.0252991498133102e-06\n", - "Epoch 89 | Loss 9.441709891350378e-07\n", - "Epoch 90 | Loss 8.802933932194375e-07\n", - "Epoch 91 | Loss 8.132572461557469e-07\n", - "Epoch 92 | Loss 7.505187111568322e-07\n", - "Epoch 93 | Loss 6.966418777239547e-07\n", - "Epoch 94 | Loss 6.404785773511559e-07\n", - "Epoch 95 | Loss 5.988044546804841e-07\n", - "Epoch 96 | Loss 5.489020536184141e-07\n", - "Epoch 97 | Loss 5.069285331770859e-07\n", - "Epoch 98 | Loss 4.6839833041967234e-07\n", - "Epoch 99 | Loss 4.330229784719919e-07\n", - "Epoch 100 | Loss 4.01301779468656e-07\n", - "Epoch 101 | Loss 3.6702312795055337e-07\n", - "Epoch 102 | Loss 3.412788199584224e-07\n", - "Epoch 103 | Loss 3.126745305312052e-07\n", - "Epoch 104 | Loss 2.900542128553373e-07\n", - "Epoch 105 | Loss 2.673624858901556e-07\n", - "Epoch 106 | Loss 2.467128937298071e-07\n", - "Epoch 107 | Loss 2.2656895489334856e-07\n", - "Epoch 108 | Loss 2.085350333653908e-07\n", - "Epoch 109 | Loss 1.9469188536848812e-07\n", - "Epoch 110 | Loss 1.7820164935276283e-07\n", - "Epoch 111 | Loss 1.640496481019208e-07\n", - "Epoch 112 | Loss 1.5106211262608455e-07\n", - "Epoch 113 | Loss 1.4119848223789955e-07\n", - "Epoch 114 | Loss 1.292007696718066e-07\n", - "Epoch 115 | Loss 1.1874412702599467e-07\n", - "Epoch 116 | Loss 1.1027902270851239e-07\n", - "Epoch 117 | Loss 1.0224775106258967e-07\n", - "Epoch 118 | Loss 9.397244913387625e-08\n", - "Epoch 119 | Loss 8.638233314437162e-08\n", - "Epoch 120 | Loss 8.06963340770389e-08\n", - "Epoch 121 | Loss 7.429052919349288e-08\n", - "Epoch 122 | Loss 6.864083280756386e-08\n", - "Epoch 123 | Loss 6.33055696357184e-08\n", - "Epoch 124 | Loss 5.899834870509737e-08\n", - "Epoch 125 | Loss 5.4242624712314306e-08\n", - "Epoch 126 | Loss 5.034641898340614e-08\n", - "Epoch 127 | Loss 4.660330291945879e-08\n", - "Epoch 128 | Loss 4.314035280090745e-08\n", - "Epoch 129 | Loss 3.981389918540132e-08\n", - "Epoch 130 | Loss 3.704318295355837e-08\n", - "Epoch 131 | Loss 3.4305289858396905e-08\n", - "Epoch 132 | Loss 3.1603543001644865e-08\n", - "Epoch 133 | Loss 2.932675616681832e-08\n", - "Epoch 134 | Loss 2.7229185806650127e-08\n", - "Epoch 135 | Loss 2.520537197082856e-08\n", - "Epoch 136 | Loss 2.3212538811910533e-08\n", - "Epoch 137 | Loss 2.1593655612378752e-08\n", - "Epoch 138 | Loss 1.9959724261288564e-08\n", - "Epoch 139 | Loss 1.848102724875526e-08\n", - "Epoch 140 | Loss 1.7027341753179298e-08\n", - "Epoch 141 | Loss 1.5813454030146326e-08\n", - "Epoch 142 | Loss 1.4578917522711054e-08\n", - "Epoch 143 | Loss 1.3489509966222379e-08\n", - "Epoch 144 | Loss 1.2418868714184316e-08\n", - "Epoch 145 | Loss 1.1502672471082855e-08\n", - "Epoch 146 | Loss 1.059197351504494e-08\n", - "Epoch 147 | Loss 9.77656057985274e-09\n", - "Epoch 148 | Loss 8.973389180762976e-09\n", - "Epoch 149 | Loss 8.29561541610071e-09\n", - "Epoch 150 | Loss 7.622592994314037e-09\n", - "Epoch 151 | Loss 7.009538649847957e-09\n", - "Epoch 152 | Loss 6.42317602452753e-09\n", - "Epoch 153 | Loss 5.922748802421972e-09\n", - "Epoch 154 | Loss 5.416817199714565e-09\n", - "Epoch 155 | Loss 4.97056685968615e-09\n", - "Epoch 156 | Loss 4.548607161353243e-09\n", - "Epoch 157 | Loss 4.166262719362401e-09\n", - "Epoch 158 | Loss 3.801337132542815e-09\n", - "Epoch 159 | Loss 3.4831560781203587e-09\n", - "Epoch 160 | Loss 3.169006122372877e-09\n", - "Epoch 161 | Loss 2.890839248064704e-09\n", - "Epoch 162 | Loss 2.631715595460305e-09\n", - "Epoch 163 | Loss 2.40175223653661e-09\n", - "Epoch 164 | Loss 2.1709880287419868e-09\n", - "Epoch 165 | Loss 1.9793050924534993e-09\n", - "Epoch 166 | Loss 1.7947676471210319e-09\n", - "Epoch 167 | Loss 1.6246952978985594e-09\n", - "Epoch 168 | Loss 1.4662527536720128e-09\n", - "Epoch 169 | Loss 1.3339776518865908e-09\n", - "Epoch 170 | Loss 1.198592456232976e-09\n", - "Epoch 171 | Loss 1.0805168769488162e-09\n", - "Epoch 172 | Loss 9.745193630127017e-10\n", - "Epoch 173 | Loss 8.790876549097978e-10\n", - "Epoch 174 | Loss 7.861964944597971e-10\n", - "Epoch 175 | Loss 7.081532756848237e-10\n", - "Epoch 176 | Loss 6.343271518451939e-10\n", - "Epoch 177 | Loss 5.686702897385907e-10\n", - "Epoch 178 | Loss 5.081131012009327e-10\n", - "Epoch 179 | Loss 4.540037138717548e-10\n", - "Epoch 180 | Loss 4.045487719974579e-10\n", - "Epoch 181 | Loss 3.613877312708314e-10\n", - "Epoch 182 | Loss 3.21198860300143e-10\n", - "Epoch 183 | Loss 2.854309803476494e-10\n", - "Epoch 184 | Loss 2.537583324023182e-10\n", - "Epoch 185 | Loss 2.244044223897077e-10\n", - "Epoch 186 | Loss 1.99460459616564e-10\n", - "Epoch 187 | Loss 1.7593777950857055e-10\n", - "Epoch 188 | Loss 1.5583428623061128e-10\n", - "Epoch 189 | Loss 1.3767095584122574e-10\n", - "Epoch 190 | Loss 1.2213353501262156e-10\n", - "Epoch 191 | Loss 1.0617428195852059e-10\n", - "Epoch 192 | Loss 9.563760842007555e-11\n", - "Epoch 193 | Loss 8.258923837690156e-11\n", - "Epoch 194 | Loss 7.227734310396987e-11\n", - "Epoch 195 | Loss 6.424510857200186e-11\n", - "Epoch 196 | Loss 5.537511064809611e-11\n", - "Epoch 197 | Loss 4.8596399538342986e-11\n", - "Epoch 198 | Loss 4.2393518257747763e-11\n", - "Epoch 199 | Loss 3.7616087065908444e-11\n", - "Epoch 200 | Loss 3.165019865970098e-11\n" - ] - } - ], - "source": [ - "optimizer = torch.optim.Adam(model.parameters(), lr=.01)\n", - "epochs = 200\n", - "\n", - "for epoch in range(epochs):\n", - " optimizer.zero_grad()\n", - " y_pred = model(x)\n", - " loss = F.mse_loss(target_y, y_pred)\n", - " loss.backward()\n", - " optimizer.step()\n", - " print(f\"Epoch {epoch+1} | Loss {loss}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "3dd9cb34", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "with torch.no_grad():\n", - " y = model(x)\n", - "\n", - "plt.plot(x.numpy(), target_y.numpy()) \n", - "plt.plot(x.numpy(), y.numpy())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8cb69112", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3.10.4 ('pynn')", - "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.10.4" - }, - "vscode": { - "interpreter": { - "hash": "6f77e4d31a6b2b6cebe1094e9c25b1f29bee03e4b42142e04de4cbf5445e2748" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/deprecated/ham_evol_comparison.ipynb b/docs/deprecated/ham_evol_comparison.ipynb deleted file mode 100644 index 0de1ce83..00000000 --- a/docs/deprecated/ham_evol_comparison.ipynb +++ /dev/null @@ -1,612 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "e7d97093-d746-4749-a754-cd090687a524", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/joaom/mambaforge/envs/qucint/lib/python3.9/site-packages/tqdm/auto.py:22: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n" - ] - } - ], - "source": [ - "import torch\n", - "import numpy as np\n", - "import networkx as nx\n", - "\n", - "from pyqtorch.core.circuit import QuantumCircuit\n", - "from pyqtorch.core.operation import hamiltonian_evolution, hamiltonian_evolution_eig\n", - "import copy\n", - "\n", - "from pyqtorch.matrices import generate_ising_from_graph" - ] - }, - { - "cell_type": "markdown", - "id": "5a069a95-3339-4296-aacf-ea5f8a49059e", - "metadata": {}, - "source": [ - "# Comparing the single hamiltonian evolution\n", - "Here we use an Ising hamiltonian over a graph, which is diagonal" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "7041a91f-4712-459c-8fd6-b147e12faf58", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "N = 8\n", - "p = 0.5\n", - "\n", - "graph = nx.fast_gnp_random_graph(N, p)\n", - "\n", - "batch_size = 1\n", - "qc = QuantumCircuit(N)\n", - "psi = qc.uniform_state(batch_size)\n", - "psi_ini = copy.deepcopy(psi)\n", - "\n", - "H_diag = generate_ising_from_graph(graph)\n", - "\n", - "H = torch.diag(H_diag)\n", - "\n", - "qc = QuantumCircuit(N)\n", - "psi = qc.uniform_state(batch_size)\n", - "psi_ini = copy.deepcopy(psi)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "5a447259-e5fa-4154-aa1d-a9507592b021", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "t_range = torch.arange(0, 2*np.pi, 0.1)\n", - "wf_save_rk = torch.zeros((len(t_range),)+tuple(psi.shape)).cdouble()\n", - "wf_save_eig = torch.zeros((len(t_range),)+tuple(psi.shape)).cdouble()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "d6103960-5f98-4369-b9b9-b576111f1508", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "RK4: 3.183 secs.\n", - "Eig: 0.038 secs.\n" - ] - } - ], - "source": [ - "import time\n", - "\n", - "start = time.perf_counter()\n", - "for i, t in enumerate(t_range):\n", - " t_evo = torch.tensor([t])\n", - " psi = copy.deepcopy(psi_ini)\n", - " hamiltonian_evolution(H, psi, t_evo, range(N), N)\n", - " wf_save_rk[i] = psi\n", - "end = time.perf_counter()\n", - "\n", - "secs = (end-start)\n", - "print(f\"RK4: {secs:.03f} secs.\")\n", - "\n", - "start = time.perf_counter()\n", - "for i, t in enumerate(t_range):\n", - " t_evo = torch.tensor([t])\n", - " psi = copy.deepcopy(psi_ini)\n", - " hamiltonian_evolution_eig(H, psi, t_evo, range(N), N)\n", - " wf_save_eig[i] = psi\n", - "end = time.perf_counter()\n", - "\n", - "secs = (end-start)\n", - "print(f\"Eig: {secs:.03f} secs.\")" - ] - }, - { - "cell_type": "markdown", - "id": "d7478d25-a006-495e-92fa-16658c8ddba8", - "metadata": {}, - "source": [ - "The Hamiltonian is diagonal so the eig function skips the diagonalization and is very efficient." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "e47eac6f-ed01-43f6-b41c-36ee0a8b99a4", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "norm_rk = torch.tensor([(abs(wf_save_rk[i,...])**2).sum() for i in range(len(t_range))])\n", - "norm_eig = torch.tensor([(abs(wf_save_eig[i,...])**2).sum() for i in range(len(t_range))])\n", - "\n", - "plt.plot(t_range, norm_rk, label = \"RK4\")\n", - "plt.plot(t_range, norm_eig, label = \"Eig\")\n", - "plt.ylim(0.95, 1.01)\n", - "plt.ylabel(\"Normalization ||^2\")\n", - "plt.xlabel(\"Time t\")\n", - "plt.legend()" - ] - }, - { - "cell_type": "markdown", - "id": "9cd9baa5-5584-44bc-a095-7f85ce9bff86", - "metadata": {}, - "source": [ - "It is also exact, maintaining the normalization of the wavefunction." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "c66d1b1d-575f-4303-aa86-1bbd4197dcf0", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "Text(0.5, 0, 'Time t')" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "diff = torch.tensor([torch.max(abs(wf_save_rk[i,...] - wf_save_eig[i,...])) for i in range(len(t_range))])\n", - "\n", - "plt.plot(t_range, diff)\n", - "plt.yscale(\"log\")\n", - "plt.ylabel(\"Max(Abs(|psi_rk4> - |psi_eig>))\")\n", - "plt.xlabel(\"Time t\")" - ] - }, - { - "cell_type": "markdown", - "id": "312f9b7f-60f7-4ae7-98de-55156a3067e9", - "metadata": {}, - "source": [ - "As we can see both methods give a similar result for small t but the difference increases exponentially due to the error in the RK4 method." - ] - }, - { - "cell_type": "markdown", - "id": "cac2bf97-1fa2-4660-acd2-a048bd899d58", - "metadata": {}, - "source": [ - "# Hamiltonian caching:\n", - "The results of hamiltonian diagonalization are cached for repeated use:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "eadbc7dc-7139-4f54-9651-d888f2867cab", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "n_qubits: int = 10\n", - "batch_size: int = 1\n", - "\n", - "qc = QuantumCircuit(n_qubits)\n", - "psi = qc.uniform_state(batch_size)\n", - "psi_ini = copy.deepcopy(psi)\n", - "\n", - "H_0 = torch.randn((2**n_qubits, 2**n_qubits), dtype = torch.double)\n", - "H0 = (H_0 + torch.conj(H_0.transpose(0, 1))).cdouble()\n", - "\n", - "H_1 = torch.randn((2**n_qubits, 2**n_qubits), dtype = torch.double)\n", - "H1 = (H_1 + torch.conj(H_1.transpose(0, 1))).cdouble()" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "c7a135f0-fea0-4af6-80ad-fdf860a7b7af", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "First Hamiltonian\n", - "Timing: 0.29912304878234863\n", - "Timing: 0.07640600204467773\n", - "Second Hamiltonian\n", - "Timing: 0.27396464347839355\n", - "Timing: 0.08124589920043945\n", - "First Hamiltonian again\n", - "Timing: 0.08870816230773926\n", - "Timing: 0.07958817481994629\n" - ] - } - ], - "source": [ - "import time\n", - "\n", - "n_trials = 2\n", - "wf_save_eig = torch.zeros((n_trials,)+tuple(psi.shape)).cdouble()\n", - "\n", - "print(\"First Hamiltonian\")\n", - "for i in range(n_trials):\n", - " H = H0\n", - " start = time.time()\n", - " t_evo = torch.rand(batch_size)*0.5\n", - "\n", - " psi = copy.deepcopy(psi_ini)\n", - " hamiltonian_evolution_eig(H, psi, t_evo, range(n_qubits), n_qubits)\n", - " wf_save_eig[i] = psi\n", - " end = time.time()\n", - " print(\"Timing:\", end-start)\n", - "\n", - "print(\"Second Hamiltonian\")\n", - "for i in range(n_trials):\n", - " H = H1\n", - " start = time.time()\n", - " t_evo = torch.rand(batch_size)*0.5\n", - "\n", - " psi = copy.deepcopy(psi_ini)\n", - " hamiltonian_evolution_eig(H, psi, t_evo, range(n_qubits), n_qubits)\n", - " wf_save_eig[i] = psi\n", - " end = time.time()\n", - " print(\"Timing:\", end-start)\n", - "\n", - "print(\"First Hamiltonian again\")\n", - "for i in range(n_trials):\n", - " H = H0\n", - " start = time.time()\n", - " t_evo = torch.rand(batch_size)*0.5\n", - "\n", - " psi = copy.deepcopy(psi_ini)\n", - " hamiltonian_evolution_eig(H, psi, t_evo, range(n_qubits), n_qubits)\n", - " wf_save_eig[i] = psi\n", - " end = time.time()\n", - " print(\"Timing:\", end-start)" - ] - }, - { - "cell_type": "markdown", - "id": "a1aa927c-e366-4196-b837-b6c1682f6dab", - "metadata": {}, - "source": [ - "# Comparing the batched hamiltonian evolution:\n", - "Here we use general hermitian matrices as hamiltonians" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "dc4d878c-2380-4279-aa1e-51a4425d1344", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "import torch\n", - "import numpy as np\n", - "import networkx as nx\n", - "\n", - "from pyqtorch.core.circuit import QuantumCircuit\n", - "from pyqtorch.core.operation import hamiltonian_evolution, hamiltonian_evolution_eig\n", - "from pyqtorch.core.batched_operation import batched_hamiltonian_evolution, batched_hamiltonian_evolution_eig\n", - "import copy" - ] - }, - { - "cell_type": "markdown", - "id": "783f5036-60e5-4b6a-87f6-71ca81197f1b", - "metadata": {}, - "source": [ - "Let's create a batch of hamiltonians that are just repeating the same hamiltonian." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "740a1a03-d72b-4648-93fe-492a3ae60574", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "N = 5\n", - "batch_size = 10\n", - "\n", - "qc = QuantumCircuit(N)\n", - "\n", - "psi = qc.uniform_state(batch_size)\n", - "psi_0 = copy.deepcopy(psi)\n", - "\n", - "H_batch = torch.zeros((2**N, 2**N, batch_size), dtype = torch.cdouble)\n", - "for i in range(batch_size):\n", - " H_0 = torch.randn((2**N, 2**N), dtype = torch.cdouble)\n", - " H = (H_0 + torch.conj(H_0.transpose(0, 1))).cdouble()\n", - " H_batch[...,i] = H" - ] - }, - { - "cell_type": "markdown", - "id": "7a4ce18d-fe39-4b83-9615-ae1c7639229f", - "metadata": {}, - "source": [ - "Now we create a batch of linearly increasing times." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "bb18b45b-8c18-46c8-8a01-cfc6bd05446e", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "t_size = batch_size\n", - "t_start = 0.0\n", - "t_end = 5.0\n", - "t_evo = torch.arange(t_start, t_end, (t_end-t_start)/t_size).cdouble()" - ] - }, - { - "cell_type": "markdown", - "id": "8a255ba0-6060-46da-9294-444e9b6ccf1a", - "metadata": {}, - "source": [ - "Now we evaluate the batched time-evolution, essentially evaluating the same generator for an increasing time. First with the RK4 method:" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "68c86af7-4539-40db-b169-1f23c7207b5a", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.07417893409729004\n" - ] - } - ], - "source": [ - "import time\n", - "psi = copy.deepcopy(psi_0)\n", - "start = time.time()\n", - "psi = batched_hamiltonian_evolution(H_batch, psi, t_evo, range(N), N)\n", - "end = time.time()\n", - "psi_save_rk = copy.deepcopy(psi)\n", - "print(end-start)" - ] - }, - { - "cell_type": "markdown", - "id": "701aa46a-cad9-4ad7-b057-a788c27ed53e", - "metadata": {}, - "source": [ - "And then with the eigenvalue method:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "21cea2c7-c9d3-496d-afe0-eec03f7293c0", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.005017757415771484\n" - ] - } - ], - "source": [ - "psi = copy.deepcopy(psi_0)\n", - "start = time.time()\n", - "psi = batched_hamiltonian_evolution_eig(H_batch, psi, t_evo, range(N), N)\n", - "end = time.time()\n", - "psi_save_eig = copy.deepcopy(psi)\n", - "print(end-start)" - ] - }, - { - "cell_type": "markdown", - "id": "a3b154c4-7713-4b0e-9174-9e9a2a250e5f", - "metadata": {}, - "source": [ - "Now we can compare the wavefunction normalization of both as t increases:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "88112d4d-23b6-4e11-b302-fe289cf2e907", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "t_range = t_evo.real\n", - "\n", - "norm_rk = torch.tensor([(abs(psi_save_rk[...,b])**2).sum() for b in range(batch_size)])\n", - "norm_eig = torch.tensor([(abs(psi_save_eig[...,b])**2).sum() for b in range(batch_size)])\n", - "\n", - "plt.plot(t_range, norm_rk, label = \"RK4\")\n", - "plt.plot(t_range, norm_eig, label = \"Eig\")\n", - "plt.ylim(0.95, 1.01)\n", - "plt.ylabel(\"Normalization ||^2\")\n", - "plt.xlabel(\"Time t\")\n", - "plt.legend()" - ] - }, - { - "cell_type": "markdown", - "id": "af5291ff-8c0c-4fe6-a7c4-5e53c448809b", - "metadata": {}, - "source": [ - "And also the difference between the wavefunctions as t increases:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "14d90e9e-049c-4754-899d-cadbc4aa3a5b", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "Text(0.5, 0, 'Time t')" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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t+aujSykqVWL/FIVACA+hP1IREREhpgckt59913ccccd8Pb2xtKlS7F06VL4+PjgzjvvRHp6uiVqJDPLK6lDu05AgI87gvr3E7scIiIiq2TydNsrr7yCt956C4sXL9Yfe/rpp3HLLbfglVdewVNPPWXWAsn8OvuR4kL7c1NbIiKiGzB5JKmurg533HFHt+MzZ86ESqUyS1FkWZ39SOO4VxsREdENmRyS5syZg127dnU7/tlnn+Huu+82S1FkOVqdgGP6pm32IxEREd2IUdNt77zzjv7rkSNHYu3atcjMzMTEiRMBAD/88AMOHTqE5557zjJVktkUVtSjvqUdXm7OGB7gLXY5REREVksiCILwSycNGTLEuItJJDh//nyvi7IFarUaMpkMKpUKPj4+YpdjtH9kX8DLn53ElHA5/vGbBLHLISIi6lOmfH4bNZJUXFxslsJIfEevrbTNW/+JiIhurkfrJBly+vRpPP/88+a6HFlI551t47iIJBER0U31KiRpNBp88MEHmDRpEkaNGoU9e/aYqy6ygNK6JpSpmuEklSA22FfscoiIiKxaj0LSoUOHsGDBAigUCvzud7/DpEmTcOrUKeTn55u7PjKjzlGkUYE+8HDt0Y40REREDsPokFRVVYXXXnsNw4cPx/333w9fX19kZmZCKpViwYIFGD58uCXrJDPIYT8SERGR0YweTggJCcH999+Pt99+GzNmzIBUarZ2JuojR9mPREREZDSjk05ISAi+//57ZGVloaioyJI1kQWomtpQWFkPoGM7EiIiIro5o0NSQUEB/vnPf6K8vBzjxo1DXFwc3nrrLQDg/l824MdLVyEIQMgAD/h7u4tdDhERkdUzac7slltuwZYtW1BeXo7f//732LFjB7RaLZ588km8//77qK6utlSd1EvsRyIiIjJNjxqLvLy8sHDhQhw+fBgnT55EXFwcXnrpJQQGBpq7PjKTzn6keE61ERERGaXX3dcjRozA66+/jtLSUmzfvt0cNZGZtbbrcPxyHQA2bRMRERnLqJCk0Wh+8RxnZ2fce++9Rp9PfedkmQrNbTr093DBsIFeYpdDRERkE4wKSWFhYXj11VdRXl5+w3MEQcA333yDpKQkvPPOO2YrkHqvsx8pLsSPTfZERERGMmqdpMzMTPzpT3/CypUrERMTg/j4eAQGBsLd3R1Xr17FqVOnkJ2d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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "diff = torch.tensor([torch.max(abs(psi_save_rk[..., b] - psi_save_eig[..., b])) for b in range(batch_size)])\n", - "\n", - "plt.plot(t_range, diff)\n", - "plt.yscale(\"log\")\n", - "plt.ylabel(\"Max(Abs(|psi_rk4> - |psi_eig>))\")\n", - "plt.xlabel(\"Time t\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8453624f-a170-4610-b908-1181abe038b5", - "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.9.15" - }, - "vscode": { - "interpreter": { - "hash": "50935ae23c6784d55197a912635e8caab0c12202c38f5b44c7779b3e5667ccc0" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/deprecated/state_evolution.ipynb b/docs/deprecated/state_evolution.ipynb deleted file mode 100644 index 5a2173b0..00000000 --- a/docs/deprecated/state_evolution.ipynb +++ /dev/null @@ -1,264 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This tutorial shows how to apply the `hamiltonian_evolution` operator to evolve the quantum state using an approximated but efficient matrix exponentiation." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/mdagrada/.cache/pypoetry/virtualenvs/qucint-YinaOvwL-py3.8/lib/python3.8/site-packages/tqdm/auto.py:22: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n" - ] - } - ], - "source": [ - "from pyqtorch.matrices import single_Z, ZZ\n", - "from pyqtorch.ansatz import AlternateLayerAnsatz, OneLayerXRotation, OneLayerZRotation, OneLayerEntanglingAnsatz\n", - "from pyqtorch.core.circuit import QuantumCircuit" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import torch as th\n", - "import numpy as np\n", - "import copy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We start initialising the `QuantumCircuit` instance in order to observe the typical shape of an input/output state in the PyQ format" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "torch.Size([2, 2, 2, 2, 1])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "N = 4\n", - "qc = QuantumCircuit(N)\n", - "psi = qc.uniform_state(1)\n", - "psi_0 = copy.deepcopy(psi)\n", - "psi_0.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We perform a deepcopy of `psi` as some operations below (e.g. `hamiltonian evolution`) will overwrite it." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "def overlap(state1, state2):\n", - " N = len(state1.shape)-1\n", - " state1_T = th.transpose(state1, N, 0)\n", - " overlap = th.tensordot(state1.T.conj(), state2, dims=N)\n", - " return float(th.abs(overlap**2).flatten())" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Initial overlap: 1.0\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_601/4268046128.py:4: UserWarning: The use of `x.T` on tensors of dimension other than 2 to reverse their shape is deprecated and it will throw an error in a future release. Consider `x.mT` to transpose batches of matrices or `x.permute(*torch.arange(x.ndim - 1, -1, -1))` to reverse the dimensions of a tensor. (Triggered internally at ../aten/src/ATen/native/TensorShape.cpp:3277.)\n", - " overlap = th.tensordot(state1.T.conj(), state2, dims=N)\n" - ] - } - ], - "source": [ - "print(\"Initial overlap: \", overlap(psi_0, psi_0))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Hamiltonian Evolution" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "from pyqtorch.core.operation import hamiltonian_evolution" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let us define a simple Hamiltonian for the 4-qubits system, like a $\\sigma_Z \\otimes \\sigma_Z$, in dense format as a $(N^2, N^2)$ tensor" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "torch.Size([16, 16])" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sigmaz = th.diag(th.tensor([1.0, -1.0], dtype=th.cdouble))\n", - "Hbase = th.kron(sigmaz, sigmaz)\n", - "H = th.kron(Hbase, Hbase)\n", - "H.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The overlap with itself should stay 1 after evolving for $t=0$, let's check this" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "t_evo = th.tensor([0], dtype=th.cdouble)\n", - "psi = hamiltonian_evolution(H,\n", - " psi, t_evo,\n", - " range(N), N)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overlap after tensor([0.+0.j], dtype=torch.complex128) : 1.0\n" - ] - } - ], - "source": [ - "print(f\"Overlap after {t_evo} : \", overlap(psi, psi_0))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's now evolve the state for a time $t = \\pi/4$ and check that the overlap matches the expected value of 0.5." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "t_evo = th.tensor([th.pi/4], dtype=th.cdouble)\n", - "psi = hamiltonian_evolution(H,\n", - " psi, t_evo,\n", - " range(N), N\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overlap after tensor([0.7854+0.j], dtype=torch.complex128) : 0.50000000002474\n" - ] - } - ], - "source": [ - "print(f\"Overlap after {t_evo} : \", overlap(psi, psi_0))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.5" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "50935ae23c6784d55197a912635e8caab0c12202c38f5b44c7779b3e5667ccc0" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/docsutils.py b/docs/docsutils.py new file mode 100644 index 00000000..c78fab7b --- /dev/null +++ b/docs/docsutils.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from io import StringIO + +from matplotlib.figure import Figure + + +def fig_to_html(fig: Figure) -> str: + buffer = StringIO() + fig.savefig(buffer, format="svg") + return buffer.getvalue() diff --git a/docs/essentials.ipynb b/docs/essentials.ipynb deleted file mode 100644 index 17f8126b..00000000 --- a/docs/essentials.ipynb +++ /dev/null @@ -1,259 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Gates\n", - "\n", - "`pyqtorch` implements most of the commonly used gates like Pauli gates, rotation\n", - "gates, and controlled gates. Every gate accepts a sequence of `qubits` on which\n", - "it operates and a total number `n_qubits` of the state that it will operate on:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import torch\n", - "import pyqtorch.modules as pyq\n", - "\n", - "gate = pyq.X(qubits=[0], n_qubits=1)\n", - "z = pyq.zero_state(n_qubits=1)\n", - "\n", - "gate(z)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "gate = pyq.CNOT(qubits=[0,1], n_qubits=2)\n", - "z = pyq.zero_state(n_qubits=2)\n", - "gate(z)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "z.shape" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In `pyqtorch` the state is a `n_qubit+1` dimensional `Tensor`, for example a\n", - "state with 3 qubits has the shape `(2, 2, 2, 1)` (i.e. one dimension for each\n", - "qubit, plus one dimension for the batch size).\n", - "\n", - "\n", - "_**NOTE:**_ We always work with batched state in `pyqtorch`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "z = pyq.zero_state(n_qubits=3)\n", - "print(z.shape)\n", - "z = pyq.zero_state(n_qubits=3, batch_size=5)\n", - "print(z.shape)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Circuits\n", - "### `QuantumCircuit`\n", - "\n", - "To compose multiple gates we use a `QuantumCircuit` which is constructed from\n", - "a list of operations." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "circ = pyq.QuantumCircuit(\n", - " n_qubits=2,\n", - " operations=[\n", - " pyq.X([0], 2),\n", - " pyq.CNOT([0,1], 2)\n", - " ]\n", - ")\n", - "\n", - "z = pyq.zero_state(2)\n", - "circ(z)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Every gate and circuit in `pyqtorch` accepts a state and an optional tensor of angles.\n", - "If the gate/circuit does not depend on any angles, the second argument is ignored." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "theta = torch.rand(1)\n", - "circ(z, theta) # theta is ignored" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "circ = pyq.QuantumCircuit(\n", - " n_qubits=2,\n", - " operations=[\n", - " pyq.RX([0], 2), # rotation instead of X gate\n", - " pyq.CNOT([0,1], 2)\n", - " ]\n", - ")\n", - "\n", - "circ(z, theta) # theta is used!" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The vanilla `QuantumCircuit` is always passing the same `theta` tensor to its operations, meaning\n", - "the `forward` method of the circuit is:\n", - "```python\n", - "class QuantumCircuit(torch.nn.Module):\n", - "\n", - " # ...\n", - "\n", - " def forward(self, state: torch.Tensor, thetas: torch.Tensor = None) -> torch.Tensor:\n", - " for op in self.operations:\n", - " state = op(state, thetas)\n", - " return state\n", - "```\n", - "\n", - "The `FeaturemapLayer` is a convenience constructor for a `QuantumCircuit` which accepts an operation\n", - "to put on every qubit." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "circ = pyq.FeaturemapLayer(n_qubits=3, Op=pyq.RX)\n", - "print(circ)\n", - "\n", - "states = pyq.zero_state(n_qubits=3, batch_size=4)\n", - "inputs = torch.rand(4)\n", - "\n", - "# the same batch of inputs are passed to the operations\n", - "circ(states, inputs).shape" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Trainable `QuantumCircuit`s aka `VariationalLayer`s\n", - "\n", - "If you want the angles of your circuit to be trainable you can use a `VariationalLayer`.\n", - "The `VariationalLayer` ignores the second input (because it has trainable angle parameters)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "circ = pyq.VariationalLayer(n_qubits=3, Op=pyq.RX)\n", - "\n", - "state = pyq.zero_state(3)\n", - "this_argument_is_ignored = None\n", - "circ(state, this_argument_is_ignored)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Composing `QuantumCircuit`s\n", - "\n", - "As every gate and circuit in `pyqtorch` accept the same arguments we can easily\n", - "compose them to larger circuits, i.e. to implement a hardware efficient ansatz:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def hea(n_qubits: int, n_layers: int) -> pyq.QuantumCircuit:\n", - " ops = []\n", - " for _ in range(n_layers):\n", - " ops.append(pyq.VariationalLayer(n_qubits, pyq.RX))\n", - " ops.append(pyq.VariationalLayer(n_qubits, pyq.RY))\n", - " ops.append(pyq.VariationalLayer(n_qubits, pyq.RX))\n", - " ops.append(pyq.EntanglingLayer(n_qubits))\n", - " return pyq.QuantumCircuit(n_qubits, ops)\n", - "\n", - "circ = hea(3,2)\n", - "print(circ)\n", - "\n", - "state = pyq.zero_state(3)\n", - "circ(state)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "qucint", - "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.9.16" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/fit_function.ipynb b/docs/fit_function.ipynb deleted file mode 100644 index 4c4375c5..00000000 --- a/docs/fit_function.ipynb +++ /dev/null @@ -1,485 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Fitting a 1D function" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import torch\n", - "\n", - "import pyqtorch.modules as pyq" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's define a target function we want to fit." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "\n", - "def target_function(x, degree=3):\n", - " result = 0\n", - " for i in range(degree):\n", - " result += torch.cos(i*x) + torch.sin(i*x)\n", - " return .05 * result\n", - "\n", - "x = torch.tensor(np.linspace(0, 10, 100))\n", - "target_y = target_function(x, 5)\n", - "\n", - "plt.plot(x.numpy(), target_y.numpy(), label=\"truth\")\n", - "plt.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To fit this function with a QNN we need an entangling ansatz. We will use a layer of `U`-gates and a layer of `CNOT`s:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "QuantumCircuit(\n", - " (operations): ModuleList(\n", - " (0): VariationalLayer(\n", - " (operations): ModuleList(\n", - " (0): U(qubits=[0], n_qubits=3)\n", - " (1): U(qubits=[1], n_qubits=3)\n", - " (2): U(qubits=[2], n_qubits=3)\n", - " )\n", - " )\n", - " (1): EntanglingLayer(\n", - " (operations): ModuleList(\n", - " (0): CNOT(qubits=[0, 1], n_qubits=3)\n", - " (1): CNOT(qubits=[1, 2], n_qubits=3)\n", - " (2): CNOT(qubits=[2, 0], n_qubits=3)\n", - " )\n", - " )\n", - " )\n", - ")" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def ULayerAnsatz(n_qubits, n_layers):\n", - " ops = []\n", - " for _ in range(n_layers):\n", - " ops.append(pyq.VariationalLayer(n_qubits, pyq.U))\n", - " ops.append(pyq.EntanglingLayer(n_qubits))\n", - " return pyq.QuantumCircuit(n_qubits, ops)\n", - "\n", - "ULayerAnsatz(3,1)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can define a QNN by implementing a custom `torch.nn.Module`." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "class Model(torch.nn.Module):\n", - "\n", - " def __init__(self, n_qubits, n_layers):\n", - " super().__init__()\n", - " self.n_qubits = n_qubits\n", - " self.ansatz1 = ULayerAnsatz(n_qubits, n_layers)\n", - " self.embedding = pyq.FeaturemapLayer(n_qubits, pyq.RX)\n", - " self.ansatz2 = ULayerAnsatz(n_qubits, n_layers)\n", - " self.observable = pyq.Z([0], n_qubits)\n", - " \n", - " def forward(self, x):\n", - " batch_size = len(x)\n", - " state = self.ansatz1.init_state(batch_size)\n", - " \n", - " state = self.ansatz1(state)\n", - " state = self.embedding(state, x)\n", - " state = self.ansatz2(state)\n", - " \n", - " new_state = self.observable(state)\n", - " \n", - " state = state.reshape((2**self.n_qubits, batch_size))\n", - " new_state = new_state.reshape((2**self.n_qubits, batch_size))\n", - " return torch.real(torch.sum(torch.conj(state) * new_state, axis=0))" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's verify that we are getting reasonable outputs from our untrained QNN" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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Sy65hN27dxttVtBz3333YbFYuP322402JegoKSnhG9/4BikpKURFRTF58mQ2bNhgtFlBhcPh4K677mL48OFERUUxcuRI7rnnHo/m4wh988knn3DhhReSnZ2NxWLh9ddfP+77mqbxy1/+kqysLKKioli8eDF79+71i23ivHjASy+9xLJly7j77rvZtGkTU6dOZcmSJVRUVBhtWlCxatUqbrnlFr744gs++OADOjo6OOecc2hqajLatKBl/fr1/O1vf2PKlClGmxJ0HDt2jAULFhAeHs67777Ljh07eOihh0hKSjLatKDi/vvv569//St//vOf2blzJ/fffz8PPPAAjz32mNGmBTxNTU1MnTqVxx9/vMfvP/DAAzz66KM88cQTrF27lpiYGJYsWUJra6vvjdOEfpkzZ452yy23uP/scDi07Oxs7d577zXQquCnoqJCA7RVq1YZbUpQ0tDQoI0ePVr74IMPtNNPP1277bbbjDYpqPjpT3+qnXrqqUabEfRccMEF2g033HDca5dddpl2zTXXGGRRcAJor732mvvPTqdTy8zM1P7whz+4X6utrdUiIiK0F154wef2SOSlH9rb29m4cSOLFy92v2a1Wlm8eDFr1qwx0LLgp66uDoDk5GSDLQlObrnlFi644ILj/m8L3uPNN99k1qxZXHHFFaSnpzN9+nSeeuopo80KOubPn8+KFSvYs2cPAJs3b+bTTz/lvPPOM9iy4ObgwYOUlZUdt34kJCQwd+5cv3w2Bt1gRm9TVVWFw+EgIyPjuNczMjLYtWuXQVYFP06nk9tvv50FCxYwadIko80JOl588UU2bdrE+vXrjTYlaDlw4AB//etfWbZsGT/72c9Yv3493//+97Hb7Vx33XVGmxc03HHHHdTX1zNu3DhsNhsOh4Pf/e53XHPNNUabFtSUlZUB9PjZqH/Pl4jzIpiSW265hW3btvHpp58abUrQUVRUxG233cYHH3xAZGSk0eYELU6nk1mzZvH73/8egOnTp7Nt2zaeeOIJcV68yH/+8x+ee+45nn/+eSZOnEhhYSG333472dnZ8nMOYiRt1A+pqanYbDbKy8uPe728vJzMzEyDrApubr31Vt5++20+/vhjcnNzjTYn6Ni4cSMVFRXMmDGDsLAwwsLCWLVqFY8++ihhYWE4HA6jTQwKsrKymDBhwnGvjR8/niNHjhhkUXDy4x//mDvuuIOrrrqKyZMn881vfpMf/OAH3HvvvUabFtTon39GfTaK89IPdrudmTNnsmLFCvdrTqeTFStWMG/ePAMtCz40TePWW2/ltdde46OPPmL48OFGmxSUnHXWWWzdupXCwkL316xZs7jmmmsoLCzEZrMZbWJQsGDBgpNa/ffs2cOwYcMMsig4aW5uxmo9/qPMZrPhdDoNsig0GD58OJmZmcd9NtbX17N27Vq/fDZK2sgDli1bxnXXXcesWbOYM2cOjzzyCE1NTSxdutRo04KKW265heeff5433niDuLg4d940ISGBqKgog60LHuLi4k6qI4qJiSElJUXqi7zID37wA+bPn8/vf/97vva1r7Fu3TqefPJJnnzySaNNCyouvPBCfve735Gfn8/EiRP58ssvefjhh7nhhhuMNi3gaWxsZN++fe4/Hzx4kMLCQpKTk8nPz+f222/nt7/9LaNHj2b48OHcddddZGdnc8kll/jeOJ/3MwUJjz32mJafn6/Z7XZtzpw52hdffGG0SUEH0OPXP/7xD6NNC3qkVdo3vPXWW9qkSZO0iIgIbdy4cdqTTz5ptElBR319vXbbbbdp+fn5WmRkpDZixAjt5z//udbW1ma0aQHPxx9/3OOafN1112maptql77rrLi0jI0OLiIjQzjrrLG337t1+sc2iaSJDKAiCIAhC4CA1L4IgCIIgBBTivAiCIAiCEFCI8yIIgiAIQkAhzosgCIIgCAGFOC+CIAiCIAQU4rwIgiAIghBQiPMiCIIgCEJAIc6LIAiCIAgBhTgvgiAIgiAEFOK8CIIgCIIQUIjzIgiCIAhCQCHOiyAIgiAIAcX/B2GLVghTU8fQAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "n_qubits = 5\n", - "n_layers = 3\n", - "model = Model(n_qubits, n_layers)\n", - "\n", - "with torch.no_grad():\n", - " y = model(x)\n", - "\n", - "plt.plot(x.numpy(), target_y.numpy(), label=\"truth\")\n", - "plt.plot(x.numpy(), y.numpy(), label=\"initial\")\n", - "plt.legend()\n", - "plt.show()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Our QNN is implemented as a `torch.nn.Module` so we can use the usual `torch` optimizers to train it." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Epoch 001 | Loss 1.181170973931877e-05\n", - "Epoch 002 | Loss 3.9005150204328295e-05\n", - "Epoch 003 | Loss 0.0005210030244242805\n", - "Epoch 004 | Loss 6.012409726695054e-05\n", - "Epoch 005 | Loss 0.00021009194032554318\n", - "Epoch 006 | Loss 0.0002914833038541067\n", - "Epoch 007 | Loss 0.0001454984851627904\n", - "Epoch 008 | Loss 4.142681408782311e-05\n", - "Epoch 009 | Loss 9.054882419142336e-05\n", - "Epoch 010 | Loss 0.0001531790494452143\n", - "Epoch 011 | Loss 0.00012015653011887463\n", - "Epoch 012 | Loss 5.790713974401939e-05\n", - "Epoch 013 | Loss 3.846728702473956e-05\n", - "Epoch 014 | Loss 5.630471569536954e-05\n", - "Epoch 015 | Loss 7.700979061854381e-05\n", - "Epoch 016 | Loss 7.391532378356758e-05\n", - "Epoch 017 | Loss 4.612078963466576e-05\n", - "Epoch 018 | Loss 2.155939995688723e-05\n", - "Epoch 019 | Loss 2.3721610576853985e-05\n", - "Epoch 020 | Loss 4.3170004830746536e-05\n", - "Epoch 021 | Loss 5.069962559255113e-05\n", - "Epoch 022 | Loss 3.5152058993235534e-05\n", - "Epoch 023 | Loss 1.4055374744605247e-05\n", - "Epoch 024 | Loss 1.023692829664193e-05\n", - "Epoch 025 | Loss 2.4446698606623847e-05\n", - "Epoch 026 | Loss 3.408834997147914e-05\n", - "Epoch 027 | Loss 2.4865126936362327e-05\n", - "Epoch 028 | Loss 9.659374207355973e-06\n", - "Epoch 029 | Loss 6.950788871336306e-06\n", - "Epoch 030 | Loss 1.566768989272486e-05\n", - "Epoch 031 | Loss 2.099277061607817e-05\n", - "Epoch 032 | Loss 1.6212614075325447e-05\n", - "Epoch 033 | Loss 8.235768022404368e-06\n", - "Epoch 034 | Loss 6.009959075292303e-06\n", - "Epoch 035 | Loss 9.772417521725285e-06\n", - "Epoch 036 | Loss 1.2892382127904045e-05\n", - "Epoch 037 | Loss 1.1361810341757317e-05\n", - "Epoch 038 | Loss 7.2561572882857865e-06\n", - "Epoch 039 | Loss 4.821231443489856e-06\n", - "Epoch 040 | Loss 6.128272875842876e-06\n", - "Epoch 041 | Loss 8.843188084005391e-06\n", - "Epoch 042 | Loss 8.675418546268711e-06\n", - "Epoch 043 | Loss 5.450684118847346e-06\n", - "Epoch 044 | Loss 3.5110836783759226e-06\n", - "Epoch 045 | Loss 4.989386678750474e-06\n", - "Epoch 046 | Loss 6.898259805053778e-06\n", - "Epoch 047 | Loss 6.05054456967633e-06\n", - "Epoch 048 | Loss 3.7389955465282206e-06\n", - "Epoch 049 | Loss 3.273212519167249e-06\n", - "Epoch 050 | Loss 4.637559523242552e-06\n", - "Epoch 051 | Loss 5.165131301134411e-06\n", - "Epoch 052 | Loss 4.142896350302605e-06\n", - "Epoch 053 | Loss 3.1492780545134018e-06\n", - "Epoch 054 | Loss 3.233757188669417e-06\n", - "Epoch 055 | Loss 3.86524696418792e-06\n", - "Epoch 056 | Loss 3.953219614950671e-06\n", - "Epoch 057 | Loss 3.312011206526524e-06\n", - "Epoch 058 | Loss 2.7661753462439286e-06\n", - "Epoch 059 | Loss 2.929670133548e-06\n", - "Epoch 060 | Loss 3.3791616358340403e-06\n", - "Epoch 061 | Loss 3.238319199906219e-06\n", - "Epoch 062 | Loss 2.630986577566763e-06\n", - "Epoch 063 | Loss 2.4992796964562005e-06\n", - "Epoch 064 | Loss 2.8735530062625166e-06\n", - "Epoch 065 | Loss 2.938593815700879e-06\n", - "Epoch 066 | Loss 2.5476755194736197e-06\n", - "Epoch 067 | Loss 2.3241505411727017e-06\n", - "Epoch 068 | Loss 2.49713839896336e-06\n", - "Epoch 069 | Loss 2.6112011957839276e-06\n", - "Epoch 070 | Loss 2.4362527269610276e-06\n", - "Epoch 071 | Loss 2.24253269756204e-06\n", - "Epoch 072 | Loss 2.2373816058577022e-06\n", - "Epoch 073 | Loss 2.3310248995766054e-06\n", - "Epoch 074 | Loss 2.3077427496625516e-06\n", - "Epoch 075 | Loss 2.1503126672635597e-06\n", - "Epoch 076 | Loss 2.070910242097326e-06\n", - "Epoch 077 | Loss 2.137149272933625e-06\n", - "Epoch 078 | Loss 2.1596597107860127e-06\n", - "Epoch 079 | Loss 2.0397936834282285e-06\n", - "Epoch 080 | Loss 1.9556617368367096e-06\n", - "Epoch 081 | Loss 1.998179918829005e-06\n", - "Epoch 082 | Loss 2.0117531938650416e-06\n", - "Epoch 083 | Loss 1.93268494654725e-06\n", - "Epoch 084 | Loss 1.8697853805020282e-06\n", - "Epoch 085 | Loss 1.875957033063329e-06\n", - "Epoch 086 | Loss 1.8812430094662359e-06\n", - "Epoch 087 | Loss 1.8393870837550013e-06\n", - "Epoch 088 | Loss 1.787910412014617e-06\n", - "Epoch 089 | Loss 1.7701482382035251e-06\n", - "Epoch 090 | Loss 1.7752261255685842e-06\n", - "Epoch 091 | Loss 1.7504566547702705e-06\n", - "Epoch 092 | Loss 1.7019486718406315e-06\n", - "Epoch 093 | Loss 1.6835082115283767e-06\n", - "Epoch 094 | Loss 1.685667875198429e-06\n", - "Epoch 095 | Loss 1.6616583019769176e-06\n", - "Epoch 096 | Loss 1.6231601566709535e-06\n", - "Epoch 097 | Loss 1.60813362494993e-06\n", - "Epoch 098 | Loss 1.6019701482892067e-06\n", - "Epoch 099 | Loss 1.5803956915397127e-06\n", - "Epoch 100 | Loss 1.5527873527936183e-06\n", - "Epoch 101 | Loss 1.5352883431312178e-06\n", - "Epoch 102 | Loss 1.5252310209689772e-06\n", - "Epoch 103 | Loss 1.5084573236758253e-06\n", - "Epoch 104 | Loss 1.4843679595562227e-06\n", - "Epoch 105 | Loss 1.4667658608496229e-06\n", - "Epoch 106 | Loss 1.457148855226587e-06\n", - "Epoch 107 | Loss 1.4404304018817315e-06\n", - "Epoch 108 | Loss 1.4187656091462116e-06\n", - "Epoch 109 | Loss 1.4045047593157939e-06\n", - "Epoch 110 | Loss 1.3931682693016124e-06\n", - "Epoch 111 | Loss 1.3763410548891802e-06\n", - "Epoch 112 | Loss 1.3585907789414708e-06\n", - "Epoch 113 | Loss 1.3450608769260442e-06\n", - "Epoch 114 | Loss 1.332534011814191e-06\n", - "Epoch 115 | Loss 1.317529909690219e-06\n", - "Epoch 116 | Loss 1.301470982727387e-06\n", - "Epoch 117 | Loss 1.2883992098664887e-06\n", - "Epoch 118 | Loss 1.2764695970430059e-06\n", - "Epoch 119 | Loss 1.2619454042812358e-06\n", - "Epoch 120 | Loss 1.247318667750338e-06\n", - "Epoch 121 | Loss 1.235492552299088e-06\n", - "Epoch 122 | Loss 1.2232337173189462e-06\n", - "Epoch 123 | Loss 1.2094041838489128e-06\n", - "Epoch 124 | Loss 1.196602229533154e-06\n", - "Epoch 125 | Loss 1.185027851699964e-06\n", - "Epoch 126 | Loss 1.1729201462357814e-06\n", - "Epoch 127 | Loss 1.1602693027487138e-06\n", - "Epoch 128 | Loss 1.1482910988692847e-06\n", - "Epoch 129 | Loss 1.1371439541573138e-06\n", - "Epoch 130 | Loss 1.1255718985465116e-06\n", - "Epoch 131 | Loss 1.1136108149941372e-06\n", - "Epoch 132 | Loss 1.1025357900284203e-06\n", - "Epoch 133 | Loss 1.091830958157926e-06\n", - "Epoch 134 | Loss 1.0805734231640713e-06\n", - "Epoch 135 | Loss 1.0695479487719928e-06\n", - "Epoch 136 | Loss 1.059127192210933e-06\n", - "Epoch 137 | Loss 1.0486653310712814e-06\n", - "Epoch 138 | Loss 1.0380690084037267e-06\n", - "Epoch 139 | Loss 1.0277505705549728e-06\n", - "Epoch 140 | Loss 1.0177783820600243e-06\n", - "Epoch 141 | Loss 1.0077843803092286e-06\n", - "Epoch 142 | Loss 9.977294830491515e-07\n", - "Epoch 143 | Loss 9.880084245920512e-07\n", - "Epoch 144 | Loss 9.78507612080833e-07\n", - "Epoch 145 | Loss 9.688991424059435e-07\n", - "Epoch 146 | Loss 9.59433082323173e-07\n", - "Epoch 147 | Loss 9.502412901981052e-07\n", - "Epoch 148 | Loss 9.410977540121816e-07\n", - "Epoch 149 | Loss 9.319727858151028e-07\n", - "Epoch 150 | Loss 9.230320009485983e-07\n", - "Epoch 151 | Loss 9.142515908872536e-07\n", - "Epoch 152 | Loss 9.055176796050045e-07\n", - "Epoch 153 | Loss 8.968476493496918e-07\n", - "Epoch 154 | Loss 8.883585070062157e-07\n", - "Epoch 155 | Loss 8.799798574579335e-07\n", - "Epoch 156 | Loss 8.716306904438497e-07\n", - "Epoch 157 | Loss 8.634115370027542e-07\n", - "Epoch 158 | Loss 8.553286106000076e-07\n", - "Epoch 159 | Loss 8.473066878770816e-07\n", - "Epoch 160 | Loss 8.393611035486784e-07\n", - "Epoch 161 | Loss 8.315383832451852e-07\n", - "Epoch 162 | Loss 8.23812730022084e-07\n", - "Epoch 163 | Loss 8.161585244572439e-07\n", - "Epoch 164 | Loss 8.085853311389707e-07\n", - "Epoch 165 | Loss 8.011311389518617e-07\n", - "Epoch 166 | Loss 7.937509776200442e-07\n", - "Epoch 167 | Loss 7.864414200490334e-07\n", - "Epoch 168 | Loss 7.792321202346505e-07\n", - "Epoch 169 | Loss 7.721129420068149e-07\n", - "Epoch 170 | Loss 7.650566056859965e-07\n", - "Epoch 171 | Loss 7.580867392307161e-07\n", - "Epoch 172 | Loss 7.512019255765706e-07\n", - "Epoch 173 | Loss 7.44392871495942e-07\n", - "Epoch 174 | Loss 7.376554924382388e-07\n", - "Epoch 175 | Loss 7.310041859797791e-07\n", - "Epoch 176 | Loss 7.244223352825202e-07\n", - "Epoch 177 | Loss 7.179107583362865e-07\n", - "Epoch 178 | Loss 7.114729205692211e-07\n", - "Epoch 179 | Loss 7.051138044328317e-07\n", - "Epoch 180 | Loss 6.988167086620162e-07\n", - "Epoch 181 | Loss 6.925921661054752e-07\n", - "Epoch 182 | Loss 6.864380917059668e-07\n", - "Epoch 183 | Loss 6.803475742422391e-07\n", - "Epoch 184 | Loss 6.743241679721031e-07\n", - "Epoch 185 | Loss 6.683699407928609e-07\n", - "Epoch 186 | Loss 6.624817735278676e-07\n", - "Epoch 187 | Loss 6.566483433856844e-07\n", - "Epoch 188 | Loss 6.508865052027779e-07\n", - "Epoch 189 | Loss 6.451858149751575e-07\n", - "Epoch 190 | Loss 6.395429157077213e-07\n", - "Epoch 191 | Loss 6.339613565507968e-07\n", - "Epoch 192 | Loss 6.284421739744305e-07\n", - "Epoch 193 | Loss 6.229794300541488e-07\n", - "Epoch 194 | Loss 6.175748470592854e-07\n", - "Epoch 195 | Loss 6.122284465298443e-07\n", - "Epoch 196 | Loss 6.069336984847245e-07\n", - "Epoch 197 | Loss 6.016991132192018e-07\n", - "Epoch 198 | Loss 5.965180591195436e-07\n", - "Epoch 199 | Loss 5.913902703863853e-07\n", - "Epoch 200 | Loss 5.863220844515445e-07\n" - ] - } - ], - "source": [ - "import torch.nn.functional as F\n", - "\n", - "optimizer = torch.optim.Adam(model.parameters(), lr=.01)\n", - "epochs = 200\n", - "\n", - "for epoch in range(epochs):\n", - " optimizer.zero_grad()\n", - " y_pred = model(x)\n", - " loss = F.mse_loss(target_y, y_pred)\n", - " loss.backward()\n", - " optimizer.step()\n", - " print(f\"Epoch {epoch+1:03d} | Loss {loss}\")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can verify that the final prediction looks like the target function:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "with torch.no_grad():\n", - " y_final = model(x)\n", - "\n", - "plt.plot(x.numpy(), target_y.numpy(), label=\"truth\")\n", - "plt.plot(x.numpy(), y.numpy(), label=\"initial\")\n", - "plt.plot(x.numpy(), y_final.numpy(), \"--\", label=\"final\", linewidth=3)\n", - "plt.legend()\n", - "plt.show()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "qucint", - "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.9.16" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/gate_composition.ipynb b/docs/gate_composition.ipynb deleted file mode 100644 index 8c0a59d5..00000000 --- a/docs/gate_composition.ipynb +++ /dev/null @@ -1,129 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Arbitrary Gate Composition\n", - "\n", - "To create new arbitrary gates, one need only multiply two gates together, as per the following examples." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from pyqtorch.modules import RX, RY, RZ, U\n", - "from torch import pi, Tensor as tensor" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "QuantumCircuit(\n", - " (operations): ModuleList(\n", - " (0): RX(qubits=[0], n_qubits=1)\n", - " (1): RY(qubits=[0], n_qubits=1)\n", - " )\n", - ")\n" - ] - } - ], - "source": [ - "gateA = RX(qubits=[0], n_qubits=1)\n", - "gateB = RY(qubits=[0], n_qubits=1)\n", - "gateC = gateA*gateB\n", - "\n", - "print(gateC)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "QuantumCircuit(\n", - " (operations): ModuleList(\n", - " (0): RZ(qubits=[0], n_qubits=1)\n", - " (1): RY(qubits=[0], n_qubits=1)\n", - " (2): RZ(qubits=[0], n_qubits=1)\n", - " )\n", - ")\n" - ] - } - ], - "source": [ - "def customGate(*args, **kwargs):\n", - " return RZ(*args, **kwargs) * RY(*args, **kwargs) * RZ(*args, **kwargs)\n", - "\n", - "circ = customGate(qubits=[0], n_qubits=1)\n", - "\n", - "print(circ)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "QuantumCircuit(\n", - " (operations): ModuleList(\n", - " (0): RX(qubits=[0], n_qubits=1)\n", - " (1): RY(qubits=[0], n_qubits=1)\n", - " (2): RX(qubits=[0], n_qubits=1)\n", - " (3): RZ(qubits=[0], n_qubits=1)\n", - " )\n", - ")\n" - ] - } - ], - "source": [ - "def customGate(*args, **kwargs):\n", - " return (RX(*args, **kwargs) * RY(*args, **kwargs)) * (RX(*args, **kwargs) * RZ(*args, **kwargs))\n", - "\n", - "circ = customGate(qubits=[0], n_qubits=1)\n", - "\n", - "print(circ)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.1" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/hamevo.md b/docs/hamevo.md deleted file mode 100644 index ec5437f2..00000000 --- a/docs/hamevo.md +++ /dev/null @@ -1,115 +0,0 @@ -# Hamiltonian Evolution Module Documentation - -## Overview - -The Hamiltonian Evolution (`HamiltonianEvolution`) module is designed for performing quantum operations using different Hamiltonian evolution strategies, such as 4th order Runge-Kutta (RK4), Eigenvalue Decomposition, and Matrix Exponential. - -This module also features a function `diagonalize()` that performs an eigenvalue decomposition on a given Hamiltonian. This function checks if the input Hamiltonian is already diagonal and real before computing the decomposition, thus saving computational resources when the checks are met. - -## Class Definitions - -### `HamiltonianEvolution` - -This class is a PyTorch module designed to encapsulate Hamiltonian Evolution operations. The evolution operation performed by the module depends on the strategy specified through the `hamevo_type` attribute, which can be a member of the `HamEvoType` enumeration or an equivalent string ("RK4", "EIG", "EXP"). Default is set to HamEvoEXP. - -### `HamEvo` - -This class is the base class for Hamiltonian evolution operations, which performs the evolution operation using the Runge-Kutta 4 (RK4) numerical method. It provides `apply()` method that takes a quantum state tensor as an input and applies the Hamiltonian evolution operation. The `forward()` method provides a simplified interface for applying the operation. - -### `HamEvoEig` - -A subclass of `HamEvo`, this class performs the Hamiltonian evolution operation using the Eigenvalue Decomposition method. In addition to performing the eigenvalue decomposition, it also provides checks if the Hamiltonian is already diagonal, and if so, skips the decomposition computation. - -### `HamEvoExp` - -Another subclass of `HamEvo`, this class performs the Hamiltonian evolution operation using the Matrix Exponential method. For efficiency, it checks if all the Hamiltonians in the batch are diagonal and skips the computation of matrix exponentials if they are. - -## Enum Definitions - -### `HamEvoType` - -An enumeration to represent types of Hamiltonian Evolution, including RK4, Eigenvalue Decomposition, and Exponential. It contains the corresponding classes as the enumeration values. - -## Function Definitions - -### `diagonalize` - -A function to diagonalize a Hermitian Hamiltonian, returning eigenvalues and eigenvectors. It also performs checks to see if the Hamiltonian is already diagonal and if it's real to avoid unnecessary computations. - -## Examples - -The following examples show how to use the `HamiltonianEvolution` module: -
-
-Initialization of HamiltonianEvolution takes parameters (qubits, n_qubits, n_steps, hamevo_type) -
-Using the HamiltonianEvolution instance to evolve the state takes parameters (H, t, state) -
- -### Example 1: -```python -import torch -import pyqtorch.modules as pyq - -#Define initialization parameters -n_qubits = 2 -qubits = list(range(n_qubits)) - -# Define the Hamiltonian H (random for this example) -H = torch.randn((2**n_qubits, 2**n_qubits), dtype=torch.cdouble) - -# Make sure H is Hermitian as required for a Hamiltonian -H = (H + H.conj().T) / 2 - -# Define the initial state -state = pyq.uniform_state(n_qubits) - -# Define the evolution time tensor -t = torch.tensor([torch.pi / 4], dtype=torch.cdouble) - -# Instantiate HamiltonianEvolution with RK4 string input -hamiltonian_evolution = pyq.HamiltonianEvolution(qubits, n_qubits, 100, "RK4") - -# Use the HamiltonianEvolution instance to evolve the state -output_state_rk4 = hamiltonian_evolution(H, t, state) - -``` - - - -### Example 2: -```python -import torch -import pyqtorch.modules as pyq - -# Define initialization parameters -n_qubits = 1 -qubits = list(range(n_qubits)) -n_steps = 100 - -# Define the Hamiltonian H for a qubit in a z-direction magnetic field -H = torch.tensor([[0.5, 0], [0, -0.5]], dtype=torch.cdouble) - -# Define the initial state as |0> (you could also try with |1> or a superposition state) -state = torch.tensor([[1], [0]], dtype=torch.cdouble) - -# Define the evolution time tensor -t = torch.tensor([torch.pi / 2], dtype=torch.cdouble) - -# Instantiate HamiltonianEvolution with HamEvoType input -H_evol = pyq.HamiltonianEvolution(qubits, n_qubits, n_steps, pyq.HamEvoType.EIG) - -# Use the HamiltonianEvolution instance to evolve the state -output_state_eig = H_evol(H, t, state) - -# Print the output state -print(output_state_eig) - -# Now compare the output state with the expected result: e^(-i * H * t) * |0> = [[e^(-i*t/2)], [0]] -expected_state = torch.tensor([[torch.exp(-1j * t / 2)], [0]], dtype=torch.cdouble) -print(expected_state) - -# Check if the output_state is close to the expected_state -print(torch.allclose(output_state_rk4, expected_state)) - -``` diff --git a/docs/index.md b/docs/index.md index bc643ca7..b68a85a9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,12 +1,257 @@ # Welcome to pyqtorch -`pyqtorch` is an efficient, large-scale emulator designed for quantum machine learning, seamlessly integrated with a PyTorch backend. +**pyqtorch** is a state vector simulator designed for quantum machine learning written in [PyTorch](https://pytorch.org/). It allows for building fully differentiable quantum circuits comprised of both digital and analog operations using a intuitive [torch.nn.Module](https://pytorch.org/docs/stable/generated/torch.nn.Module.html)-based API. ## Setup To install `pyqtorch` , you can go into any virtual environment of your -choice and install it normally with `pip` (including extra dependencies for development): +choice and install it normally with `pip`: -``` +```bash pip install pyqtorch ``` + +## Digital + +`pyqtorch` implements a large selection of both primitive and parametric single to n-qubit, digital quantum gates. + +Let's have a look at primitive gates first. + +```python exec="on" source="material-block" +import torch +from pyqtorch import X, CNOT, random_state + +x = X(0) +state = random_state(n_qubits=2) + +new_state = x(state) + +cnot = CNOT(0,1) +new_state= cnot(state) +``` + +Parametric gates can be initialized with or without a `param_name`. In the former case, a dictionary containing the `param_name` and a `torch.Tensor` for the parameter is expected when calling the forward method of the gate. + +```python exec="on" source="material-block" +import torch +from pyqtorch import X, RX, CNOT, CRX, random_state + +state = random_state(n_qubits=2) + +rx_with_param = RX(0, 'theta') + +theta = torch.rand(1) +values = {'theta': theta} +new_state = rx_with_param(state, values) + +crx = CRX(0, 1, 'theta') +new_state = crx(state, values) +``` + +However, if you want to run a quick state vector simulation, you can initialize parametric gates without passing a `param_name`, in which case the forward method of the gate will simply expect a `torch.Tensor`. + + +```python exec="on" source="material-block" +import torch +from pyqtorch import RX, random_state + +state = random_state(n_qubits=2) +rx = RX(0) +new_state = rx(state, torch.rand(1)) +``` + +## Analog + +`pyqtorch` also contains a `analog` module which allows for global state evolution through the `HamiltonianEvolution` class. Note that it only accepts a `torch.Tensor` as a generator which is expected to be an Hermitian matrix. To build arbitrary Pauli hamiltonians, we recommend using [Qadence](https://pasqal-io.github.io/qadence/v1.0.3/tutorials/hamiltonians/). + +```python exec="on" source="material-block" html="1" +import torch +from pyqtorch import uniform_state, HamiltonianEvolution, is_normalized + +n_qubits = 4 + +# Random hermitian hamiltonian +matrix = torch.rand(2**n_qubits, 2**n_qubits, dtype=torch.cdouble) +hermitian_matrix = matrix + matrix.T.conj() + +# To be evolved for a batch of times +t_list = torch.tensor([0.0, 0.5, 1.0, 2.0]) + +hamiltonian_evolution = HamiltonianEvolution(qubit_support=[i for i in range(n_qubits)], n_qubits=n_qubits) + +# Starting from a uniform state +psi_start = uniform_state(n_qubits) + +# Returns an evolved state at each time value +psi_end = hamiltonian_evolution( + hamiltonian=hermitian_matrix, + time_evolution=t_list, + state = psi_start) + +assert is_normalized(psi_end, atol = 1e-12) +``` + +## QuantumCircuit + +Using digital and analog operations, you can can build fully differentiable quantum circuits using the `QuantumCircuit` class; note that the default differentiation mode in pyqtorch is using torch.autograd. + +```python exec="on" source="material-block" +import torch +import pyqtorch as pyq + +rx = pyq.RX(0, param_name="theta") +y = pyq.Y(0) +cnot = pyq.CNOT(0, 1) +ops = [rx, y, cnot] +n_qubits = 2 +circ = pyq.QuantumCircuit(n_qubits, ops) + +state = pyq.random_state(n_qubits) + +theta = torch.rand(1, requires_grad=True) + +def _fwd(phi: torch.Tensor) -> torch.Tensor: + return circ(state, {"theta": theta}) + +assert torch.autograd.gradcheck(_fwd, theta) +``` + +## Fitting a function + +Let's have a look at how the `QuantumCircuit` can be used to implement a Quantum Neural Network and fit a simple function. + +```python exec="on" source="material-block" html="1" +from __future__ import annotations + +import torch +import pyqtorch as pyq +from pyqtorch.parametric import Parametric +import numpy as np +import matplotlib.pyplot as plt + +import torch.nn.functional as F + +def target_function(x: torch.Tensor, degree: int = 3) -> torch.Tensor: + result = 0 + for i in range(degree): + result += torch.cos(i*x) + torch.sin(i*x) + return .05 * result + +x = torch.tensor(np.linspace(0, 10, 100)) +y = target_function(x, 5) + + +def HEA(n_qubits: int, n_layers: int, param_name: str) -> pyq.QuantumCircuit: + ops = [] + for l in range(n_layers): + ops += [pyq.RX(i, f'{param_name}_0_{l}_{i}') for i in range(n_qubits)] + ops += [pyq.RY(i, f'{param_name}_1_{l}_{i}') for i in range(n_qubits)] + ops += [pyq.RX(i, f'{param_name}_2_{l}_{i}') for i in range(n_qubits)] + ops += [pyq.CNOT(i % n_qubits, (i+1) % n_qubits) for i in range(n_qubits)] + return pyq.QuantumCircuit(n_qubits, ops) + + +class QNN(pyq.QuantumCircuit): + + def __init__(self, n_qubits, n_layers): + super().__init__(n_qubits, []) + self.n_qubits = n_qubits + self.feature_map = pyq.QuantumCircuit(n_qubits, [pyq.RX(i, f'phi') for i in range(n_qubits)]) + self.hea = HEA(n_qubits, n_layers, 'theta') + self.observable = pyq.Z(0) + self.param_dict = torch.nn.ParameterDict({op.param_name: torch.rand(1, requires_grad=True) for op in self.hea.operations if isinstance(op, Parametric)}) + def forward(self, phi: torch.Tensor): + batch_size = len(phi) + state = self.feature_map.init_state(batch_size) + state = self.feature_map(state, {'phi': phi}) + state = self.hea(state, self.param_dict) + new_state = self.observable(state, self.param_dict) + return pyq.overlap(state, new_state) + +n_qubits = 5 +n_layers = 3 +model = QNN(n_qubits, n_layers) + +with torch.no_grad(): + y_init = model(x) + +optimizer = torch.optim.Adam(model.parameters(), lr=.01) +epochs = 200 + +for epoch in range(epochs): + optimizer.zero_grad() + y_pred = model(x) + loss = F.mse_loss(y, y_pred) + loss.backward() + optimizer.step() + + +with torch.no_grad(): + y_final = model(x) + +plt.plot(x.numpy(), y.numpy(), label="truth") +plt.plot(x.numpy(), y_init.numpy(), label="initial") +plt.plot(x.numpy(), y_final.numpy(), "--", label="final", linewidth=3) +plt.legend() +from docs import docsutils # markdown-exec: hide +print(docsutils.fig_to_html(plt.gcf())) # markdown-exec: hide +``` + +## First Order Adjoint Differentiation + +`pyqtorch` also offers a [adjoint differentiation mode](https://arxiv.org/abs/2009.02823) which can be used through the `expectation` method of `QuantumCircuit`. + +```python exec="on" source="material-block" +import pyqtorch as pyq +import torch +from pyqtorch.utils import DiffMode + +n_qubits = 3 +batch_size = 1 +diff_mode = DiffMode.ADJOINT + + +rx = pyq.RX(0, param_name="theta_0") +cry = pyq.CPHASE(0, 1, param_name="theta_1") +rz = pyq.RZ(2, param_name="theta_2") +cnot = pyq.CNOT(1, 2) +ops = [rx, cry, rz, cnot] +n_qubits = 3 +adjoint_circ = pyq.QuantumCircuit(n_qubits, ops, DiffMode.ADJOINT) +ad_circ = pyq.QuantumCircuit(n_qubits, ops, DiffMode.AD) +obs = pyq.QuantumCircuit(n_qubits, [pyq.Z(0)]) + +theta_0_value = torch.pi / 2 +theta_1_value = torch.pi +theta_2_value = torch.pi / 4 + +state = pyq.zero_state(n_qubits) + +theta_0_ad = torch.tensor([theta_0_value], requires_grad=True) +thetas_0_adjoint = torch.tensor([theta_0_value], requires_grad=True) + +theta_1_ad = torch.tensor([theta_1_value], requires_grad=True) +thetas_1_adjoint = torch.tensor([theta_1_value], requires_grad=True) + +theta_2_ad = torch.tensor([theta_2_value], requires_grad=True) +thetas_2_adjoint = torch.tensor([theta_2_value], requires_grad=True) + +values_ad = {"theta_0": theta_0_ad, "theta_1": theta_1_ad, "theta_2": theta_2_ad} +values_adjoint = { + "theta_0": thetas_0_adjoint, + "theta_1": thetas_1_adjoint, + "theta_2": thetas_2_adjoint, +} +exp_ad = ad_circ.expectation(values_ad, obs, state) +exp_adjoint = adjoint_circ.expectation(values_adjoint, obs, state) + +grad_ad = torch.autograd.grad(exp_ad, tuple(values_ad.values()), torch.ones_like(exp_ad)) + +grad_adjoint = torch.autograd.grad( + exp_adjoint, tuple(values_adjoint.values()), torch.ones_like(exp_adjoint) +) + +assert len(grad_ad) == len(grad_adjoint) +for i in range(len(grad_ad)): + assert torch.allclose(grad_ad[i], grad_adjoint[i]) +``` diff --git a/docs/matrices.md b/docs/matrices.md deleted file mode 100644 index a49493cc..00000000 --- a/docs/matrices.md +++ /dev/null @@ -1 +0,0 @@ -::: pyqtorch.matrices diff --git a/docs/parametric.md b/docs/parametric.md deleted file mode 100644 index 299bc929..00000000 --- a/docs/parametric.md +++ /dev/null @@ -1 +0,0 @@ -::: pyqtorch.modules.parametric diff --git a/docs/primitive.md b/docs/primitive.md deleted file mode 100644 index beefed9f..00000000 --- a/docs/primitive.md +++ /dev/null @@ -1 +0,0 @@ -::: pyqtorch.modules.primitive diff --git a/docs/utils.md b/docs/utils.md deleted file mode 100644 index 08bb96c8..00000000 --- a/docs/utils.md +++ /dev/null @@ -1 +0,0 @@ -::: pyqtorch.modules.utils diff --git a/mkdocs.yml b/mkdocs.yml index 64d55e13..54e9a4a7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -4,23 +4,9 @@ repo_name: "pyqtorch" nav: - - Setup : index.md - - Contribute: - - How to Contribute: CONTRIBUTING.md - - Code of Conduct: CODE_OF_CONDUCT.md - - Overview: - - Essentials : essentials.ipynb - - Tutorials: - - Fitting a function: fit_function.ipynb - - QAOA : QAOA.ipynb - - Documentation: - - Modules: - - Circuit : circuit.md - - Matrices : matrices.md - - Parametric : parametric.md - - Primitive : primitive.md - - Utils : utils.md - + - Pyqtorch in a Nutshell: index.md + - How to Contribute: CONTRIBUTING.md + - Code of Conduct: CODE_OF_CONDUCT.md theme: name: material diff --git a/pyproject.toml b/pyproject.toml index c1f619aa..cd893fea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,10 +11,12 @@ authors = [ { name = "Mario Dagrada", email = "mario.dagrada@pasqal.com" }, { name = "Dominik Seitz", email = "dominik.seitz@pasqal.com" }, { name = "Niklas Heim", email = "niklas.heim@pasqal.com" }, + { name = "Roland Guichard", email = "roland.guichard@pasqal.com" }, + { name = "Joao P. Moutinho", email = "joao.moutinho@pasqal.com"}, ] requires-python = ">=3.8,<3.12" license = {text = "Apache 2.0"} -version = "0.5.1" +version = "1.0.0" classifiers=[ "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", @@ -42,6 +44,7 @@ features = [ [tool.hatch.envs.tests.scripts] test = "pytest -n auto {args} && hatch -e docs run mkdocs build --clean --strict" +test-cov = "pytest -n auto --cov=pyqtorch tests/" [tool.hatch.envs.docs] dependencies = [ @@ -54,6 +57,7 @@ dependencies = [ "mkdocs-exclude", "markdown-exec", "mike", + "matplotlib", ] [tool.hatch.envs.docs.scripts] diff --git a/pyqtorch/__init__.py b/pyqtorch/__init__.py index 6e57b83f..2f87fa37 100644 --- a/pyqtorch/__init__.py +++ b/pyqtorch/__init__.py @@ -1,19 +1,43 @@ -# Copyright 2022 PyQ Development Team - -# 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 +# # 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. +from __future__ import annotations -# http://www.apache.org/licenses/LICENSE-2.0 +import torch -# 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. -from __future__ import annotations +from .analog import HamiltonianEvolution +from .apply import apply_operator +from .circuit import QuantumCircuit +from .parametric import CPHASE, CRX, CRY, CRZ, PHASE, RX, RY, RZ, U +from .primitive import ( + CNOT, + CSWAP, + CY, + CZ, + SWAP, + H, + I, + N, + S, + SDagger, + T, + Toffoli, + X, + Y, + Z, +) +from .utils import ( + is_normalized, + overlap, + random_state, + uniform_state, + zero_state, +) -from .core import measurement, utils -from .core.batched_operation import * # noqa: F403 -from .core.circuit import * # noqa: F403 -from .core.operation import * # noqa: F403 +torch.set_default_dtype(torch.float64) diff --git a/pyqtorch/abstract.py b/pyqtorch/abstract.py new file mode 100644 index 00000000..f5ff00ad --- /dev/null +++ b/pyqtorch/abstract.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Any, Tuple + +import torch +from torch.nn import Module + +import pyqtorch as pyq +from pyqtorch.utils import Operator, State + + +class AbstractOperator(ABC, Module): + def __init__(self, target: int): + super().__init__() + self.target: int = target + self.qubit_support: Tuple[int, ...] = (target,) + self.n_qubits: int = max(self.qubit_support) + + def __mul__(self, other: AbstractOperator | pyq.QuantumCircuit) -> pyq.QuantumCircuit: + if isinstance(other, AbstractOperator): + ops = torch.nn.ModuleList([self, other]) + return pyq.QuantumCircuit(max(self.target, other.target), ops) + elif isinstance(other, pyq.QuantumCircuit): + ops = torch.nn.ModuleList([self]) + other.operations + return pyq.QuantumCircuit(max(self.target, other.target), ops) + else: + raise TypeError(f"Unable to compose {type(self)} with {type(other)}") + + def __key(self) -> tuple: + return self.qubit_support + + def __eq__(self, other: Any) -> bool: + if isinstance(other, type(self)): + return self.__key() == other.__key() + else: + return False + + def __hash__(self) -> int: + return hash(self.qubit_support) + + @abstractmethod + def unitary(self, values: dict[str, torch.Tensor] | torch.Tensor = {}) -> Operator: + ... + + @abstractmethod + def dagger(self, values: dict[str, torch.Tensor] | torch.Tensor = {}) -> Operator: + ... + + @abstractmethod + def forward( + self, state: torch.Tensor, values: dict[str, torch.Tensor] | torch.Tensor = {} + ) -> State: + ... + + def extra_repr(self) -> str: + return f"qubit_support={self.qubit_support}" diff --git a/pyqtorch/adjoint.py b/pyqtorch/adjoint.py new file mode 100644 index 00000000..6992d255 --- /dev/null +++ b/pyqtorch/adjoint.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from typing import Any + +from torch import Tensor +from torch.autograd import Function + +from pyqtorch.apply import apply_operator +from pyqtorch.circuit import QuantumCircuit +from pyqtorch.parametric import Parametric +from pyqtorch.utils import overlap, param_dict + + +class AdjointExpectation(Function): + @staticmethod + def forward( + ctx: Any, + circuit: QuantumCircuit, + observable: QuantumCircuit, + state: Tensor, + param_names: list[str], + *param_values: Tensor, + ) -> Tensor: + ctx.circuit = circuit + ctx.observable = observable + ctx.param_names = param_names + values = param_dict(param_names, param_values) + ctx.out_state = circuit.run(state, values) + ctx.projected_state = observable.run(ctx.out_state, values) + ctx.save_for_backward(*param_values) + return overlap(ctx.out_state, ctx.projected_state) + + @staticmethod + def backward(ctx: Any, grad_out: Tensor) -> tuple: + param_values = ctx.saved_tensors + values = param_dict(ctx.param_names, param_values) + grads: list = [] + for op in ctx.circuit.reverse(): + ctx.out_state = apply_operator(ctx.out_state, op.dagger(values), op.qubit_support) + if isinstance(op, Parametric): + mu = apply_operator(ctx.out_state, op.jacobian(values), op.qubit_support) + grads = [grad_out * 2 * overlap(ctx.projected_state, mu)] + grads + ctx.projected_state = apply_operator( + ctx.projected_state, op.dagger(values), op.qubit_support + ) + return (None, None, None, None, *grads) diff --git a/pyqtorch/analog.py b/pyqtorch/analog.py new file mode 100644 index 00000000..72f087b1 --- /dev/null +++ b/pyqtorch/analog.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from typing import Tuple + +import torch + +from pyqtorch.apply import apply_operator +from pyqtorch.utils import Operator, State, is_diag + +BATCH_DIM = 2 + + +class HamiltonianEvolution(torch.nn.Module): + def __init__( + self, + qubit_support: Tuple[int, ...], + n_qubits: int = None, + ): + super().__init__() + self.qubit_support: Tuple[int, ...] = qubit_support + if n_qubits is None: + n_qubits = len(qubit_support) + self.n_qubits: int = n_qubits + + def _diag_operator(hamiltonian: Operator, time_evolution: torch.Tensor) -> Operator: + evol_operator = torch.diagonal(hamiltonian) * (-1j * time_evolution).view((-1, 1)) + evol_operator = torch.diag_embed(torch.exp(evol_operator)) + return torch.transpose(evol_operator, 0, -1) + + def _matrixexp_operator(hamiltonian: Operator, time_evolution: torch.Tensor) -> Operator: + evol_operator = torch.transpose(hamiltonian, 0, -1) * (-1j * time_evolution).view( + (-1, 1, 1) + ) + evol_operator = torch.linalg.matrix_exp(evol_operator) + return torch.transpose(evol_operator, 0, -1) + + self._evolve_diag_operator = _diag_operator + self._evolve_matrixexp_operator = _matrixexp_operator + + def forward( + self, + hamiltonian: Operator, + time_evolution: torch.Tensor, + state: State, + ) -> State: + if len(hamiltonian.size()) < 3: + hamiltonian = hamiltonian.unsqueeze(2) + self.batch_size = max(hamiltonian.size()[2], len(time_evolution)) + diag_check = torch.tensor( + [is_diag(hamiltonian[..., i]) for i in range(hamiltonian.size()[BATCH_DIM])] + ) + evolve_operator = ( + self._evolve_diag_operator + if bool(torch.prod(diag_check)) + else self._evolve_matrixexp_operator + ) + return apply_operator( + state, + evolve_operator(hamiltonian, time_evolution), + self.qubit_support, + self.n_qubits, + self.batch_size, + ) diff --git a/pyqtorch/ansatz.py b/pyqtorch/ansatz.py deleted file mode 100644 index f236df9b..00000000 --- a/pyqtorch/ansatz.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright 2022 PyQ Development Team - -# 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. -from __future__ import annotations - -from typing import Optional - -import numpy as np -import torch -import torch.nn as nn -import torch.nn.init as init - -from pyqtorch.core.circuit import QuantumCircuit -from pyqtorch.core.operation import CNOT, RX, RY, RZ, U - - -class OneLayerRotation(QuantumCircuit): - def __init__(self, n_qubits: int, arbitrary: bool = False): - super().__init__(n_qubits) - self.theta: nn.Parameter - if arbitrary: - self.theta = nn.Parameter(torch.empty((self.n_qubits, 3))) - else: - self.theta = nn.Parameter(torch.empty((self.n_qubits,))) - self.reset_parameters() - self.arbitrary = arbitrary - - def reset_parameters(self) -> None: - init.uniform_(self.theta, -2 * np.pi, 2 * np.pi) - - def forward(self, state: torch.Tensor, _: Optional[torch.Tensor] = None) -> torch.Tensor: - if self.arbitrary: - for i, t in enumerate(self.theta): - state = U(t[0], t[1], t[2], state, [i], self.n_qubits) - return state - - -class OneLayerXRotation(OneLayerRotation): - def __init__(self, n_qubits: int): - super().__init__(n_qubits) - - def forward(self, state: torch.Tensor, _: Optional[torch.Tensor] = None) -> torch.Tensor: - for i, t in enumerate(self.theta): - state = RX(t, state, [i], self.n_qubits) - return state - - -class OneLayerYRotation(OneLayerRotation): - def __init__(self, n_qubits: int): - super().__init__(n_qubits) - - def forward(self, state: torch.Tensor, _: Optional[torch.Tensor] = None) -> torch.Tensor: - for i, t in enumerate(self.theta): - state = RY(t, state, [i], self.n_qubits) - return state - - -class OneLayerZRotation(OneLayerRotation): - def __init__(self, n_qubits: int): - super().__init__(n_qubits) - - def forward(self, state: torch.Tensor, _: Optional[torch.Tensor] = None) -> torch.Tensor: - for i, t in enumerate(self.theta): - state = RZ(t, state, [i], self.n_qubits) - return state - - -class OneLayerEntanglingAnsatz(QuantumCircuit): - def __init__(self, n_qubits: int): - super().__init__(n_qubits) - self.param_layer = OneLayerRotation(n_qubits=self.n_qubits, arbitrary=True) - - def forward(self, state: torch.Tensor, _: Optional[torch.Tensor] = None) -> torch.Tensor: - state = self.param_layer(state) - for i in range(self.n_qubits): - state = CNOT(state, [i % self.n_qubits, (i + 1) % self.n_qubits], self.n_qubits) - return state - - -class AlternateLayerAnsatz(QuantumCircuit): - def __init__(self, n_qubits: int, n_layers: int): - super().__init__(n_qubits) - self.layers = nn.ModuleList( - [OneLayerEntanglingAnsatz(self.n_qubits) for _ in range(n_layers)] - ) - - def forward(self, state: torch.Tensor, _: Optional[torch.Tensor] = None) -> torch.Tensor: - for layer in self.layers: - state = layer(state) - return state diff --git a/pyqtorch/apply.py b/pyqtorch/apply.py new file mode 100644 index 00000000..f61b3f16 --- /dev/null +++ b/pyqtorch/apply.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from string import ascii_letters as ABC +from typing import Tuple + +from numpy import array +from numpy.typing import NDArray +from torch import einsum + +from pyqtorch.utils import Operator, State + +ABC_ARRAY: NDArray = array(list(ABC)) + + +def apply_operator( + state: State, + operator: Operator, + qubits: Tuple[int, ...] | list[int], + n_qubits: int = None, + batch_size: int = None, +) -> State: + qubits = list(qubits) + if n_qubits is None: + n_qubits = len(state.size()) - 1 + if batch_size is None: + batch_size = state.size(-1) + n_support = len(qubits) + n_state_dims = n_qubits + 1 + operator = operator.view([2] * n_support * 2 + [operator.size(-1)]) + in_state_dims = ABC_ARRAY[0:n_state_dims].copy() + operator_dims = ABC_ARRAY[n_state_dims : n_state_dims + 2 * n_support + 1].copy() + operator_dims[n_support : 2 * n_support] = in_state_dims[qubits] + operator_dims[-1] = in_state_dims[-1] + out_state_dims = in_state_dims.copy() + out_state_dims[qubits] = operator_dims[0:n_support] + operator_dims, in_state_dims, out_state_dims = list( + map(lambda e: "".join(list(e)), [operator_dims, in_state_dims, out_state_dims]) + ) + return einsum(f"{operator_dims},{in_state_dims}->{out_state_dims}", operator, state) diff --git a/pyqtorch/circuit.py b/pyqtorch/circuit.py new file mode 100644 index 00000000..ee504d92 --- /dev/null +++ b/pyqtorch/circuit.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +from typing import Any, Iterator + +import torch + +from pyqtorch.abstract import AbstractOperator +from pyqtorch.utils import DiffMode, State, overlap, zero_state + + +class QuantumCircuit(torch.nn.Module): + def __init__( + self, n_qubits: int, operations: list[AbstractOperator], diff_mode: DiffMode = DiffMode.AD + ): + super().__init__() + self.n_qubits = n_qubits + self.operations = torch.nn.ModuleList(operations) + self.diff_mode = diff_mode + + def __mul__(self, other: AbstractOperator | QuantumCircuit) -> QuantumCircuit: + n_qubits = max(self.n_qubits, other.n_qubits) + if isinstance(other, QuantumCircuit): + return QuantumCircuit(n_qubits, self.operations.extend(other.operations)) + + elif isinstance(other, AbstractOperator): + return QuantumCircuit(n_qubits, self.operations.append(other)) + + else: + raise ValueError(f"Cannot compose {type(self)} with {type(other)}") + + def __iter__(self) -> Iterator: + return iter(self.operations) + + def __key(self) -> tuple: + return (self.n_qubits,) + + def __eq__(self, other: Any) -> bool: + if isinstance(other, QuantumCircuit): + return self.__key() == other.__key() + else: + raise NotImplementedError(f"Unable to compare QuantumCircuit to {type(other)}.") + + def __hash__(self) -> int: + return hash(self.__key()) + + def run(self, state: State = None, values: dict[str, torch.Tensor] = {}) -> State: + if state is None: + state = self.init_state() + for op in self.operations: + state = op(state, values) + return state + + def forward(self, state: State, values: dict[str, torch.Tensor] = {}) -> State: + return self.run(state, values) + + def expectation( + self, + values: dict[str, torch.Tensor], + observable: QuantumCircuit, + state: State = None, + ) -> torch.Tensor: + if observable is None: + raise ValueError("Please provide an observable to compute expectation.") + if state is None: + state = self.init_state(batch_size=1) + if self.diff_mode == DiffMode.AD: + state = self.run(state, values) + return overlap(state, observable.forward(state, values)) + else: + from pyqtorch.adjoint import AdjointExpectation + + return AdjointExpectation.apply( + self, observable, state, values.keys(), *values.values() + ) + + @property + def _device(self) -> torch.device: + try: + (_, buffer) = next(self.named_buffers()) + return buffer.device + except StopIteration: + return torch.device("cpu") + + def init_state(self, batch_size: int = 1) -> torch.Tensor: + return zero_state(self.n_qubits, batch_size, device=self._device) + + def reverse(self) -> QuantumCircuit: + return QuantumCircuit(self.n_qubits, torch.nn.ModuleList(list(reversed(self.operations)))) diff --git a/pyqtorch/core/__init__.py b/pyqtorch/core/__init__.py deleted file mode 100644 index 0b4003ad..00000000 --- a/pyqtorch/core/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2022 PyQ Development Team - -# 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. diff --git a/pyqtorch/core/batched_operation.py b/pyqtorch/core/batched_operation.py deleted file mode 100644 index bf372ab4..00000000 --- a/pyqtorch/core/batched_operation.py +++ /dev/null @@ -1,603 +0,0 @@ -# Copyright 2022 pyqtorch Development Team - -# 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. -from __future__ import annotations - -from typing import Any - -import numpy as np -import torch -from numpy.typing import ArrayLike - -from pyqtorch.core.operation import RX, H, diagonalize -from pyqtorch.core.utils import _apply_batch_gate -from pyqtorch.matrices import DEFAULT_MATRIX_DTYPE, OPERATIONS_DICT - -IMAT = OPERATIONS_DICT["I"] -XMAT = OPERATIONS_DICT["X"] -YMAT = OPERATIONS_DICT["Y"] -ZMAT = OPERATIONS_DICT["Z"] - -BATCH_DIM = 2 - - -def get_parametrized_batch_for_operation( - operation_type: str, theta: torch.Tensor, batch_size: int, device: torch.device -) -> torch.Tensor: - """Helper method which takes a string describing an operation type and a - parameter theta vector and returns a batch of the corresponding parametrized - rotation matrices - - Args: - operation_type (str): the type of operation which should be performed - (RX,RY,RZ) - theta (torch.Tensor): 1D-tensor holding the values of the parameter - batch_size (int): the batch size - device (torch.device): the device which to run on - - Returns: - torch.Tensor: a batch of gates after applying theta - """ - - cos_t = torch.cos(theta / 2).unsqueeze(0).unsqueeze(1) - cos_t = cos_t.repeat((2, 2, 1)) - sin_t = torch.sin(theta / 2).unsqueeze(0).unsqueeze(1) - sin_t = sin_t.repeat((2, 2, 1)) - - batch_imat = OPERATIONS_DICT["I"].unsqueeze(2).repeat(1, 1, batch_size).to(device) - batch_operation_mat = ( - OPERATIONS_DICT[operation_type].unsqueeze(2).repeat(1, 1, batch_size).to(device) - ) - - return cos_t * batch_imat - 1j * sin_t * batch_operation_mat - - -def create_controlled_batch_from_operation( - operation_batch: torch.Tensor, batch_size: int, n_control_qubits: int = 1 -) -> torch.Tensor: - """Method which takes a 2x2 torch.Tensor and transforms it into a Controlled Operation Gate - - Args: - - operation_matrix (torch.Tensor): the type of operation which should be performed (RX,RY,RZ) - batch_size (int): the batch size - - Returns: - - torch.Tensor: the resulting controlled gate populated by operation_matrix - """ - controlled_batch: torch.Tensor = ( - torch.eye(2 ** (n_control_qubits + 1), dtype=DEFAULT_MATRIX_DTYPE) - .unsqueeze(2) - .repeat(1, 1, batch_size) - ) - controlled_batch[ - 2 ** (n_control_qubits + 1) - 2 :, 2 ** (n_control_qubits + 1) - 2 :, : - ] = torch.clone(operation_batch) - return controlled_batch - - -def batchedRX( - theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int -) -> torch.Tensor: - """Parametrized single-qubit RX rotation with batched parameters - - A batched operation is an operation which efficiently applies a set of parametrized - gates with parameters held by the `theta` argument on a set of input states held by - the `state` argument. The number of gates and input states is the batch size. For - large batches, this gate is much faster than its standard non-batched version - - Notice that for this operation to work the input state must also have been - initialized with its *last* dimension equal to the batch size. Use the - QuantumCircuit.init_state() method to properly initialize a state usable - for batched operations - - Example: - ```py - import torch - from pyqtorch.core.circuit import QuantumCircuit - from pyqtorch.core.batched_operation import batchedRX - - nqubits = 4 - batch_size = 10 - - state = QuantumCircuit(nqubits).init_state(batch_size) - batched_params = torch.rand(batch_size) - - # if the length of the batched_params is not matching the batch size - # an error will be thrown - out_state = batchedRX(batched_params, state, [0], nqubits) - ``` - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - - batch_size = len(theta) - - mat = get_parametrized_batch_for_operation("X", theta, batch_size, dev) - - return _apply_batch_gate(state, mat, qubits, N_qubits, batch_size) - - -def batchedRY( - theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int -) -> torch.Tensor: - """Parametrized single-qubit RY rotation with batched parameters - - A batched operation is an operation which efficiently applies a set of parametrized - gates with parameters held by the `theta` argument on a set of input states held by - the `state` argument. The number of gates and input states is the batch size. For - large batches, this gate is much faster than its standard non-batched version - - Notice that for this operation to work the input state must also have been - initialized with its *last* dimension equal to the batch size. Use the - QuantumCircuit.init_state() method to properly initialize a state usable - for batched operations - - Example: - ```py - import torch - from pyqtorch.core.circuit import QuantumCircuit - from pyqtorch.core.batched_operation import batchedRY - - nqubits = 4 - batch_size = 10 - - state = QuantumCircuit(nqubits).init_state(batch_size) - batched_params = torch.rand(batch_size) - - # if the length of the batched_params is not matching the batch size - # an error will be thrown - out_state = batchedRY(batched_params, state, [0], nqubits) - ``` - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - - batch_size = len(theta) - - mat = get_parametrized_batch_for_operation("Y", theta, batch_size, dev) - - return _apply_batch_gate(state, mat, qubits, N_qubits, batch_size) - - -def batchedRZ( - theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int -) -> torch.Tensor: - """Parametrized single-qubit RZ rotation with batched parameters - - A batched operation is an operation which efficiently applies a set of parametrized - gates with parameters held by the `theta` argument on a set of input states held by - the `state` argument. The number of gates and input states is the batch size. For - large batches, this gate is much faster than its standard non-batched version - - Notice that for this operation to work the input state must also have been - initialized with its *last* dimension equal to the batch size. Use the - QuantumCircuit.init_state() method to properly initialize a state usable - for batched operations - - Example: - ```py - import torch - from pyqtorch.core.circuit import QuantumCircuit - from pyqtorch.core.batched_operation import batchedRZ - - nqubits = 4 - batch_size = 10 - - state = QuantumCircuit(nqubits).init_state(batch_size) - batched_params = torch.rand(batch_size) - - # if the length of the batched_params is not matching the batch size - # an error will be thrown - out_state = batchedRZ(batched_params, state, [0], nqubits) - ``` - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - - batch_size = len(theta) - - mat = get_parametrized_batch_for_operation("Z", theta, batch_size, dev) - - return _apply_batch_gate(state, mat, qubits, N_qubits, batch_size) - - -def batchedRZZ( - theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int -) -> torch.Tensor: - """Parametrized two-qubit RZZ rotation with batched parameters - - A batched operation is an operation which efficiently applies a set of parametrized - gates with parameters held by the `theta` argument on a set of input states held by - the `state` argument. The number of gates and input states is the batch size. For - large batches, this gate is much faster than its standard non-batched version - - Notice that for this operation to work the input state must also have been - initialized with its *last* dimension equal to the batch size. Use the - QuantumCircuit.init_state() method to properly initialize a state usable - for batched operations - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - batch_size = len(theta) - - cos_t = torch.cos(theta / 2).unsqueeze(0).unsqueeze(1) - cos_t = cos_t.repeat((4, 4, 1)) - sin_t = torch.sin(theta / 2).unsqueeze(0).unsqueeze(1) - sin_t = sin_t.repeat((4, 4, 1)) - - mat = torch.diag(torch.tensor([1, -1, -1, 1], dtype=DEFAULT_MATRIX_DTYPE).to(dev)) - - imat = torch.eye(4, dtype=DEFAULT_MATRIX_DTYPE).unsqueeze(2).repeat(1, 1, batch_size).to(dev) - xmat = mat.unsqueeze(2).repeat(1, 1, batch_size).to(dev) - - mat = cos_t * imat + 1j * sin_t * xmat - - return _apply_batch_gate(state, mat, qubits, N_qubits, batch_size) - - -def batchedRXX( - theta: torch.Tensor, state: torch.Tensor, qubits: Any, N_qubits: int -) -> torch.Tensor: - """Parametrized two-qubit RXX rotation with batched parameters - - A batched operation is an operation which efficiently applies a set of parametrized - gates with parameters held by the `theta` argument on a set of input states held by - the `state` argument. The number of gates and input states is the batch size. For - large batches, this gate is much faster than its standard non-batched version - - Notice that for this operation to work the input state must also have been - initialized with its *last* dimension equal to the batch size. Use the - QuantumCircuit.init_state() method to properly initialize a state usable - for batched operations - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - for q in qubits: - state = H(state, [q], N_qubits) - state = batchedRZZ(theta, state, qubits, N_qubits) - for q in qubits: - state = H(state, [q], N_qubits) - - return state - - -def batchedRYY( - theta: torch.Tensor, state: torch.Tensor, qubits: Any, N_qubits: int -) -> torch.Tensor: - """Parametrized two-qubit RYY rotation with batched parameters - - A batched operation is an operation which efficiently applies a set of parametrized - gates with parameters held by the `theta` argument on a set of input states held by - the `state` argument. The number of gates and input states is the batch size. For - large batches, this gate is much faster than its standard non-batched version - - Notice that for this operation to work the input state must also have been - initialized with its *last* dimension equal to the batch size. Use the - QuantumCircuit.init_state() method to properly initialize a state usable - for batched operations - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - for q in qubits: - state = RX(torch.tensor(np.pi / 2), state, [q], N_qubits) - state = batchedRZZ(theta, state, qubits, N_qubits) - for q in qubits: - state = RX(-torch.tensor(np.pi / 2), state, [q], N_qubits) - - return state - - -def batchedCPHASE( - theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int -) -> torch.Tensor: - """Parametrized two-qubit CPHASE gate with batched parameters - - A batched operation is an operation which efficiently applies a set of parametrized - gates with parameters held by the `theta` argument on a set of input states held by - the `state` argument. The number of gates and input states is the batch size. For - large batches, this gate is much faster than its standard non-batched version - - Notice that for this operation to work the input state must also have been - initialized with its *last* dimension equal to the batch size. Use the - QuantumCircuit.init_state() method to properly initialize a state usable - for batched operations - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - batch_size = len(theta) - mat = torch.eye(4, dtype=DEFAULT_MATRIX_DTYPE).repeat((batch_size, 1, 1)) - mat = torch.permute(mat, (1, 2, 0)) - phase_rotation_angles = torch.exp(torch.tensor(1j) * theta).unsqueeze(0).unsqueeze(1) - mat[3, 3, :] = phase_rotation_angles - - return _apply_batch_gate(state, mat.to(dev), qubits, N_qubits, batch_size) - - -def batchedCRX( - theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int -) -> torch.Tensor: - """Parametrized two-qubit Controlled X rotation gate with batched parameters - - A batched operation is an operation which efficiently applies a set of parametrized - gates with parameters held by the `theta` argument on a set of input states held by - the `state` argument. The number of gates and input states is the batch size. For - large batches, this gate is much faster than its standard non-batched version - - Notice that for this operation to work the input state must also have been - initialized with its *last* dimension equal to the batch size. Use the - QuantumCircuit.init_state() method to properly initialize a state usable - for batched operations - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - batch_size = len(theta) - - operations_batch = get_parametrized_batch_for_operation("X", theta, batch_size, dev) - controlledX_batch = create_controlled_batch_from_operation(operations_batch, batch_size) - - return _apply_batch_gate(state, controlledX_batch.to(dev), qubits, N_qubits, batch_size) - - -def batchedCRY( - theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int -) -> torch.Tensor: - """Parametrized two-qubit Controlled Y rotation gate with batched parameters - - A batched operation is an operation which efficiently applies a set of parametrized - gates with parameters held by the `theta` argument on a set of input states held by - the `state` argument. The number of gates and input states is the batch size. For - large batches, this gate is much faster than its standard non-batched version - - Notice that for this operation to work the input state must also have been - initialized with its *last* dimension equal to the batch size. Use the - QuantumCircuit.init_state() method to properly initialize a state usable - for batched operations - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - batch_size = len(theta) - - operations_batch = get_parametrized_batch_for_operation("Y", theta, batch_size, dev) - controlledY_batch = create_controlled_batch_from_operation(operations_batch, batch_size) - - return _apply_batch_gate(state, controlledY_batch.to(dev), qubits, N_qubits, batch_size) - - -def batchedCRZ( - theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int -) -> torch.Tensor: - """Parametrized two-qubit Controlled Z rotation gate with batched parameters - - A batched operation is an operation which efficiently applies a set of parametrized - gates with parameters held by the `theta` argument on a set of input states held by - the `state` argument. The number of gates and input states is the batch size. For - large batches, this gate is much faster than its standard non-batched version - - Notice that for this operation to work the input state must also have been - initialized with its *last* dimension equal to the batch size. Use the - QuantumCircuit.init_state() method to properly initialize a state usable - for batched operations - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - batch_size = len(theta) - - operations_batch = get_parametrized_batch_for_operation("Z", theta, batch_size, dev) - controlledZ_batch = create_controlled_batch_from_operation(operations_batch, batch_size) - - return _apply_batch_gate(state, controlledZ_batch.to(dev), qubits, N_qubits, batch_size) - - -def batched_hamiltonian_evolution( - H: torch.Tensor, - state: torch.Tensor, - t: torch.Tensor, - qubits: Any, - N_qubits: int, - n_steps: int = 100, -) -> torch.Tensor: - """A function to perform time-evolution according to the generator `H` - - The operation is batched on the generator matrix `H` which acts on a `N_qubits`-sized - input `state`, for a duration `t`. See also tutorials for more information - on how to use this gate. - - Args: - H (torch.Tensor): the tensor containing dense matrices representing the - Hamiltonian, provided as a `Tensor` object with shape - `(N_0,N_1,...N_(N**2),batch_size)`, i.e. the matrix is reshaped into the - list of its rows - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - t (torch.Tensor): the evolution time, real for default unitary evolution - qubits (Any): The qubits support where the H evolution is applied - N_qubits (int): The number of qubits - n_steps (int, optional): The number of steps to divide the time interval - in. Defaults to 100. - - Returns: - torch.Tensor: replaces state with the evolved state according to the - instructions above (save a copy of `state` if you need further - processing on it) - """ - _state = state.clone() - batch_size = H.size()[BATCH_DIM] - - h = t.reshape((1, -1)) / n_steps - for _ in range(N_qubits - 1): - h = h.unsqueeze(0) - - h = h.expand_as(state) - - for _ in range(n_steps): - k1 = -1j * _apply_batch_gate(_state, H, qubits, N_qubits, batch_size) - k2 = -1j * _apply_batch_gate(_state + h / 2 * k1, H, qubits, N_qubits, batch_size) - k3 = -1j * _apply_batch_gate(_state + h / 2 * k2, H, qubits, N_qubits, batch_size) - k4 = -1j * _apply_batch_gate(_state + h * k3, H, qubits, N_qubits, batch_size) - # k1 = -1j * torch.matmul(H, state) - # k2 = -1j * torch.matmul(H, state + h/2 * k1) - # k3 = -1j * torch.matmul(H, state + h/2 * k2) - # k4 = -1j * torch.matmul(H, state + h * k3) - - _state += h / 6 * (k1 + 2 * k2 + 2 * k3 + k4) - - return _state - - -def batched_hamiltonian_evolution_eig( - H: torch.Tensor, - state: torch.Tensor, - t: torch.Tensor, - qubits: Any, - N_qubits: int, -) -> torch.Tensor: - """A function to perform time-evolution according to the generator `H` - - The operation is batched on the generator matrix `H` which acts on a `N_qubits`-sized - input `state`, for a duration `t`. See also tutorials for more information - on how to use this gate. - - Args: - H (torch.Tensor): the dense matrix representing the Hamiltonian, - provided as a `Tensor` object with shape - `(N_0,N_1,...N_(N**2),batch_size)`, i.e. the matrix is reshaped into - the list of its rows - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - t (torch.Tensor): the evolution time, real for default unitary evolution - qubits (Any): The qubits support where the H evolution is applied - N_qubits (int): The number of qubits - - Returns: - torch.Tensor: returns the evolved state as a new copy - """ - batch_size_h = H.size()[BATCH_DIM] - batch_size_t = len(t) - - evol_operator = torch.zeros(H.size()).to(dtype=DEFAULT_MATRIX_DTYPE) - - t_evo = torch.zeros(batch_size_h).to(dtype=DEFAULT_MATRIX_DTYPE) - - if batch_size_t >= batch_size_h: - t_evo = t[:batch_size_h] - else: - if batch_size_t == 1: - t_evo[:] = t[0] - else: - t_evo[:batch_size_t] = t - - for i in range(batch_size_h): - eig_values, eig_vectors = diagonalize(H[..., i]) - - if eig_vectors is None: - # Compute e^(-i H t) - evol_operator[..., i] = torch.diag(torch.exp(-1j * eig_values * t_evo[i])) - - else: - # Compute e^(-i D t) - eig_exp = torch.diag(torch.exp(-1j * eig_values * t_evo[i])) - # e^(-i H t) = V.e^(-i D t).V^\dagger - evol_operator[..., i] = torch.matmul( - torch.matmul(eig_vectors, eig_exp), - torch.conj(eig_vectors.transpose(0, 1)), - ) - - return _apply_batch_gate(state, evol_operator, qubits, N_qubits, batch_size_h) diff --git a/pyqtorch/core/circuit.py b/pyqtorch/core/circuit.py deleted file mode 100644 index 1b622146..00000000 --- a/pyqtorch/core/circuit.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2022 PyQ Development Team - -# 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. -from __future__ import annotations - -from typing import Union - -import torch -import torch.nn as nn - -from pyqtorch.matrices import DEFAULT_MATRIX_DTYPE - - -class QuantumCircuit(nn.Module): - def __init__(self, n_qubits: int): - super(QuantumCircuit, self).__init__() - self.n_qubits = n_qubits - - def init_state( - self, batch_size: int = 1, device: Union[str, torch.device] = "cpu" - ) -> torch.Tensor: - state = torch.zeros( - (2**self.n_qubits, batch_size), dtype=DEFAULT_MATRIX_DTYPE, device=device - ) - state[0] = 1 - state = state.reshape([2] * self.n_qubits + [batch_size]) - return state - - def uniform_state( - self, batch_size: int = 1, device: Union[str, torch.device] = "cpu" - ) -> torch.Tensor: - state = torch.ones( - (2**self.n_qubits, batch_size), dtype=DEFAULT_MATRIX_DTYPE, device=device - ) - state = state / torch.sqrt(torch.tensor(2**self.n_qubits)) - state = state.reshape([2] * self.n_qubits + [batch_size]) - return state diff --git a/pyqtorch/core/measurement.py b/pyqtorch/core/measurement.py deleted file mode 100644 index 8c969ba5..00000000 --- a/pyqtorch/core/measurement.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright 2022 PyQ Development Team - -# 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. -from __future__ import annotations - -import torch - -from pyqtorch.core.operation import X, Y, Z - -qubit_operators = {"X": X, "Y": Y, "Z": Z} - - -def total_magnetization(state: torch.Tensor, N_qubits: int, batch_size: int) -> torch.Tensor: - new_state: torch.Tensor = torch.zeros_like(state) - for i in range(N_qubits): - new_state += Z(state, [i], N_qubits) - - state = state.reshape((2**N_qubits, batch_size)) - new_state = new_state.reshape((2**N_qubits, batch_size)) - - ret = torch.real(torch.sum(torch.conj(state) * new_state, dim=0)) - return ret diff --git a/pyqtorch/core/operation.py b/pyqtorch/core/operation.py deleted file mode 100644 index f58a8d83..00000000 --- a/pyqtorch/core/operation.py +++ /dev/null @@ -1,624 +0,0 @@ -# Copyright 2022 PyQ Development Team - -# 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. -from __future__ import annotations - -from functools import lru_cache -from typing import Any, Optional, Tuple - -import torch -from numpy.typing import ArrayLike - -from pyqtorch.core.utils import _apply_gate -from pyqtorch.matrices import DEFAULT_MATRIX_DTYPE, OPERATIONS_DICT - - -def get_parametrized_matrix_for_operation(operation_type: str, theta: torch.Tensor) -> torch.Tensor: - """Helper method which takes a string describing an operation type and a - parameter theta and returns the corresponding parametrized rotation matrix - - Args: - operation_type (str): the type of operation which should be performed (RX,RY,RZ) - theta (torch.Tensor): 1D-tensor holding the values of the parameter - - Returns: - torch.Tensor: the resulting gate after applying theta - """ - return OPERATIONS_DICT["I"] * torch.cos(theta / 2) - 1j * OPERATIONS_DICT[ - operation_type - ] * torch.sin(theta / 2) - - -def create_controlled_matrix_from_operation( - operation_matrix: torch.Tensor, n_control_qubits: int = 1 -) -> torch.Tensor: - """Method which takes a torch.Tensor and transforms it into a Controlled Operation Gate - - Args: - - operation_matrix: (torch.Tensor): the type of operation which should be - performed (RX,RY,RZ,SWAP) - n_control_qubits: (int): The number of control qubits used - - Returns: - - torch.Tensor: the resulting controlled gate populated by operation_matrix - """ - mat_size = len(operation_matrix) - controlled_mat: torch.Tensor = torch.eye( - 2**n_control_qubits * mat_size, dtype=DEFAULT_MATRIX_DTYPE - ) - controlled_mat[-mat_size:, -mat_size:] = operation_matrix - return controlled_mat - - -def RX(theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """Parametrized single-qubit RX rotation - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - mat: torch.Tensor = get_parametrized_matrix_for_operation("X", theta).to(dev) - return _apply_gate(state, mat, qubits, N_qubits) - - -def RY(theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """Parametrized single-qubit RY rotation - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - mat: torch.Tensor = get_parametrized_matrix_for_operation("Y", theta).to(dev) - return _apply_gate(state, mat, qubits, N_qubits) - - -def RZ(theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """Parametrized single-qubit RZ rotation - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - mat: torch.Tensor = get_parametrized_matrix_for_operation("Z", theta).to(dev) - return _apply_gate(state, mat, qubits, N_qubits) - - -def RZZ(theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """Parametrized two-qubits RZ rotation - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - mat = torch.diag(torch.tensor([1, -1, -1, 1], dtype=DEFAULT_MATRIX_DTYPE).to(dev)) - mat = 1j * torch.sin(theta / 2) * mat + torch.cos(theta / 2) * torch.eye( - 4, dtype=DEFAULT_MATRIX_DTYPE - ).to(dev) - return _apply_gate(state, torch.diag(mat), qubits, N_qubits) - - -def U( - phi: torch.Tensor, - theta: torch.Tensor, - omega: torch.Tensor, - state: torch.Tensor, - qubits: ArrayLike, - N_qubits: int, -) -> torch.Tensor: - """Parametrized arbitrary rotation along the axes of the Bloch sphere - - The angles `phi, theta, omega` in tensor format, applied as: - U(phi, theta, omega) = RZ(omega)RY(theta)RZ(phi) - - Args: - phi (torch.Tensor): 1D-tensor holding the values of the `phi` parameter - theta (torch.Tensor): 1D-tensor holding the values of the `theta` parameter - omega (torch.Tensor): 1D-tensor holding the values of the `omega` parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - t_plus = torch.exp(-1j * (phi + omega) / 2) - t_minus = torch.exp(-1j * (phi - omega) / 2) - mat = ( - torch.tensor([[1, 0], [0, 0]], dtype=DEFAULT_MATRIX_DTYPE).to(dev) - * torch.cos(theta / 2) - * t_plus - - torch.tensor([[0, 1], [0, 0]], dtype=DEFAULT_MATRIX_DTYPE).to(dev) - * torch.sin(theta / 2) - * torch.conj(t_minus) - + torch.tensor([[0, 0], [1, 0]], dtype=DEFAULT_MATRIX_DTYPE).to(dev) - * torch.sin(theta / 2) - * t_minus - + torch.tensor([[0, 0], [0, 1]], dtype=DEFAULT_MATRIX_DTYPE).to(dev) - * torch.cos(theta / 2) - * torch.conj(t_plus) - ) - return _apply_gate(state, mat, qubits, N_qubits) - - -def I(state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: # noqa: E743 - """I single-qubit gate - - Args: - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - mat = OPERATIONS_DICT["I"].to(dev) - return _apply_gate(state, mat, qubits, N_qubits) - - -def X(state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """X single-qubit gate - - Args: - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - mat = OPERATIONS_DICT["X"].to(dev) - return _apply_gate(state, mat, qubits, N_qubits) - - -def Z(state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """Z single-qubit gate - - Args: - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - mat = OPERATIONS_DICT["Z"].to(dev) - return _apply_gate(state, mat, qubits, N_qubits) - - -def Y(state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """Y single-qubit gate - - Args: - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - mat = OPERATIONS_DICT["Y"].to(dev) - return _apply_gate(state, mat, qubits, N_qubits) - - -def H(state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """Hadamard single-qubit gate - - Args: - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - mat = OPERATIONS_DICT["H"].to(dev) - return _apply_gate(state, mat, qubits, N_qubits) - - -def ControlledOperationGate( - state: torch.Tensor, - qubits: ArrayLike, - N_qubits: int, - operation_matrix: torch.Tensor, -) -> torch.Tensor: - """Generalized Controlled Rotation gate with two-qubits support - - Args: - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - operation_matrix (torch.Tensor): a tensor holding the parameters for the - operation (RX,RY,RZ) - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - dev = state.device - controlled_operation_matrix: torch.Tensor = create_controlled_matrix_from_operation( - operation_matrix - ) - return _apply_gate(state, controlled_operation_matrix.to(dev), qubits, N_qubits) - - -def CNOT(state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """Controlled NOT gate with two-qubits support - - Args: - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - - torch.Tensor: the resulting state after applying the gate - """ - - return ControlledOperationGate(state, qubits, N_qubits, OPERATIONS_DICT["X"]) - - -def CRX(theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """Controlled RX rotation gate with two-qubits support - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - operation_matrix: torch.Tensor = get_parametrized_matrix_for_operation("X", theta) - return ControlledOperationGate(state, qubits, N_qubits, operation_matrix) - - -def CRY(theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """Controlled RY rotation gate with two-qubits support - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - operation_matrix: torch.Tensor = get_parametrized_matrix_for_operation("Y", theta) - return ControlledOperationGate(state, qubits, N_qubits, operation_matrix) - - -def CRZ(theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """Controlled RZ rotation gate with two-qubits support - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - operation_matrix: torch.Tensor = get_parametrized_matrix_for_operation("Z", theta) - return ControlledOperationGate(state, qubits, N_qubits, operation_matrix) - - -def S(state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """S single-qubit gate - - Args: - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - mat = OPERATIONS_DICT["S"].to(dev) - return _apply_gate(state, mat, qubits, N_qubits) - - -def SDagger(state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """SDagger single-qubit gate - - Args: - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - mat = OPERATIONS_DICT["SDAGGER"].to(dev) - return _apply_gate(state, mat, qubits, N_qubits) - - -def T(state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """T single-qubit gate - - Args: - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - mat = OPERATIONS_DICT["T"].to(dev) - return _apply_gate(state, mat, qubits, N_qubits) - - -def N(state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """N (projector) single-qubit gate (I-Z)/2 - - Args: - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - mat = OPERATIONS_DICT["N"].to(dev) - return _apply_gate(state, mat, qubits, N_qubits) - - -def SWAP(state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """SWAP 2-qubit gate - - Args: - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - mat = OPERATIONS_DICT["SWAP"].to(dev) - return _apply_gate(state, mat, qubits, N_qubits) - - -def CPHASE( - theta: torch.Tensor, state: torch.Tensor, qubits: ArrayLike, N_qubits: int -) -> torch.Tensor: - """Parametrized 2-qubit CPHASE gate - - Args: - theta (torch.Tensor): 1D-tensor holding the values of the parameter - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - - dev = state.device - mat: torch.Tensor = torch.tensor( - [ - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, torch.exp(torch.tensor(1j * theta))], - ], - dtype=DEFAULT_MATRIX_DTYPE, - ).to(dev) - return _apply_gate(state, mat, qubits, N_qubits) - - -def CSWAP(state: torch.Tensor, qubits: ArrayLike, N_qubits: int) -> torch.Tensor: - """Controlled SWAP gate with three-qubit support - - Args: - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - qubits (ArrayLike): list of qubit indices where the gate will operate - N_qubits (int): the number of qubits in the system - - Returns: - - torch.Tensor: the resulting state after applying the gate - """ - - return ControlledOperationGate(state, qubits, N_qubits, OPERATIONS_DICT["SWAP"]) - - -def hamiltonian_evolution( - H: torch.Tensor, - state: torch.Tensor, - t: torch.Tensor, - qubits: Any, - N_qubits: int, - n_steps: int = 100, -) -> torch.Tensor: - """A function to perform time-evolution according to the generator `H` acting on a - `N_qubits`-sized input `state`, for a duration `t`. See also tutorials for more information - on how to use this gate. - - Args: - H (torch.Tensor): the dense matrix representing the Hamiltonian, - provided as a `Tensor` object with shape - `(N_0,N_1,...N_(N**2),batch_size)`, i.e. the matrix is reshaped into - the list of its rows - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - t (torch.Tensor): the evolution time, real for default unitary evolution - qubits (Any): The qubits support where the H evolution is applied - N_qubits (int): The number of qubits - n_steps (int, optional): The number of steps to divide the time interval - in. Defaults to 100. - - Returns: - torch.Tensor: replaces state with the evolved state according to the - instructions above (save a copy of `state` if you need further - processing on it) - """ - _state = state.clone() - - h = t.reshape((1, -1)) / n_steps - for _ in range(N_qubits - 1): - h = h.unsqueeze(0) - - h = h.expand_as(_state) - - for _ in range(n_steps): - k1 = -1j * _apply_gate(_state, H, qubits, N_qubits) - k2 = -1j * _apply_gate(_state + h / 2 * k1, H, qubits, N_qubits) - k3 = -1j * _apply_gate(_state + h / 2 * k2, H, qubits, N_qubits) - k4 = -1j * _apply_gate(_state + h * k3, H, qubits, N_qubits) - _state += h / 6 * (k1 + 2 * k2 + 2 * k3 + k4) - - return _state - - -@lru_cache(maxsize=256) -def diagonalize(H: torch.Tensor) -> Tuple[torch.Tensor, Optional[torch.Tensor]]: - """ - Diagonalizes an Hermitian Hamiltonian, returning eigenvalues and eigenvectors. - First checks if it's already diagonal, and second checks if H is real. - """ - - def is_diag(H: torch.Tensor) -> bool: - return len(torch.abs(torch.triu(H, diagonal=1)).to_sparse().coalesce().values()) == 0 - - def is_real(H: torch.Tensor) -> bool: - return len(torch.imag(H).to_sparse().coalesce().values()) == 0 - - if is_diag(H): - # Skips diagonalization - eig_values = torch.diagonal(H) - eig_vectors = None - else: - if is_real(H): - eig_values, eig_vectors = torch.linalg.eigh(H.real) - eig_values = eig_values.to(DEFAULT_MATRIX_DTYPE) - eig_vectors = eig_vectors.to(DEFAULT_MATRIX_DTYPE) - else: - eig_values, eig_vectors = torch.linalg.eigh(H) - - return eig_values, eig_vectors - - -def hamiltonian_evolution_eig( - H: torch.Tensor, - state: torch.Tensor, - t: torch.Tensor, - qubits: Any, - N_qubits: int, -) -> torch.Tensor: - """A function to perform time-evolution according to the generator `H` acting on a - `N_qubits`-sized input `state`, for a duration `t`. See also tutorials for more information - on how to use this gate. Uses exact diagonalization. - - Args: - H (torch.Tensor): the dense matrix representing the Hamiltonian, - provided as a `Tensor` object with shape - `(N_0,N_1,...N_(N**2),batch_size)`, i.e. the matrix is reshaped into - the list of its rows - state (torch.Tensor): the input quantum state, of shape `(N_0, N_1,..., N_N, batch_size)` - t (torch.Tensor): the evolution time, real for default unitary evolution - qubits (Any): The qubits support where the H evolution is applied - N_qubits (int): The number of qubits - - Returns: - torch.Tensor: replaces state with the evolved state according to the - instructions above (save a copy of `state` if you need further processing on it) - """ - _state = state.clone() - batch_size_s = state.size()[-1] - batch_size_t = len(t) - - t_evo = torch.zeros(batch_size_s).to(DEFAULT_MATRIX_DTYPE) - - if batch_size_t >= batch_size_s: - t_evo = t[:batch_size_s] - else: - if batch_size_t == 1: - t_evo[:] = t[0] - else: - t_evo[:batch_size_t] = t - - eig_values, eig_vectors = diagonalize(H) - - if eig_vectors is None: - for i, t_val in enumerate(t_evo): - # Compute e^(-i H t) - evol_operator = torch.diag(torch.exp(-1j * eig_values * t_val)) - _state[..., [i]] = _apply_gate(state[..., [i]], evol_operator, qubits, N_qubits) - - else: - for i, t_val in enumerate(t_evo): - # Compute e^(-i D t) - eig_exp = torch.diag(torch.exp(-1j * eig_values * t_val)) - # e^(-i H t) = V.e^(-i D t).V^\dagger - evol_operator = torch.matmul( - torch.matmul(eig_vectors, eig_exp), - torch.conj(eig_vectors.transpose(0, 1)), - ) - _state[..., [i]] = _apply_gate(state[..., [i]], evol_operator, qubits, N_qubits) - - return _state diff --git a/pyqtorch/core/utils.py b/pyqtorch/core/utils.py deleted file mode 100644 index 14c63e1a..00000000 --- a/pyqtorch/core/utils.py +++ /dev/null @@ -1,153 +0,0 @@ -# Copyright 2022 PyQ Development Team - -# 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. -from __future__ import annotations - -from string import ascii_letters as ABC - -import numpy as np -import torch -from numpy.typing import NDArray - -ABC_ARRAY: NDArray = np.array(list(ABC)) - - -def _apply_gate( - state: torch.Tensor, - mat: torch.Tensor, - qubits: list[int] | tuple[int], - N_qubits: int, -) -> torch.Tensor: - """ - Apply the matrix 'mat' corresponding to a gate to `state`. - - Arguments: - state (torch.Tensor): input state of shape [2] * N_qubits + [batch_size] - mat (torch.Tensor): the matrix representing the gate - qubits (list, tuple, array): iterator containing the qubits - the gate is applied to - N_qubits (int): the total number of qubits of the system - - Returns: - state (torch.Tensor): the quantum state after application of the gate. - Same shape as `ìnput_state` - """ - mat = mat.view([2] * len(qubits) * 2) - mat_dims = list(range(len(qubits), 2 * len(qubits))) - qubits = list(qubits) - axes = (mat_dims, qubits) - state = torch.tensordot(mat, state, dims=axes) - inv_perm = torch.argsort( - torch.tensor(qubits + [j for j in range(N_qubits + 1) if j not in qubits], dtype=torch.int) - ) - state = torch.permute(state, tuple(inv_perm)) - return state - - -def _apply_einsum_gate( - state: torch.Tensor, mat: torch.Tensor, qubits: list[int] | tuple[int], N_qubits: int -) -> torch.Tensor: - """ - Same as `apply_gate` but with the `torch.einsum` function - - Arguments: - state (torch.Tensor): input state of shape [2] * N_qubits + [batch_size] - mat (torch.Tensor): the matrix representing the gate - qubits (list, tuple, array): Sized iterator containing the qubits - the gate is applied to - N_qubits (int): the total number of qubits of the system - - Returns: - state (torch.Tensor): The state after application of the gate. - Same shape as `ìnput_state` - """ - mat = mat.reshape([2] * len(qubits) * 2) - state_indices = ABC_ARRAY[0 : N_qubits + 1] - qubits = list(qubits) - # Create new indices for the matrix indices - mat_indices = ABC_ARRAY[N_qubits + 2 : N_qubits + 2 + 2 * len(qubits)] - mat_indices[len(qubits) : 2 * len(qubits)] = state_indices[qubits] - - # Create the new state indices: same as input states but - # modified affected qubits - new_state_indices = state_indices.copy() - new_state_indices[qubits] = mat_indices[0 : len(qubits)] - - # Transform the arrays into strings - state_indices = "".join(list(state_indices)) - new_state_indices = "".join(list(new_state_indices)) - mat_indices = "".join(list(mat_indices)) - - einsum_indices = f"{mat_indices},{state_indices}->{new_state_indices}" - - state = torch.einsum(einsum_indices, mat, state) - - return state - - -def _apply_batch_gate( - state: torch.Tensor, - mat: torch.Tensor, - qubits: list[int] | tuple[int], - N_qubits: int, - batch_size: int, -) -> torch.Tensor: - """ - Apply a batch execution of gates to a circuit. - Given an tensor of states [state_0, ... state_b] and - an tensor of gates [G_0, ... G_b] it will return the - tensor [G_0 * state_0, ... G_b * state_b]. All gates - are applied to the same qubit. - - Arguments: - state (torch.Tensor): input state of shape [2] * N_qubits + [batch_size] - mat (torch.Tensor): the tensor representing the gates. - The last dimension is the batch dimension. - It has to be the sam eas the last dimension of `state` - qubits (list, tuple, array): Sized iterator containing the qubits - the gate is applied to. - N_qubits (int): the total number of qubits of the system - batch_size (int): the size of the batch - - Returns: - torch.Tensor: The state after application of the gate. - Same shape as `input_state`. - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - state = pyq.zero_state(n_qubits=2) - print(state) #tensor([[[1.+0.j],[0.+0.j]],[[0.+0.j],[0.+0.j]]], dtype=torch.complex128) - ``` - """ - mat = mat.view([2] * len(qubits) * 2 + [batch_size]) - - state_indices = ABC_ARRAY[0 : N_qubits + 1].copy() - qubits = list(qubits) - mat_indices = ABC_ARRAY[N_qubits + 2 : N_qubits + 2 + 2 * len(qubits) + 1].copy() - mat_indices[len(qubits) : 2 * len(qubits)] = state_indices[qubits] - mat_indices[-1] = state_indices[-1] - - new_state_indices = state_indices.copy() - new_state_indices[qubits] = mat_indices[0 : len(qubits)] - - state_indices = "".join(list(state_indices)) - new_state_indices = "".join(list(new_state_indices)) - mat_indices = "".join(list(mat_indices)) - - einsum_indices = f"{mat_indices},{state_indices}->{new_state_indices}" - - state = torch.einsum(einsum_indices, mat, state) - - return state diff --git a/pyqtorch/embedding.py b/pyqtorch/embedding.py deleted file mode 100644 index 82a66b16..00000000 --- a/pyqtorch/embedding.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2022 PyQ Development Team - -# 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. -from __future__ import annotations - -import torch - -from pyqtorch.core.batched_operation import batchedRX -from pyqtorch.core.circuit import QuantumCircuit - - -class SingleLayerEncoding(QuantumCircuit): - def __init__(self, n_qubits: int): - super().__init__(n_qubits) - - def forward(self, state: torch.Tensor, x: torch.Tensor) -> torch.Tensor: - for i in range(self.n_qubits): - state = batchedRX(x, state, [i], self.n_qubits) - return state diff --git a/pyqtorch/matrices.py b/pyqtorch/matrices.py index f7c131cc..d7cc1365 100644 --- a/pyqtorch/matrices.py +++ b/pyqtorch/matrices.py @@ -1,24 +1,7 @@ -# Copyright 2022 PyQ Development Team - -# 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. from __future__ import annotations -from typing import Any, Optional, Union - import torch -torch.set_default_dtype(torch.float64) - DEFAULT_MATRIX_DTYPE = torch.cdouble IMAT = torch.eye(2, dtype=DEFAULT_MATRIX_DTYPE) @@ -68,125 +51,43 @@ } -def ZZ(N: int, i: int = 0, j: int = 0, device: Union[str, torch.device] = "cpu") -> torch.Tensor: - """ - Returns the tensor representation of the ZZ interaction operator - between qubits i and j in a quantum circuit. - - Arguments: - N (int): The total number of qubits in the circuit. - i (int): Index of the first qubit (default: 0). - j (int): Index of the second qubit (default: 0). - device (Union[str, torch.device]): Device to store the tensor on (default: "cpu"). - - Returns: - torch.Tensor: The tensor representation of the ZZ interaction operator. - - Examples: - ```python exec="on" source="above" result="json" - from pyqtorch.matrices import ZZ - result=ZZ(2, 0, 1) - print(result) #tensor([ 1.+0.j, -1.+0.j, -1.+0.j, 1.-0.j], dtype=torch.complex128) - ``` - """ - if i == j: - return torch.ones(2**N).to(device) - - op_list = [ZDIAG.to(device) if k in [i, j] else IDIAG.to(device) for k in range(N)] - operator = op_list[0] - for op in op_list[1::]: - operator = torch.kron(operator, op) - - return operator - - -def NN(N: int, i: int = 0, j: int = 0, device: Union[str, torch.device] = "cpu") -> torch.Tensor: - """ - Returns the tensor representation of the NN interaction operator - between qubits i and j in a quantum circuit. - - Arguments: - N (int): The total number of qubits in the circuit. - i (int): Index of the first qubit (default: 0). - j (int): Index of the second qubit (default: 0). - device (Union[str, torch.device]): Device to store the tensor on (default: "cpu"). - - Returns: - torch.Tensor: The tensor representation of the NN interaction operator. - - Examples: - ```python exec="on" source="above" result="json" - from pyqtorch.matrices import NN - result=NN(2, 0, 1) - print(result) #tensor([0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j], dtype=torch.complex128) - ``` - """ - if i == j: - return torch.ones(2**N, dtype=DEFAULT_MATRIX_DTYPE).to(device) - - op_list = [NDIAG.to(device) if k in [i, j] else IDIAG.to(device) for k in range(N)] - operator = op_list[0] - for op in op_list[1::]: - operator = torch.kron(operator, op) - - return operator - - -def single_Z(N: int, i: int = 0, device: Union[str, torch.device] = "cpu") -> torch.Tensor: - op_list = [ZDIAG.to(device) if k == i else IDIAG.to(device) for k in range(N)] - operator = op_list[0] - for op in op_list[1::]: - operator = torch.kron(operator, op) +def _unitary( + theta: torch.Tensor, P: torch.Tensor, I: torch.Tensor, batch_size: int # noqa: E741 +) -> torch.Tensor: + cos_t = torch.cos(theta / 2).unsqueeze(0).unsqueeze(1) + cos_t = cos_t.repeat((2, 2, 1)) + sin_t = torch.sin(theta / 2).unsqueeze(0).unsqueeze(1) + sin_t = sin_t.repeat((2, 2, 1)) - return operator + batch_imat = I.unsqueeze(2).repeat(1, 1, batch_size) + batch_operation_mat = P.unsqueeze(2).repeat(1, 1, batch_size) + return cos_t * batch_imat - 1j * sin_t * batch_operation_mat -def single_N(N: int, i: int = 0, device: Union[str, torch.device] = "cpu") -> torch.Tensor: - op_list = [NDIAG.to(device) if k == i else IDIAG.to(device) for k in range(N)] - operator = op_list[0] - for op in op_list[1::]: - operator = torch.kron(operator, op) - return operator +def _dagger(matrices: torch.Tensor) -> torch.Tensor: # noqa: E741 + return torch.permute(matrices.conj(), (1, 0, 2)) -def sum_Z(N: int, device: Union[str, torch.device] = "cpu") -> torch.Tensor: - H = torch.zeros(2**N, dtype=torch.cdouble).to(device) - for i in range(N): - H += single_Z(N, i, device) - return H +def _jacobian( + theta: torch.Tensor, P: torch.Tensor, I: torch.Tensor, batch_size: int # noqa: E741 +) -> torch.Tensor: + cos_t = torch.cos(theta / 2).unsqueeze(0).unsqueeze(1) + cos_t = cos_t.repeat((2, 2, 1)) + sin_t = torch.sin(theta / 2).unsqueeze(0).unsqueeze(1) + sin_t = sin_t.repeat((2, 2, 1)) + batch_imat = I.unsqueeze(2).repeat(1, 1, batch_size) + batch_operation_mat = P.unsqueeze(2).repeat(1, 1, batch_size) -def sum_N(N: int, device: Union[str, torch.device] = "cpu") -> torch.Tensor: - H = torch.zeros(2**N, dtype=torch.cdouble).to(device) - for i in range(N): - H += single_N(N, i, device) - return H + return -1 / 2 * (sin_t * batch_imat + 1j * cos_t * batch_operation_mat) -def generate_ising_from_graph( - graph: Any, # optional library type - precomputed_zz: Optional[torch.Tensor] = None, - type_ising: str = "Z", - device: Union[str, torch.device] = "cpu", -) -> torch.Tensor: - N = graph.number_of_nodes() - # construct the hamiltonian - H = torch.zeros(2**N, dtype=DEFAULT_MATRIX_DTYPE).to(device) - - for edge in graph.edges.data(): - if precomputed_zz is not None: - if (edge[0], edge[1]) in precomputed_zz[N]: - key = (edge[0], edge[1]) - else: - key = (edge[1], edge[0]) - H += precomputed_zz[N][key] - else: - if type_ising == "Z": - H += ZZ(N, edge[0], edge[1], device) - elif type_ising == "N": - H += NN(N, edge[0], edge[1], device) - else: - raise ValueError("'type_ising' must be in ['Z', 'N']") - - return H +def _controlled(unitary: torch.Tensor, batch_size: int, n_control_qubits: int = 1) -> torch.Tensor: + _controlled: torch.Tensor = ( + torch.eye(2 ** (n_control_qubits + 1), dtype=DEFAULT_MATRIX_DTYPE) + .unsqueeze(2) + .repeat(1, 1, batch_size) + ) + _controlled[2 ** (n_control_qubits + 1) - 2 :, 2 ** (n_control_qubits + 1) - 2 :, :] = unitary + return _controlled diff --git a/pyqtorch/matrices_sparse.py b/pyqtorch/matrices_sparse.py deleted file mode 100644 index 5839b1c6..00000000 --- a/pyqtorch/matrices_sparse.py +++ /dev/null @@ -1,194 +0,0 @@ -# Copyright 2022 PyQ Development Team - -# 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. -from __future__ import annotations - -import typing -from itertools import combinations -from typing import Any, Union - -import numpy as np -import scipy.sparse as sp -import torch - -IMAT = sp.coo_matrix(np.eye(2, dtype=np.cdouble)) -XMAT = sp.coo_matrix(np.array([[0, 1], [1, 0]], dtype=np.cdouble)) -YMAT = sp.coo_matrix(np.array([[0, -1j], [1j, 0]], dtype=np.cdouble)) -ZMAT = sp.coo_matrix(np.array([[1, 0], [0, -1]], dtype=np.cdouble)) -NMAT = sp.coo_matrix(np.array([0, 1], dtype=np.cdouble)) - - -def XX( - N: int, i: int = 0, j: int = 0, device: Union[str, torch.device] = "cpu" -) -> Union[sp.coo_matrix, sp.bsr_matrix]: - op_list = [XMAT.copy() if k in [i, j] else IMAT.copy() for k in range(N)] - operator = op_list[0] - for op in op_list[1::]: - operator = sp.kron(operator, op, format="coo") - return operator - - -def YY( - N: int, i: int = 0, j: int = 0, device: Union[str, torch.device] = "cpu" -) -> Union[sp.coo_matrix, sp.bsr_matrix]: - op_list = [YMAT.copy() if k in [i, j] else IMAT.copy() for k in range(N)] - operator = op_list[0] - for op in op_list[1::]: - operator = sp.kron(operator, op, format="coo") - return operator - - -def ZZ( - N: int, i: int = 0, j: int = 0, device: Union[str, torch.device] = "cpu" -) -> Union[sp.coo_matrix, sp.bsr_matrix]: - op_list = [ZMAT.copy() if k in [i, j] else IMAT.copy() for k in range(N)] - operator = op_list[0] - for op in op_list[1::]: - operator = sp.kron(operator, op, format="coo") - return operator - - -def NN( - N: int, i: int = 0, j: int = 0, device: Union[str, torch.device] = "cpu" -) -> Union[sp.coo_matrix, sp.bsr_matrix]: - op_list = [NMAT.copy() if k in [i, j] else IMAT.copy() for k in range(N)] - operator = op_list[0] - for op in op_list[1::]: - operator = sp.kron(operator, op, format="coo") - return operator - - -def single_Z( - N: int, i: int = 0, device: Union[str, torch.device] = "cpu" -) -> Union[sp.coo_matrix, sp.bsr_matrix]: - op_list = [ZMAT.copy() if k == i else IMAT.copy() for k in range(N)] - operator = op_list[0] - for op in op_list[1::]: - operator = sp.kron(operator, op, format="coo") - return operator - - -def single_N( - N: int, i: int = 0, device: Union[str, torch.device] = "cpu" -) -> Union[sp.coo_matrix, sp.bsr_matrix]: - op_list = [NMAT.copy() if k == i else IMAT.copy() for k in range(N)] - operator = op_list[0] - for op in op_list[1::]: - operator = sp.kron(operator, op, format="coo") - return operator - - -def sum_Z(N: int, device: Union[str, torch.device] = "cpu") -> torch.Tensor: - H = torch.zeros(2**N, dtype=torch.cdouble).to(device) - for i in range(N): - H += single_Z(N, i, device) - return H - - -def sum_N(N: int, device: Union[str, torch.device] = "cpu") -> torch.Tensor: - H = torch.zeros(2**N, dtype=torch.cdouble).to(device) - for i in range(N): - H += single_N(N, i, device) - return H - - -def generate_ising_from_graph( - graph: Any, - precomputed_zz: Any = None, - type_ising: str = "Z", - device: Union[str, torch.device] = "cpu", -) -> torch.Tensor: - N = graph.number_of_nodes() - # construct the hamiltonian - H = torch.zeros(2**N, dtype=torch.cdouble).to(device) - - for edge in graph.edges.data(): - if precomputed_zz is not None: - if (edge[0], edge[1]) in precomputed_zz[N]: - key = (edge[0], edge[1]) - else: - key = (edge[1], edge[0]) - H += precomputed_zz[N][key] - else: - if type_ising == "Z": - H += ZZ(N, edge[0], edge[1], device).copy() - elif type_ising == "N": - H += NN(N, edge[0], edge[1], device).copy() - else: - raise ValueError("'type_ising' must be in ['Z', 'N']") - - return H - - -@typing.no_type_check -def general_hamiltonian( - graph: Any = None, - alpha: Any = None, - beta: Any = None, - gamma: Any = None, - device: Union[str, torch.device] = "cpu", -) -> Union[sp.coo_matrix, sp.bsr_matrix]: - # alpha, beta, gamma: matrices of parameters (e.g. alpha_ij) - # connectivity_graph: which qubits are connected - # no 1 body terms - N = alpha.shape[0] - # construct the hamiltonian - H = sp.coo_matrix((2**N, 2**N)) - for edge in combinations(range(N), 2): - if alpha is not None: - if alpha[edge[0], edge[1]] > 1e-15: - h = alpha[edge[0], edge[1]] * XX(N, edge[0], edge[1]).copy() - H += h.copy() # alpha_ij * XX - if beta is not None: - if beta[edge[0], edge[1]] > 1e-15: - h = beta[edge[0], edge[1]] * YY(N, edge[0], edge[1]).copy() - H += h.copy() # beta_ij * YY - if gamma is not None: - if gamma[edge[0], edge[1]] > 1e-15: - h = gamma[edge[0], edge[1]] * ZZ(N, edge[0], edge[1]).copy() - H += h.copy() # gamma_ij * ZZ - return H - - -def get_sparse_torch(coo_matrix: Union[sp.coo_matrix, sp.bsr_matrix]) -> torch.Tensor: - values = coo_matrix.data - indices = np.vstack((coo_matrix.row, coo_matrix.col)) - - i = torch.LongTensor(indices) - v = torch.FloatTensor(values) - shape = coo_matrix.shape - - tensor = torch.sparse_coo_tensor(i, v, torch.Size(shape), dtype=torch.cdouble) - return tensor - - -def heisenberg_hamiltonian( # type: ignore [empty-body] - graph: Any, - alpha: Any, - beta: Any, - gamma: Any, - device: Union[str, torch.device] = "cpu", -) -> torch.Tensor: - # to do - pass - - -def XY_hamiltonian( # type: ignore [empty-body] - graph: Any, - alpha: Any, - beta: Any, - gamma: Any, - device: Union[str, torch.device] = "cpu", -) -> torch.Tensor: - # to do - pass diff --git a/pyqtorch/modules/__init__.py b/pyqtorch/modules/__init__.py deleted file mode 100644 index 06dd17c9..00000000 --- a/pyqtorch/modules/__init__.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import annotations - -from pyqtorch.modules.circuit import ( - EntanglingLayer, - FeaturemapLayer, - QuantumCircuit, - VariationalLayer, -) -from pyqtorch.modules.hamevo import HamEvo, HamEvoEig, HamEvoExp, HamEvoType, HamiltonianEvolution -from pyqtorch.modules.parametric import CPHASE, CRX, CRY, CRZ, PHASE, RX, RY, RZ, U -from pyqtorch.modules.primitive import ( - CNOT, - CSWAP, - CY, - CZ, - SWAP, - H, - I, - N, - S, - SDagger, - T, - Toffoli, - X, - Y, - Z, -) -from pyqtorch.modules.utils import ( - _apply_parallel, - flatten_wf, - invert_endianness, - is_normalized, - normalize, - overlap, - random_state, - uniform_state, - zero_state, -) diff --git a/pyqtorch/modules/abstract.py b/pyqtorch/modules/abstract.py deleted file mode 100644 index a3fee0ab..00000000 --- a/pyqtorch/modules/abstract.py +++ /dev/null @@ -1,55 +0,0 @@ -from __future__ import annotations - -from abc import ABC, abstractmethod -from typing import Any - -import torch -from numpy.typing import ArrayLike -from torch.nn import Module - -import pyqtorch.modules as pyq - - -class AbstractGate(ABC, Module): - def __init__(self, qubits: ArrayLike, n_qubits: int): - super().__init__() - self.qubits = qubits - self.n_qubits = n_qubits - - def __mul__(self, other: AbstractGate | pyq.QuantumCircuit) -> pyq.QuantumCircuit: - if isinstance(other, AbstractGate): - ml = torch.nn.ModuleList([self, other]) - return pyq.QuantumCircuit(max(self.n_qubits, other.n_qubits), ml) - elif isinstance(other, pyq.QuantumCircuit): - ml = torch.nn.ModuleList([self]) + other.operations - return pyq.QuantumCircuit(max(self.n_qubits, other.n_qubits), ml) - else: - return TypeError(f"Cannot compose {type(self)} with {type(other)}") - - def __key(self) -> tuple: - return (self.n_qubits, *self.qubits) - - def __eq__(self, other: Any) -> bool: - if isinstance(other, type(self)): - return self.__key() == other.__key() - return NotImplemented - - def __hash__(self) -> int: - return hash(self.__key()) - - @abstractmethod - def matrices(self, tensors: torch.Tensor) -> torch.Tensor: - # NOTE: thetas are assumed to be of shape (1,batch_size) or (batch_size,) because we - # want to allow e.g. (3,batch_size) in the U gate. - ... - - @abstractmethod - def apply(self, matrix: torch.Tensor, state: torch.Tensor) -> torch.Tensor: - ... - - @abstractmethod - def forward(self, state: torch.Tensor, thetas: torch.Tensor) -> torch.Tensor: - ... - - def extra_repr(self) -> str: - return f"qubits={self.qubits}, n_qubits={self.n_qubits}" diff --git a/pyqtorch/modules/circuit.py b/pyqtorch/modules/circuit.py deleted file mode 100644 index e66dd46d..00000000 --- a/pyqtorch/modules/circuit.py +++ /dev/null @@ -1,196 +0,0 @@ -from __future__ import annotations - -from typing import Any - -import torch -from torch.nn import Module, ModuleList, Parameter, init - -from pyqtorch.modules.abstract import AbstractGate -from pyqtorch.modules.primitive import CNOT -from pyqtorch.modules.utils import zero_state - - -class QuantumCircuit(Module): - def __init__(self, n_qubits: int, operations: list): - """ - Creates a QuantumCircuit that can be used to compose multiple gates - from a list of operations. - - Arguments: - n_qubits (int): The total number of qubits in the circuit. - operations (list): A list of gate operations to be applied in the circuit. - - Example: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - #create a circuit with 2 qubits than provide a list of operations . - #in this example we apply a X gate followed by a CNOT gate. - circ = pyq.QuantumCircuit( - n_qubits=2, - operations=[ - pyq.X([0], 2), - pyq.CNOT([0,1], 2) - ] - ) - #create a zero state - z = pyq.zero_state(2) - - #apply the circuit and its list of operations onto the zero state - result=circ(z) - - #print the result - print(result) #tensor([[[0.+0.j],[0.+0.j]],[[0.+0.j],[1.+0.j]]], dtype=torch.complex128) - ``` - """ - super().__init__() - self.n_qubits = n_qubits - self.operations = torch.nn.ModuleList(operations) - - def __mul__(self, other: AbstractGate | QuantumCircuit) -> QuantumCircuit: - if isinstance(other, QuantumCircuit): - n_qubits = max(self.n_qubits, other.n_qubits) - return QuantumCircuit(n_qubits, self.operations.extend(other.operations)) - - if isinstance(other, AbstractGate): - n_qubits = max(self.n_qubits, other.n_qubits) - return QuantumCircuit(n_qubits, self.operations.append(other)) - - else: - return ValueError(f"Cannot compose {type(self)} with {type(other)}") - - def __key(self) -> tuple: - return (self.n_qubits, *self.operations) - - def __eq__(self, other: Any) -> bool: - if isinstance(other, QuantumCircuit): - return self.__key() == other.__key() - return NotImplemented - - def __hash__(self) -> int: - return hash(self.__key()) - - def forward(self, state: torch.Tensor, thetas: torch.Tensor = None) -> torch.Tensor: - """ - Forward pass of the quantum circuit. - - Arguments: - state (torch.Tensor): The input quantum state tensor. - thetas (torch.Tensor): Optional tensor of parameters for the circuit operations. - - Returns: - torch.Tensor: The output quantum state tensor after applying the circuit operations. - - """ - for op in self.operations: - state = op(state, thetas) - return state - - @property - def _device(self) -> torch.device: - try: - (_, buffer) = next(self.named_buffers()) - return buffer.device - except StopIteration: - return torch.device("cpu") - - def init_state(self, batch_size: int) -> torch.Tensor: - return zero_state(self.n_qubits, batch_size, device=self._device) - - -def FeaturemapLayer(n_qubits: int, Op: Any) -> QuantumCircuit: - """ - Creates a feature map layer in a quantum neural network. - The FeaturemapLayer is a convenience constructor for a QuantumCircuit - which accepts an operation to put on every qubit. - - Arguments: - n_qubits (int): The total number of qubits in the circuit. - Op (Any): The quantum operation to be applied in the feature map layer. - - Returns: - QuantumCircuit: The feature map layer represented as a QuantumCircuit. - - Example: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - #create a FeaturemapLayer to apply the RX operation on all 3 Qubits - circ = pyq.FeaturemapLayer(n_qubits=3, Op=pyq.RX) - print(circ) - - states = pyq.zero_state(n_qubits=3, batch_size=4) - inputs = torch.rand(4) - - # the same batch of inputs are passed to the operations - circ(states, inputs).shape - ``` - """ - operations = [Op([i], n_qubits) for i in range(n_qubits)] - return QuantumCircuit(n_qubits, operations) - - -class VariationalLayer(QuantumCircuit): - def __init__(self, n_qubits: int, Op: Any): - """ - Represents a variational layer in a quantum neural network allowing you - to create a trainable QuantumCircuit. - If you want the angles of your circuit to be trainable you can use a VariationalLayer. - The VariationalLayer ignores the second input (because it has trainable angle parameters). - - Arguments: - n_qubits (int): The total number of qubits in the circuit. - Op (Any): The quantum operation to be applied in the variational layer. - - - Example: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - #create a variational layer with 3 qubits and operation of RX as the second parameter - circ = pyq.VariationalLayer(n_qubits=3, Op=pyq.RX) - state = pyq.zero_state(3) - this_argument_is_ignored = None - result=circ(state, this_argument_is_ignored) - print(result) - ``` - """ - operations = ModuleList([Op([i], n_qubits) for i in range(n_qubits)]) - super().__init__(n_qubits, operations) - - self.thetas = Parameter(torch.empty(n_qubits, Op.n_params)) - self.reset_parameters() - - def reset_parameters(self) -> None: - init.uniform_(self.thetas, -2 * torch.pi, 2 * torch.pi) - - def forward(self, state: torch.Tensor, _: torch.Tensor = None) -> torch.Tensor: - for op, t in zip(self.operations, self.thetas): - state = op(state, t) - return state - - -class EntanglingLayer(QuantumCircuit): - def __init__(self, n_qubits: int): - """ - Represents an entangling layer in a quantum neural network by entangling Qubits - - Args: - n_qubits (int): The total number of qubits in the circuit. - - Example: - ```python exec="on" source="above" result="json" - from pyqtorch.modules.circuit import EntanglingLayer - - # Create an entangling layer with 4 qubits - entangling_layer = EntanglingLayer(n_qubits=4) - - print(entangling_layer) - ``` - """ - operations = ModuleList( - [CNOT([i % n_qubits, (i + 1) % n_qubits], n_qubits) for i in range(n_qubits)] - ) - super().__init__(n_qubits, operations) diff --git a/pyqtorch/modules/hamevo.py b/pyqtorch/modules/hamevo.py deleted file mode 100644 index 32ae34c9..00000000 --- a/pyqtorch/modules/hamevo.py +++ /dev/null @@ -1,345 +0,0 @@ -from __future__ import annotations - -from enum import Enum -from functools import lru_cache -from typing import Any, Optional, Tuple, Union - -import torch -from torch.nn import Module - -from pyqtorch.core.utils import _apply_batch_gate -from pyqtorch.modules.utils import is_diag, is_real - -BATCH_DIM = 2 - - -class HamEvo(torch.nn.Module): - """ - Base class for Hamiltonian evolution classes, performing the evolution using RK4 method. - - Args: - H (tensor): Hamiltonian tensor. - t (tensor): Time tensor. - qubits (Any): Qubits for operation. - n_qubits (int): Number of qubits. - n_steps (int): Number of steps to be performed in RK4-based evolution. Defaults to 100. - - - """ - - def __init__( - self, H: torch.Tensor, t: torch.Tensor, qubits: Any, n_qubits: int, n_steps: int = 100 - ): - super().__init__() - self.H: torch.Tensor - self.t: torch.Tensor - self.qubits = qubits - self.n_qubits = n_qubits - self.n_steps = n_steps - if H.ndim == 2: - H = H.unsqueeze(2) - if H.size(-1) == t.size(0) or t.size(0) == 1: - self.register_buffer("H", H) - self.register_buffer("t", t) - elif H.size(-1) == 1: - (x, y, _) = H.size() - self.register_buffer("H", H.expand(x, y, t.size(0))) - self.register_buffer("t", t) - else: - msg = "H and t batchsizes either have to match or (one of them has to) be equal to one." - raise ValueError(msg) - - def apply(self, state: torch.Tensor) -> torch.Tensor: - """ - Applies the Hamiltonian evolution operation on the given state using RK4 method. - - Args: - state (tensor): Input quantum state. - - Returns: - tensor: Output state after Hamiltonian evolution. - """ - - batch_size = max(state.size(-1), self.H.size(-1)) - if state.size(-1) == 1: - state = state.repeat(*[1 for _ in range(len(state.size()) - 1)], batch_size) - - h = self.t.reshape((1, -1)) / self.n_steps - for _ in range(self.n_qubits - 1): - h = h.unsqueeze(0) - - h = h.expand_as(state) - _state = state.clone() - for _ in range(self.n_steps): - k1 = -1j * _apply_batch_gate(_state, self.H, self.qubits, self.n_qubits, batch_size) - k2 = -1j * _apply_batch_gate( - _state + h / 2 * k1, self.H, self.qubits, self.n_qubits, batch_size - ) - k3 = -1j * _apply_batch_gate( - _state + h / 2 * k2, self.H, self.qubits, self.n_qubits, batch_size - ) - k4 = -1j * _apply_batch_gate( - _state + h * k3, self.H, self.qubits, self.n_qubits, batch_size - ) - _state += h / 6 * (k1 + 2 * k2 + 2 * k3 + k4) - - return _state - - def forward(self, state: torch.Tensor) -> torch.Tensor: - """ - Forward pass of the module, applies the Hamiltonian evolution operation. - - Args: - state (tensor): Input quantum state. - - Returns: - tensor: Output state after Hamiltonian evolution. - """ - - return self.apply(state) - - -@lru_cache(maxsize=256) -def diagonalize(H: torch.Tensor) -> Tuple[torch.Tensor, Optional[torch.Tensor]]: - """ - Diagonalizes an Hermitian Hamiltonian, returning eigenvalues and eigenvectors. - First checks if it's already diagonal, and second checks if H is real. - """ - - if is_diag(H): - # Skips diagonalization - eig_values = torch.diagonal(H) - eig_vectors = None - else: - if is_real(H): - eig_values, eig_vectors = torch.linalg.eigh(H.real) - eig_values = eig_values.to(torch.cdouble) - eig_vectors = eig_vectors.to(torch.cdouble) - else: - eig_values, eig_vectors = torch.linalg.eigh(H) - - return eig_values, eig_vectors - - -class HamEvoEig(HamEvo): - """ - Class for Hamiltonian evolution operation using Eigenvalue Decomposition method. - - Args: - H (tensor): Hamiltonian tensor - t (tensor): Time tensor - qubits (Any): Qubits for operation - n_qubits (int): Number of qubits - n_steps (int): Number of steps to be performed, defaults to 100 - """ - - def __init__( - self, H: torch.Tensor, t: torch.Tensor, qubits: Any, n_qubits: int, n_steps: int = 100 - ): - super().__init__(H, t, qubits, n_qubits, n_steps) - if len(self.H.size()) < 3: - self.H = self.H.unsqueeze(2) - batch_size_h = self.H.size()[BATCH_DIM] - batch_size_t = self.t.size(0) - - self._eigs = [] - if batch_size_h == batch_size_t or batch_size_t == 1: - for i in range(batch_size_h): - eig_values, eig_vectors = diagonalize(self.H[..., i]) - self._eigs.append((eig_values, eig_vectors)) - elif batch_size_h == 1: - eig_values, eig_vectors = diagonalize(self.H[..., 0]) - for i in range(batch_size_t): - self._eigs.append((eig_values, eig_vectors)) - else: - msg = "H and t batchsizes either have to match or (one of them has to) be equal to one." - raise ValueError(msg) - self.batch_size = max(batch_size_h, batch_size_t) - - def apply(self, state: torch.Tensor) -> torch.Tensor: - """ - Applies the Hamiltonian evolution operation on the given state - using Eigenvalue Decomposition method. - - Args: - state (tensor): Input quantum state. - - Returns: - tensor: Output state after Hamiltonian evolution. - """ - - (x, y, _) = self.H.size() - evol_operator = torch.zeros(x, y, self.batch_size).to(torch.cdouble) - t_evo = self.t.repeat(self.batch_size) if self.t.size(0) == 1 else self.t - - for i, (eig_values, eig_vectors) in enumerate(self._eigs): - if eig_vectors is None: - # Compute e^(-i H t) - evol_operator[..., i] = torch.diag(torch.exp(-1j * eig_values * t_evo[i])) - - else: - # Compute e^(-i D t) - eig_exp = torch.diag(torch.exp(-1j * eig_values * t_evo[i])) - # e^(-i H t) = V.e^(-i D t).V^\dagger - evol_operator[..., i] = torch.matmul( - torch.matmul(eig_vectors, eig_exp), - torch.conj(eig_vectors.transpose(0, 1)), - ) - - return _apply_batch_gate(state, evol_operator, self.qubits, self.n_qubits, self.batch_size) - - -class HamEvoExp(HamEvo): - """ - Class for Hamiltonian evolution operation, using matrix exponential method. - - Args: - H (tensor): Hamiltonian tensor - t (tensor): Time tensor - qubits (Any): Qubits for operation - n_qubits (int): Number of qubits - n_steps (int): Number of steps to be performed, defaults to 100. - """ - - def __init__( - self, H: torch.Tensor, t: torch.Tensor, qubits: Any, n_qubits: int, n_steps: int = 100 - ): - super().__init__(H, t, qubits, n_qubits, n_steps) - if len(self.H.size()) < 3: - self.H = self.H.unsqueeze(2) - batch_size_h = self.H.size()[BATCH_DIM] - - # Check if all hamiltonians in the batch are diagonal - diag_check = torch.tensor([is_diag(self.H[..., i]) for i in range(batch_size_h)]) - self.batch_is_diag = bool(torch.prod(diag_check)) - - def apply(self, state: torch.Tensor) -> torch.Tensor: - """ - Applies the Hamiltonian evolution operation - on the given state using matrix exponential method. - - Args: - state (tensor): Input quantum state. - - Returns: - tensor: Output state after Hamiltonian evolution. - """ - - batch_size_t = len(self.t) - batch_size_h = self.H.size()[BATCH_DIM] - t_evo = self.t - - if self.batch_is_diag: - # Skips the matrix exponential for diagonal hamiltonians - H_diagonals = torch.diagonal(self.H) - evol_exp_arg = H_diagonals * (-1j * t_evo).view((-1, 1)) - evol_operator_T = torch.diag_embed(torch.exp(evol_exp_arg)) - evol_operator = torch.transpose(evol_operator_T, 0, -1) - else: - H_T = torch.transpose(self.H, 0, -1) - evol_exp_arg = H_T * (-1j * t_evo).view((-1, 1, 1)) - evol_operator_T = torch.linalg.matrix_exp(evol_exp_arg) - evol_operator = torch.transpose(evol_operator_T, 0, -1) - - batch_size = max(batch_size_h, batch_size_t) - return _apply_batch_gate(state, evol_operator, self.qubits, self.n_qubits, batch_size) - - -class HamEvoType(Enum): - """ - An Enumeration to represent types of Hamiltonian Evolution - - RK4: Hamiltonian evolution performed using the 4th order Runge-Kutta method. - EIG: Hamiltonian evolution performed using Eigenvalue Decomposition. - EXP: Hamiltonian evolution performed using the Exponential of the Hamiltonian. - """ - - RK4 = HamEvo - EIG = HamEvoEig - EXP = HamEvoExp - - -class HamiltonianEvolution(Module): - """ - A module to encapsulate Hamiltonian Evolution operations. - - Performs Hamiltonian Evolution using different strategies - such as, RK4, Eigenvalue Decomposition, and Exponential, - Based on the 'hamevo_type' parameter. - - Attributes: - qubits: A list of qubits to be used in the operation - n_qubits (int): Total number of qubits - n_steps (int): The number of steps to be performed. Defaults to 100 - hamevo_type (Enum or str): The type of Hamiltonian evolution to be performed. - Must be a member of the `HamEvoType` enum or equivalent string. - Defaults to HamEvoExp. - - Examples: - (1) - # Instantiate HamiltonianEvolution with RK4 string input - >>> hamiltonian_evolution = HamiltonianEvolution(qubits, n_qubits, 100, "RK4") - # Use the HamiltonianEvolution instance to evolve the state - >>> output_state = hamiltonian_evolution(H, t, state) - - (2) - # Instantiate HamiltonianEvolution with HamEvoType. input - >>>H_evol = HamiltonianEvolution(qubits, n_qubits, 100, HamEvoType.Eig) - # Use the HamiltonianEvolution instance to evolve the state - >>>output = H_evol(H, t, state) # Use the HamiltonianEvolution instance to evolve the state - - """ - - def __init__( - self, - qubits: Any, - n_qubits: int, - n_steps: int = 100, - hamevo_type: Union[HamEvoType, str] = HamEvoType.EXP, - ): - super().__init__() - self.qubits = qubits - self.n_qubits = n_qubits - self.n_steps = n_steps - - # Handles the case where the Hamiltonian Evolution type is provided as a string - if isinstance(hamevo_type, str): - try: - hamevo_type = HamEvoType[hamevo_type.upper()] - except KeyError: - allowed = [e.name for e in HamEvoType] - raise ValueError( - f"Invalid Hamiltonian Evolution type: {hamevo_type}. Expected from: {allowed}" - ) - - self.hamevo_type = hamevo_type - - def get_hamevo_instance(self, H: torch.Tensor, t: torch.Tensor) -> torch.nn.Module: - """ - Returns an instance of the Hamiltonian evolution object of the appropriate type. - - Args: - H (tensor): The Hamiltonian to be used in the evolution. - t (tensor): The evolution time. - - Returns: - An instance of the Hamiltonian evolution object of the appropriate type. - - """ - - return self.hamevo_type.value(H, t, self.qubits, self.n_qubits, self.n_steps) - - def forward(self, H: torch.Tensor, t: torch.Tensor, state: torch.Tensor) -> torch.Tensor: - """ - Performs Hamiltonian evolution on the given state. - - Args: - H (tensor): The Hamiltonian to be used in the evolution. - t (tensor): The evolution time. - state (tensor): The state on which to perform Hamiltonian evolution. - - Returns: - The state (tensor) after Hamiltonian evolution. - """ - ham_evo_instance = self.get_hamevo_instance(H, t) - return ham_evo_instance.forward(state) diff --git a/pyqtorch/modules/ops.py b/pyqtorch/modules/ops.py deleted file mode 100644 index 3002e495..00000000 --- a/pyqtorch/modules/ops.py +++ /dev/null @@ -1,53 +0,0 @@ -from __future__ import annotations - -import torch - - -def _vmap_gate( - state: torch.Tensor, - mat: torch.Tensor, - qubits: list[int] | tuple[int], - n_qubits: int, - batch_size: int, -) -> torch.Tensor: - """ - Vmap a batched gate over a batched state and - apply the matrix 'mat' to `state`. - - Arguments: - state (torch.Tensor): input state of shape [2] * N_qubits + [batch_size] - mat (torch.Tensor): the matrix representing the gate - qubits (list, tuple, array): iterator containing the qubits - the gate is applied to - n_qubits (int): the total number of qubits of the system - batch - - Returns: - state (torch.Tensor): the quantum state after application of the gate. - Same shape as `ìnput_state` - """ - - def _apply( - state: torch.Tensor, - mat: torch.Tensor, - qubits: list[int] | tuple[int], - n_qubits: int, - ) -> torch.Tensor: - mat = mat.view([2] * len(qubits) * 2) - mat_dims = list(range(len(qubits), 2 * len(qubits))) - state_dims = list(qubits) - axes = (mat_dims, state_dims) - state = torch.tensordot(mat, state, dims=axes) - inv_perm = torch.argsort( - torch.tensor( - state_dims + [j for j in range(n_qubits) if j not in state_dims], dtype=torch.int - ) - ) - state = torch.permute(state, tuple(inv_perm)) - return state - - return torch.vmap( - lambda s, m: _apply(state=s, mat=m, qubits=qubits, n_qubits=n_qubits), - in_dims=(len(state.size()) - 1, len(mat.size()) - 1), - out_dims=len(state.size()) - 1, - )(state, mat) diff --git a/pyqtorch/modules/parametric.py b/pyqtorch/modules/parametric.py deleted file mode 100644 index a8fa06f8..00000000 --- a/pyqtorch/modules/parametric.py +++ /dev/null @@ -1,483 +0,0 @@ -from __future__ import annotations - -from enum import Enum - -import torch -from numpy.typing import ArrayLike - -from pyqtorch.core.batched_operation import ( - create_controlled_batch_from_operation, -) -from pyqtorch.core.utils import _apply_batch_gate -from pyqtorch.matrices import DEFAULT_MATRIX_DTYPE, OPERATIONS_DICT -from pyqtorch.modules.abstract import AbstractGate -from pyqtorch.modules.ops import _vmap_gate -from pyqtorch.modules.utils import rot_matrices - -torch.set_default_dtype(torch.float64) - - -class StrEnum(str, Enum): - def __str__(self) -> str: - """Used when dumping enum fields in a schema.""" - ret: str = self.value - return ret - - @classmethod - def list(cls) -> list[str]: - return list(map(lambda c: c.value, cls)) # type: ignore - - -class ApplyFn(StrEnum): - """Which function to use to perform matmul between gate and state.""" - - VMAP = "vmap" - EINSUM = "einsum" - - -APPLY_FN_DICT = {ApplyFn.VMAP: _vmap_gate, ApplyFn.EINSUM: _apply_batch_gate} -DEFAULT_APPLY_FN = ApplyFn.EINSUM - - -class RotationGate(AbstractGate): - n_params = 1 - - def __init__( - self, gate: str, qubits: ArrayLike, n_qubits: int, apply_fn_type: ApplyFn = DEFAULT_APPLY_FN - ): - super().__init__(qubits, n_qubits) - self.gate = gate - self.register_buffer("imat", OPERATIONS_DICT["I"]) - self.register_buffer("paulimat", OPERATIONS_DICT[gate]) - self.apply_fn = APPLY_FN_DICT[apply_fn_type] - - def matrices(self, thetas: torch.Tensor) -> torch.Tensor: - theta = thetas.squeeze(0) if thetas.ndim == 2 else thetas - batch_size = len(theta) - return rot_matrices(theta, self.paulimat, self.imat, batch_size) - - def apply(self, matrices: torch.Tensor, state: torch.Tensor) -> torch.Tensor: - batch_size = matrices.size(-1) - return self.apply_fn(state, matrices, self.qubits, self.n_qubits, batch_size) - - def forward(self, state: torch.Tensor, thetas: torch.Tensor) -> torch.Tensor: - mats = self.matrices(thetas) - return self.apply(mats, state) - - def extra_repr(self) -> str: - return f"qubits={self.qubits}, n_qubits={self.n_qubits}" - - -class U(AbstractGate): - n_params = 3 - - def __init__(self, qubits: ArrayLike, n_qubits: int, apply_fn_type: ApplyFn = DEFAULT_APPLY_FN): - """ - Represents a parametrized arbitrary rotation along the axes of the Bloch sphere. - - The angles `phi, theta, omega` in tensor format, applied as: - - U(phi, theta, omega) = RZ(omega)RY(theta)RZ(phi) - - Arguments: - qubits (ArrayLike): The target qubits for the U gate. It should be a list of qubits. - n_qubits (int): The total number of qubits in the circuit. - - """ - - super().__init__(qubits, n_qubits) - - self.register_buffer( - "a", torch.tensor([[1, 0], [0, 0]], dtype=DEFAULT_MATRIX_DTYPE).unsqueeze(2) - ) - self.register_buffer( - "b", torch.tensor([[0, 1], [0, 0]], dtype=DEFAULT_MATRIX_DTYPE).unsqueeze(2) - ) - self.register_buffer( - "c", torch.tensor([[0, 0], [1, 0]], dtype=DEFAULT_MATRIX_DTYPE).unsqueeze(2) - ) - self.register_buffer( - "d", torch.tensor([[0, 0], [0, 1]], dtype=DEFAULT_MATRIX_DTYPE).unsqueeze(2) - ) - - def matrices(self, thetas: torch.Tensor) -> torch.Tensor: - if thetas.ndim == 1: - thetas = thetas.unsqueeze(1) - assert thetas.size(0) == 3 - phi, theta, omega = thetas[0, :], thetas[1, :], thetas[2, :] - batch_size = thetas.size(1) - - t_plus = torch.exp(-1j * (phi + omega) / 2) - t_minus = torch.exp(-1j * (phi - omega) / 2) - sin_t = torch.sin(theta / 2).unsqueeze(0).unsqueeze(1).repeat((2, 2, 1)) - cos_t = torch.cos(theta / 2).unsqueeze(0).unsqueeze(1).repeat((2, 2, 1)) - - a = self.a.repeat(1, 1, batch_size) * cos_t * t_plus - b = self.b.repeat(1, 1, batch_size) * sin_t * torch.conj(t_minus) - c = self.c.repeat(1, 1, batch_size) * sin_t * t_minus - d = self.d.repeat(1, 1, batch_size) * cos_t * torch.conj(t_plus) - return a - b + c + d - - def apply(self, matrices: torch.Tensor, state: torch.Tensor) -> torch.Tensor: - batch_size = matrices.size(-1) - return _apply_batch_gate(state, matrices, self.qubits, self.n_qubits, batch_size) - - def forward(self, state: torch.Tensor, thetas: torch.Tensor) -> torch.Tensor: - """ - Represents batched state and theta forwarding to the matrices - and apply functions that are part of the U gate implementation - - Arguments: - state (torch.Tensor): batched state - thetas (torch.Tensor): Tensor of size 3 ,contains values of `phi`/`theta`/`omega` - - Returns: - torch.Tensor: the resulting state after applying the gate - """ - mats = self.matrices(thetas) - return self.apply(mats, state) - - -class ControlledRotationGate(AbstractGate): - n_params = 1 - - def __init__( - self, gate: str, qubits: ArrayLike, n_qubits: int, apply_fn_type: ApplyFn = DEFAULT_APPLY_FN - ): - super().__init__(qubits, n_qubits) - self.gate = gate - self.register_buffer("imat", OPERATIONS_DICT["I"]) - self.register_buffer("paulimat", OPERATIONS_DICT[gate]) - self.apply_fn = APPLY_FN_DICT[apply_fn_type] - - def matrices(self, thetas: torch.Tensor) -> torch.Tensor: - theta = thetas.squeeze(0) if thetas.ndim == 2 else thetas - batch_size = len(theta) - return rot_matrices(theta, self.paulimat, self.imat, batch_size) - - def apply(self, matrices: torch.Tensor, state: torch.Tensor) -> torch.Tensor: - batch_size = matrices.size(-1) - controlled_mats = create_controlled_batch_from_operation( - matrices, batch_size, len(self.qubits) - 1 - ) - return self.apply_fn(state, controlled_mats, self.qubits, self.n_qubits, batch_size) - - def forward(self, state: torch.Tensor, thetas: torch.Tensor) -> torch.Tensor: - mats = self.matrices(thetas) - return self.apply(mats, state) - - -class RX(RotationGate): - def __init__(self, qubits: ArrayLike, n_qubits: int, apply_fn_type: ApplyFn = DEFAULT_APPLY_FN): - """ - Represents an X-axis rotation (RX) gate in a quantum circuit. - The RX gate class creates a single-qubit RX gate that performs - a given rotation around the X axis. - - Arguments: - qubits (ArrayLike):The list of qubits the controlled RX gate acts on. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create an RX gate - rx_gate = pyq.RX(qubits=[0], n_qubits=1) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=1) - - #Create a random theta angle - theta = torch.rand(1) - - # Every rotational gate accepts a second parameter that is expected to be a Theta angle. - # Apply the RX gate to the zero state with your random theta as a second parameter. - - result=rx_gate(z_state, theta) - print(result) - - ``` - """ - super().__init__("X", qubits, n_qubits, apply_fn_type) - - -class RY(RotationGate): - def __init__(self, qubits: ArrayLike, n_qubits: int, apply_fn_type: ApplyFn = DEFAULT_APPLY_FN): - """ - Represents a Y-axis rotation (RY) gate in a quantum circuit. - The RY gate class creates a single-qubit RY gate that performs - a given rotation around the Y axis. - - Arguments: - qubits (ArrayLike): The qubit index to apply the RY gate to. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create an RY gate - ry_gate = pyq.RY(qubits=[0], n_qubits=1) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=1) - - # Create a random theta angle - theta = torch.rand(1) - - # Apply the RY gate to the zero state with the random theta angle - result = ry_gate(z_state, theta) - print(result) - ``` - """ - super().__init__("Y", qubits, n_qubits, apply_fn_type) - - -class RZ(RotationGate): - def __init__(self, qubits: ArrayLike, n_qubits: int, apply_fn_type: ApplyFn = DEFAULT_APPLY_FN): - """ - Represents a Z-axis rotation (RZ) gate in a quantum circuit. - The RZ gate class creates a single-qubit RZ gate that performs - a given rotation around the Z axis. - - - Arguments: - qubits (ArrayLike): The qubit index or list of qubit indices to apply the RZ gate to. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create an RZ gate - rz_gate = pyq.RZ(qubits=[0], n_qubits=1) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=1) - - # Create a random theta angle - theta = torch.rand(1) - - # Apply the RZ gate to the zero state with the random theta angle - result = rz_gate(z_state, theta) - print(result) - ``` - """ - super().__init__("Z", qubits, n_qubits, apply_fn_type) - - -class PHASE(RotationGate): - def __init__(self, qubits: ArrayLike, n_qubits: int, apply_fn_type: ApplyFn = DEFAULT_APPLY_FN): - """ - Represents a PHASE rotation gate in a quantum circuit. - - Arguments: - qubits (ArrayLike): The qubit index or list of qubit indices. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create an PHASE gate - gate = pyq.PHASE(qubits=[0], n_qubits=1) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=1) - - # Create a random theta angle - theta = torch.rand(1) - - # Apply the PHASE gate to the zero state with the random theta angle - result = gate(z_state, theta) - print(result) - ``` - """ - super().__init__("I", qubits, n_qubits) - self.apply_fn = APPLY_FN_DICT[apply_fn_type] - - def apply(self, matrices: torch.Tensor, state: torch.Tensor) -> torch.Tensor: - batch_size = matrices.size(-1) - return self.apply_fn(state, matrices, self.qubits, self.n_qubits, batch_size) - - def forward(self, state: torch.Tensor, thetas: torch.Tensor) -> torch.Tensor: - mats = self.matrices(thetas) - return self.apply(mats, state) - - def matrices(self, thetas: torch.Tensor) -> torch.Tensor: - theta = thetas.squeeze(0) if thetas.ndim == 2 else thetas - batch_mat = self.imat.unsqueeze(2).repeat(1, 1, len(theta)) - batch_mat[1, 1, :] = torch.exp(1.0j * thetas).unsqueeze(0).unsqueeze(1) - return batch_mat - - -class CRX(ControlledRotationGate): - def __init__(self, qubits: ArrayLike, n_qubits: int, apply_fn_type: ApplyFn = DEFAULT_APPLY_FN): - """ - Represents a controlled-X-axis rotation (CRX) gate in a quantum circuit. - The CRX gate class creates a controlled RX gate, applying the RX according - to the control qubit state. - - - Arguments: - qubits (ArrayLike):The list of qubits the controlled CRX gate acts on. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - # Create a CRX gate - #The CRX gate is a controlled version of the RX gate. - #It applies an RX rotation to the target qubit based on the state of the control qubit. - #The gate takes two qubits as input: the control qubit and the target qubit. - - crx_gate = pyq.CRX(qubits=[0, 1], n_qubits=2) - - # Create a X gate - x_gate=pyq.X(qubits=[0], n_qubits=2) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=2) - - #Apply an X gate to zero state to change its state from 0 to 1 - activation_state=x_gate(z_state) - - # Create a random theta angle - theta = torch.rand(1) - - # Apply the CRX gate to the activation state with the random theta angle - result = crx_gate(activation_state, theta) - print(result) - - ``` - """ - super().__init__("X", qubits, n_qubits, apply_fn_type) - - -class CRY(ControlledRotationGate): - def __init__(self, qubits: ArrayLike, n_qubits: int, apply_fn_type: ApplyFn = DEFAULT_APPLY_FN): - """ - Represents a controlled-Y-axis rotation (CRY) gate in a quantum circuit. - The CRY gate class creates a controlled RY gate, applying the RY according - to the control qubit state. - - - Arguments: - qubits (ArrayLike):The list of qubits the controlled CRY gate acts on. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create a CRY gate - # The CRY gate is a controlled version of the RY gate. - # It applies an RY rotation to the target qubit based on the state of the control qubit. - # The gate takes two qubits as input: the control qubit and the target qubit. - cry_gate = pyq.CRY(qubits=[0, 1], n_qubits=2) - - # Create a Y gate - y_gate = pyq.Y(qubits=[0], n_qubits=2) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=2) - - # Apply a Y gate to the zero state to change its state - activation_state = y_gate(z_state) - - # Create a random theta angle - theta = torch.rand(1) - - # Apply the CRY gate to the activation state with the random theta angle - result = cry_gate(activation_state, theta) - print(result) - ``` - """ - super().__init__("Y", qubits, n_qubits, apply_fn_type) - - -class CRZ(ControlledRotationGate): - def __init__(self, qubits: ArrayLike, n_qubits: int, apply_fn_type: ApplyFn = DEFAULT_APPLY_FN): - """ - Represents a controlled-Z-axis rotation (CRZ) gate in a quantum circuit. - The CRZ gate class creates a controlled RZ gate, applying the RZ according - to the control qubit state. - - Arguments: - qubits (ArrayLike):The list of qubits the controlled CRZ gate acts on. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create a CRZ gate - # The CRZ gate is a controlled version of the RZ gate. - # It applies an RZ rotation to the target qubit based on the state of the control qubit. - # The gate takes two qubits as input: the control qubit and the target qubit. - crz_gate = pyq.CRZ(qubits=[0, 1], n_qubits=2) - - # Create a X gate - x_gate = pyq.X(qubits=[0], n_qubits=2) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=2) - - # Apply a X gate to the zero state to change its state - activation_state = x_gate(z_state) - - # Create a random theta angle - theta = torch.rand(1) - - # Apply the CRZ gate to the activation state with the random theta angle - result = crz_gate(activation_state, theta) - print(result) - ``` - """ - super().__init__("Z", qubits, n_qubits, apply_fn_type) - - -class CPHASE(AbstractGate): - n_params = 1 - - def __init__(self, qubits: ArrayLike, n_qubits: int, apply_fn_type: ApplyFn = DEFAULT_APPLY_FN): - """ - Represents a controlled-phase (CPHASE) gate in a quantum circuit. - The CPhase gate class creates a controlled Phase gate, applying the PhaseGate - according to the control qubit state. - - Arguments: - qubits (ArrayLike): The control and target qubits for the CPHASE gate. - n_qubits (int): The total number of qubits in the circuit. - - """ - - super().__init__(qubits, n_qubits) - - self.register_buffer("imat", torch.eye(2 ** len(qubits), dtype=DEFAULT_MATRIX_DTYPE)) - self.apply_fn = APPLY_FN_DICT[apply_fn_type] - - def matrices(self, thetas: torch.Tensor) -> torch.Tensor: - theta = thetas.squeeze(0) if thetas.ndim == 2 else thetas - batch_size = len(theta) - mat = self.imat.repeat((batch_size, 1, 1)) - mat = torch.permute(mat, (1, 2, 0)) - phase_rotation_angles = torch.exp(torch.tensor(1j) * theta).unsqueeze(0).unsqueeze(1) - mat[-1, -1, :] = phase_rotation_angles - return mat - - def apply(self, matrices: torch.Tensor, state: torch.Tensor) -> torch.Tensor: - batch_size = matrices.size(-1) - return self.apply_fn(state, matrices, self.qubits, self.n_qubits, batch_size) - - def forward(self, state: torch.Tensor, thetas: torch.Tensor) -> torch.Tensor: - mats = self.matrices(thetas) - return self.apply(mats, state) diff --git a/pyqtorch/modules/primitive.py b/pyqtorch/modules/primitive.py deleted file mode 100644 index f79820e9..00000000 --- a/pyqtorch/modules/primitive.py +++ /dev/null @@ -1,493 +0,0 @@ -from __future__ import annotations - -import math - -import torch -from numpy.typing import ArrayLike - -from pyqtorch.core.operation import _apply_gate, create_controlled_matrix_from_operation -from pyqtorch.matrices import OPERATIONS_DICT -from pyqtorch.modules.abstract import AbstractGate - - -class PrimitiveGate(AbstractGate): - def __init__(self, gate: str, qubits: ArrayLike, n_qubits: int): - super().__init__(qubits, n_qubits) - self.gate = gate - self.register_buffer("matrix", OPERATIONS_DICT[gate]) - - def matrices(self, _: torch.Tensor) -> torch.Tensor: - return self.matrix - - def apply(self, matrix: torch.Tensor, state: torch.Tensor) -> torch.Tensor: - return _apply_gate(state, matrix, self.qubits, self.n_qubits) - - def forward(self, state: torch.Tensor, _: torch.Tensor = None) -> torch.Tensor: - return self.apply(self.matrix, state) - - -class X(PrimitiveGate): - def __init__(self, qubits: ArrayLike, n_qubits: int): - """ - Represents an X gate (Pauli-X gate) in a quantum circuit. - The X gate class creates a X gate that performs a PI rotation around the X axis - - Arguments: - qubits (ArrayLike): The qubits to which the X gate is applied. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create an X gate - x_gate = pyq.X(qubits=[0], n_qubits=1) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=1) - - # Apply the X gate to the zero state - result = x_gate(z_state) - print(result) - ``` - """ - super().__init__("X", qubits, n_qubits) - - -class Y(PrimitiveGate): - def __init__(self, qubits: ArrayLike, n_qubits: int): - """ - Represents a Y gate (Pauli-Y gate) in a quantum circuit. - The Y gate class creates a Y gate that performs a PI rotation around the Y axis. - - - Arguments: - qubits (ArrayLike): The qubits to which the Y gate is applied. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create a Y gate - y_gate = pyq.Y(qubits=[0], n_qubits=1) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=1) - - # Apply the Y gate to the zero state - result = y_gate(z_state) - print(result) - ``` - """ - super().__init__("Y", qubits, n_qubits) - - -class Z(PrimitiveGate): - def __init__(self, qubits: ArrayLike, n_qubits: int): - """ - Represents a Z gate (Pauli-Z gate) in a quantum circuit. - The ZGate class creates a Z gate that performs a PI rotation around the Z axis. - - Arguments: - qubits (ArrayLike): The qubits to which the Z gate is applied. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create a Z gate - z_gate = pyq.Z(qubits=[0], n_qubits=1) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=1) - - # Apply the Z gate to the zero state - result = z_gate(z_state) - - print(result) - ``` - """ - super().__init__("Z", qubits, n_qubits) - - -# FIXME: do we really have to apply a matrix here? -# can't we just return the identical state? -class I(PrimitiveGate): # noqa: E742 - def __init__(self, qubits: ArrayLike, n_qubits: int): - """ - Represents an I gate (identity gate) in a quantum circuit. - The I gate class creates a I gate, which has no effect on the state of a qubit. - - Arguments: - qubits (ArrayLike): The qubits to which the I gate is applied. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create an I gate - i_gate = pyq.I(qubits=[0], n_qubits=1) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=1) - - # Apply the I gate to the zero state - result = i_gate(z_state) - print(result) - ``` - """ - super().__init__("I", qubits, n_qubits) - - -class H(PrimitiveGate): - def __init__(self, qubits: ArrayLike, n_qubits: int): - """ - Represents an H gate (Hadamard gate) in a quantum circuit. - The H Gate class creates a H gate. It performs a PI rotation - around the X+Z axis changing the basis from |0⟩,|1⟩ to |+⟩,|-⟩ - and from |+⟩,|-⟩ back to |0⟩,|1⟩ depending on the number of times - the gate is applied - - Arguments: - qubits (ArrayLike): The qubits to which the H gate is applied. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create an H gate - h_gate = pyq.H(qubits=[0], n_qubits=1) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=1) - - # Apply the H gate to the zero state - result = h_gate(z_state) - print(result) # tensor([[0.7071+0.j],[0.7071+0.j]], dtype=torch.complex128) - ``` - """ - super().__init__("H", qubits, n_qubits) - - -class T(PrimitiveGate): - def __init__(self, qubits: ArrayLike, n_qubits: int): - """ - Represents a T gate (PI/4 phase gate) in a quantum circuit. - - Arguments: - qubits (ArrayLike): The qubits to which the T gate is applied. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create a T gate - t_gate = pyq.T(qubits=[0], n_qubits=1) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=1) - - # Apply the T gate to the zero state - result = t_gate(z_state) - print(result) - ``` - """ - super().__init__("T", qubits, n_qubits) - - -class S(PrimitiveGate): - def __init__(self, qubits: ArrayLike, n_qubits: int): - """ - Represents an S gate (PI/2 phase gate) in a quantum circuit. - - Arguments: - qubits (ArrayLike): The qubits to which the S gate is applied. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create an S gate - s_gate = pyq.S(qubits=[0], n_qubits=1) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=1) - - # Apply the S gate to the zero state - result = s_gate(z_state) - print(result) - ``` - """ - super().__init__("S", qubits, n_qubits) - - -class SDagger(PrimitiveGate): - def __init__(self, qubits: ArrayLike, n_qubits: int): - """ - Represents an SDagger gate (-PI/2 phase gate) in a quantum circuit. - - Arguments: - qubits (ArrayLike): The qubits to which the S gate is applied. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create an SDagger gate - sdagger_gate = pyq.SDagger(qubits=[0], n_qubits=1) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=1) - - # Apply the SDagger gate to the zero state - result = sdagger_gate(z_state) - print(result) - ``` - """ - super().__init__("SDAGGER", qubits, n_qubits) - - -class N(PrimitiveGate): - def __init__(self, qubits: ArrayLike, n_qubits: int): - """ - Represents an N gate ((I-Z)/2 projector gate) in a quantum circuit. - - Arguments: - qubits (ArrayLike): The qubits to which the S gate is applied. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create an N gate - n_gate = pyq.N(qubits=[0], n_qubits=1) - - # Create a zero state - n_state = pyq.zero_state(n_qubits=1) - - # Apply the N gate to the zero state - result = n_gate(n_state) - print(result) - ``` - """ - super().__init__("N", qubits, n_qubits) - - -class SWAP(PrimitiveGate): - def __init__(self, qubits: ArrayLike, n_qubits: int): - """ - Represents a SWAP gate in a quantum circuit. - The SwapGate swaps the qubit states of two quantum wires. - - - Arguments: - qubits (ArrayLike): The qubits to which the SWAP gate is applied. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create a SWAP gate - swap_gate = pyq.SWAP(qubits=[0, 1], n_qubits=2) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=2) - - # Apply the SWAP gate to the zero state - result = swap_gate(z_state) - print(result) - ``` - """ - super().__init__("SWAP", qubits, n_qubits) - - -class ControlledOperationGate(AbstractGate): - def __init__(self, gate: str, qubits: ArrayLike, n_qubits: int): - super().__init__(qubits, n_qubits) - self.gate = gate - mat = OPERATIONS_DICT[gate] - self.register_buffer( - "matrix", - create_controlled_matrix_from_operation( - mat, n_control_qubits=len(qubits) - (int)(math.log2(mat.shape[0])) - ), - ) - - def matrices(self, _: torch.Tensor) -> torch.Tensor: - return self.matrix - - def apply(self, matrix: torch.Tensor, state: torch.Tensor) -> torch.Tensor: - return _apply_gate(state, matrix, self.qubits, self.n_qubits) - - def forward(self, state: torch.Tensor, _: torch.Tensor = None) -> torch.Tensor: - return self.apply(self.matrix, state) - - -class CNOT(ControlledOperationGate): - def __init__(self, qubits: ArrayLike, n_qubits: int): - """ - Represents a controlled NOT (CNOT) gate in a quantum circuit. - - Arguments: - qubits (ArrayLike): The control and target qubits for the CNOT gate. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create a CNOT gate - cnot_gate = pyq.CNOT(qubits=[0, 1], n_qubits=2) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=2) - - # Apply the CNOT gate to the zero state - result = cnot_gate(z_state) - print(result) - ``` - """ - super().__init__("X", qubits, n_qubits) - - -CX = CNOT - - -class CY(ControlledOperationGate): - def __init__(self, qubits: ArrayLike, n_qubits: int): - """ - Represents a controlled-Y (CY) gate in a quantum circuit. - The CY Gate class creates a controlled Y gate, applying the Y gate - according to the control qubit state. - - Arguments: - qubits (ArrayLike): The control and target qubits for the CY gate. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create a CY gate - cy_gate = pyq.CY(qubits=[0, 1], n_qubits=2) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=2) - - # Apply the CY gate to the zero state - result = cy_gate(z_state) - print(result) - ``` - """ - super().__init__("Y", qubits, n_qubits) - - -class CZ(ControlledOperationGate): - def __init__(self, qubits: ArrayLike, n_qubits: int): - """ - Represents a controlled-Z (CZ) gate in a quantum circuit. - The CZ gate class creates a controlled Z gate, applying - the Z gate according to the control qubit state. - - - Arguments: - qubits (ArrayLike): The control and target qubits for the CZ gate. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create a CZ gate - cz_gate = pyq.CZ(qubits=[0, 1], n_qubits=2) - - # Create a zero state - z_state = pyq.zero_state(n_qubits=2) - - # Apply the CZ gate to the zero state - result = cz_gate(z_state) - print(result) - ``` - """ - super().__init__("Z", qubits, n_qubits) - - -class CSWAP(ControlledOperationGate): - def __init__(self, qubits: ArrayLike, n_qubits: int): - """ - Represents a controlled-SWAP (CSWAP) gate in a quantum circuit. - The CSWAP gate class creates a controlled SWAP gate, applying - the SWAP gate according to the control qubit state. - - - Arguments: - qubits (ArrayLike): The control and targets qubits for the CSWAP gate. - n_qubits (int): The total number of qubits in the circuit. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create a CSWAP gate - cswap_gate = pyq.CSWAP(qubits=[0, 1, 2], n_qubits=3) - - # Create a zero state - swap_state = pyq.zero_state(n_qubits=3) - - # Apply the CSWAP gate to the zero state - result = cswap_gate(swap_state) - print(result) - """ - super().__init__("SWAP", qubits, n_qubits) - - -class Toffoli(ControlledOperationGate): - def __init__(self, qubits: ArrayLike, n_qubits: int): - """ - Represents a multi qubit controlled toffoli gate in a quantum circuit. - This gate performs a NOT operation only if all the control qubits are in state 1. - Arguments: - qubits (ArrayLike): The first n-1 qubits will be considered as the control - qubits and the last one will be the target qubit of the - Toffoli gate. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - # Create a Toffoli gate with 2 control qubits. - toffoli_gate = pyq.Toffoli(qubits=[0, 1, 2], n_qubits=3) - - # Create a one state - state_1111 = pyq.X(qubits=[0], n_qubits=3)(pyq.X(qubits=[1], n_qubits=3) - (pyq.X(qubits=[2], n_qubits=3)(pyq.zero_state(n_qubits=3))) - - # Apply the Toffoli gate to the zero state - result = toffoli_gate(toffoli_state) - print(result) - """ - super().__init__("X", qubits, n_qubits) diff --git a/pyqtorch/modules/utils.py b/pyqtorch/modules/utils.py deleted file mode 100644 index 7d3676f1..00000000 --- a/pyqtorch/modules/utils.py +++ /dev/null @@ -1,204 +0,0 @@ -from __future__ import annotations - -from functools import reduce -from typing import Tuple - -import torch -from numpy import log2 - -from pyqtorch.core.utils import _apply_gate -from pyqtorch.matrices import DEFAULT_MATRIX_DTYPE -from pyqtorch.modules.abstract import AbstractGate - - -def normalize(wf: torch.Tensor) -> torch.Tensor: - return wf / torch.sqrt((wf.abs() ** 2).sum()) - - -def is_normalized(state: torch.Tensor, atol: float = 1e-15) -> bool: - n_qubits = len(state.size()) - 1 - batch_size = state.size()[-1] - state = state.reshape((2**n_qubits, batch_size)) - sum_probs = (state.abs() ** 2).sum(dim=0) - ones = torch.ones(batch_size) - return torch.allclose(sum_probs, ones, rtol=0.0, atol=atol) # type:ignore[no-any-return] - - -def is_diag(H: torch.Tensor) -> bool: - """ - Returns True if Hamiltonian H is diagonal. - """ - return len(torch.abs(torch.triu(H, diagonal=1)).to_sparse().coalesce().values()) == 0 - - -def is_real(H: torch.Tensor) -> bool: - """ - Returns True if Hamiltonian H is real. - """ - return len(torch.imag(H).to_sparse().coalesce().values()) == 0 - - -def overlap(state: torch.Tensor, other: torch.Tensor) -> torch.Tensor: - n_qubits = len(state.size()) - 1 - batch_size = state.size()[-1] - state = state.reshape((2**n_qubits, batch_size)) - other = other.reshape((2**n_qubits, batch_size)) - res = [] - for i in range(batch_size): - ovrlp = torch.real(torch.sum(torch.conj(state[:, i]) * other[:, i])) - res.append(ovrlp) - return torch.stack(res) - - -def rot_matrices( - theta: torch.Tensor, P: torch.Tensor, I: torch.Tensor, batch_size: int # noqa: E741 -) -> torch.Tensor: - """ - Returns: - torch.Tensor: a batch of gates after applying theta - """ - cos_t = torch.cos(theta / 2).unsqueeze(0).unsqueeze(1) - cos_t = cos_t.repeat((2, 2, 1)) - sin_t = torch.sin(theta / 2).unsqueeze(0).unsqueeze(1) - sin_t = sin_t.repeat((2, 2, 1)) - - batch_imat = I.unsqueeze(2).repeat(1, 1, batch_size) - batch_operation_mat = P.unsqueeze(2).repeat(1, 1, batch_size) - - return cos_t * batch_imat - 1j * sin_t * batch_operation_mat - - -def zero_state( - n_qubits: int, - batch_size: int = 1, - device: str | torch.device = "cpu", - dtype: torch.dtype = DEFAULT_MATRIX_DTYPE, -) -> torch.Tensor: - """ - Generates the zero state for a specified number of qubits. - - Arguments: - n_qubits (int): The number of qubits for which the zero state is to be generated. - batch_size (int): The batch size for the zero state. - device (str): The device on which the zero state tensor is to be allocated eg cpu or gpu. - dtype (DEFAULT_MATRIX_DTYPE): The data type of the zero state tensor. - - Returns: - torch.Tensor: A tensor representing the zero state. - The shape of the tensor is (batch_size, 2^n_qubits), - where 2^n_qubits is the total number of possible states for the given number of qubits. - The data type of the tensor is specified by the dtype parameter. - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - state = pyq.zero_state(n_qubits=2) - print(state) #tensor([[[1.+0.j],[0.+0.j]],[[0.+0.j],[0.+0.j]]], dtype=torch.complex128) - ``` - """ - state = torch.zeros((2**n_qubits, batch_size), dtype=dtype, device=device) - state[0] = 1 - state = state.reshape([2] * n_qubits + [batch_size]) - return state - - -def uniform_state( - n_qubits: int, - batch_size: int = 1, - device: str | torch.device = "cpu", - dtype: torch.dtype = DEFAULT_MATRIX_DTYPE, -) -> torch.Tensor: - """ - Generates the uniform state for a specified number of qubits. - Returns a tensor representing the uniform state. - The shape of the tensor is (2^n_qubits, batch_size), - where 2^n_qubits is the total number of possible states for the given number of qubits. - The data type of the tensor is specified by the dtype parameter. - Each element of the tensor is initialized to 1/sqrt(2^n_qubits), - ensuring that the total probability of the state is equal to 1. - - Arguments: - n_qubits (int): The number of qubits for which the uniform state is to be generated. - batch_size (int): The batch size for the uniform state. - device (str): The device on which the uniform state tensor is to be allocated. - dtype (DEFAULT_MATRIX_DTYPE): The data type of the uniform state tensor. - - Returns: - torch.Tensor: A tensor representing the uniform state. - - - Examples: - ```python exec="on" source="above" result="json" - import torch - import pyqtorch.modules as pyq - - state = pyq.uniform_state(n_qubits=2) - print(state) - #tensor([[[0.5000+0.j],[0.5000+0.j]],[[0.5000+0.j],[0.5000+0.j]]], dtype=torch.complex128) - ``` - """ - state = torch.ones((2**n_qubits, batch_size), dtype=dtype, device=device) - state = state / torch.sqrt(torch.tensor(2**n_qubits)) - state = state.reshape([2] * n_qubits + [batch_size]) - return state - - -def random_state( - n_qubits: int, - batch_size: int = 1, - device: str | torch.device = "cpu", - dtype: torch.dtype = DEFAULT_MATRIX_DTYPE, -) -> torch.Tensor: - def _rand(n_qubits: int) -> torch.Tensor: - N = 2**n_qubits - x = -torch.log(torch.rand(N)) - sumx = torch.sum(x) - phases = torch.rand(N) * 2.0 * torch.pi - return normalize( - (torch.sqrt(x / sumx) * torch.exp(1j * phases)).reshape(N, 1).type(dtype).to(device) - ) - - _state = torch.concat(tuple(_rand(n_qubits) for _ in range(batch_size)), dim=1) - return _state.reshape([2] * n_qubits + [batch_size]) - - -def flatten_wf(wf: torch.Tensor) -> torch.Tensor: - return torch.flatten(wf, start_dim=0, end_dim=-2).t() - - -def invert_endianness(wf: torch.Tensor) -> torch.Tensor: - """ - Inverts the endianness of a wave function. - - Args: - wf (Tensor): the target wf as a torch Tensor of shape batch_size X 2**n_qubits - - Returns: - The inverted wave function. - """ - try: - wf = flatten_wf(wf) - except RuntimeError: - wf = wf - n_qubits = int(log2(wf.shape[1])) - ls = list(range(2**n_qubits)) - permute_ind = torch.tensor([int(f"{num:0{n_qubits}b}"[::-1], 2) for num in ls]) - return wf[:, permute_ind] - - -def _apply_parallel( - state: torch.Tensor, - thetas: torch.Tensor, - gates: Tuple[AbstractGate] | list[AbstractGate], - n_qubits: int, -) -> torch.Tensor: - qubits_list: list[Tuple] = [g.qubits for g in gates] - mats = [g.matrices(thetas) for g in gates] - - return reduce( - lambda state, inputs: _apply_gate(state, *inputs, N_qubits=n_qubits), # type: ignore - zip(mats, qubits_list), - state, - ) diff --git a/pyqtorch/parametric.py b/pyqtorch/parametric.py new file mode 100644 index 00000000..44d357d8 --- /dev/null +++ b/pyqtorch/parametric.py @@ -0,0 +1,251 @@ +from __future__ import annotations + +from math import log2 +from typing import Tuple + +import torch + +from pyqtorch.matrices import ( + DEFAULT_MATRIX_DTYPE, + OPERATIONS_DICT, + _controlled, + _jacobian, + _unitary, +) +from pyqtorch.primitive import Primitive +from pyqtorch.utils import Operator + + +class Parametric(Primitive): + n_params = 1 + + def __init__( + self, + generator_name: str, + target: int, + param_name: str = "", + ): + super().__init__(OPERATIONS_DICT[generator_name], target) + self.register_buffer("identity", OPERATIONS_DICT["I"]) + self.param_name = param_name + + def parse_values(values: dict[str, torch.Tensor] | torch.Tensor = {}) -> torch.Tensor: + return Parametric._expand_values(values[self.param_name]) + + def parse_tensor(values: dict[str, torch.Tensor] | torch.Tensor = {}) -> torch.Tensor: + return Parametric._expand_values(values) + + self.parse_values = parse_tensor if param_name == "" else parse_values + + @staticmethod + def _expand_values(values: torch.Tensor) -> torch.Tensor: + return values.unsqueeze(0) if len(values.size()) == 0 else values + + def unitary(self, values: dict[str, torch.Tensor] | torch.Tensor = {}) -> Operator: + thetas = self.parse_values(values) + batch_size = len(thetas) + return _unitary(thetas, self.pauli, self.identity, batch_size) + + def jacobian(self, values: dict[str, torch.Tensor] | torch.Tensor = {}) -> Operator: + thetas = self.parse_values(values) + batch_size = len(thetas) + return _jacobian(thetas, self.pauli, self.identity, batch_size) + + +class RX(Parametric): + def __init__( + self, + target: int, + param_name: str = "", + ): + super().__init__("X", target, param_name) + + +class RY(Parametric): + def __init__( + self, + target: int, + param_name: str = "", + ): + super().__init__("Y", target, param_name) + + +class RZ(Parametric): + def __init__(self, target: int, param_name: str = ""): + super().__init__("Z", target, param_name) + + +class PHASE(Parametric): + def __init__(self, target: int, param_name: str = ""): + super().__init__("I", target, param_name) + + def unitary(self, values: dict[str, torch.Tensor] = {}) -> Operator: + thetas = self.parse_values(values) + batch_size = len(thetas) + batch_mat = self.identity.unsqueeze(2).repeat(1, 1, batch_size) + batch_mat[1, 1, :] = torch.exp(1.0j * thetas).unsqueeze(0).unsqueeze(1) + return batch_mat + + def jacobian(self, values: dict[str, torch.Tensor] = {}) -> Operator: + thetas = self.parse_values(values) + batch_mat = ( + torch.zeros((2, 2), dtype=torch.complex128).unsqueeze(2).repeat(1, 1, len(thetas)) + ) + batch_mat[1, 1, :] = 1j * torch.exp(1j * thetas).unsqueeze(0).unsqueeze(1) + return batch_mat + + +class ControlledRotationGate(Parametric): + n_params = 1 + + def __init__( + self, + gate: str, + control: int | Tuple[int, ...], + target: int, + param_name: str = "", + ): + self.control = control if isinstance(control, tuple) else (control,) + super().__init__(gate, target, param_name) + self.qubit_support = self.control + (self.target,) + self.n_qubits = max(list(self.qubit_support)) + + def unitary(self, values: dict[str, torch.Tensor] = {}) -> Operator: + thetas = self.parse_values(values) + batch_size = len(thetas) + mat = _unitary(thetas, self.pauli, self.identity, batch_size) + return _controlled(mat, batch_size, len(self.control) - (int)(log2(mat.shape[0])) + 1) + + def jacobian(self, values: dict[str, torch.Tensor] = {}) -> Operator: + thetas = self.parse_values(values) + batch_size = len(thetas) + n_control = len(self.control) + jU = _jacobian(thetas, self.pauli, self.identity, batch_size) + n_dim = 2 ** (n_control + 1) + jC = ( + torch.zeros((n_dim, n_dim), dtype=torch.complex128) + .unsqueeze(2) + .repeat(1, 1, batch_size) + ) + unitary_idx = 2 ** (n_control + 1) - 2 + jC[unitary_idx:, unitary_idx:, :] = jU + return jC + + +class CRX(ControlledRotationGate): + def __init__( + self, + control: int | Tuple[int], + target: int, + param_name: str = "", + ): + super().__init__("X", control, target, param_name) + + +class CRY(ControlledRotationGate): + def __init__( + self, + control: int | Tuple[int], + target: int, + param_name: str = "", + ): + super().__init__("Y", control, target, param_name) + + +class CRZ(ControlledRotationGate): + def __init__( + self, + control: Tuple[int], + target: int, + param_name: str = "", + ): + super().__init__("Z", control, target, param_name) + + +class CPHASE(ControlledRotationGate): + n_params = 1 + + def __init__( + self, + control: int | Tuple[int], + target: int, + param_name: str = "", + ): + super().__init__("S", control, target, param_name) + self.phase = PHASE(target, param_name) + + def unitary(self, values: dict[str, torch.Tensor] = {}) -> Operator: + thetas = self.parse_values(values) + batch_size = len(thetas) + return _controlled(self.phase.unitary(values), batch_size, len(self.control)) + + def jacobian(self, values: dict[str, torch.Tensor] = {}) -> Operator: + thetas = self.parse_values(values) + batch_size = len(thetas) + n_control = len(self.control) + jU = self.phase.jacobian(values) + n_dim = 2 ** (n_control + 1) + jC = ( + torch.zeros((n_dim, n_dim), dtype=torch.complex128) + .unsqueeze(2) + .repeat(1, 1, batch_size) + ) + unitary_idx = 2 ** (n_control + 1) - 2 + jC[unitary_idx:, unitary_idx:, :] = jU + return jC + + +class U(Parametric): + n_params = 3 + + def __init__(self, target: int, phi: str, theta: str, omega: str): + self.phi = phi + self.theta = theta + self.omega = omega + super().__init__("X", target, param_name="") + + self.register_buffer( + "a", torch.tensor([[1, 0], [0, 0]], dtype=DEFAULT_MATRIX_DTYPE).unsqueeze(2) + ) + self.register_buffer( + "b", torch.tensor([[0, 1], [0, 0]], dtype=DEFAULT_MATRIX_DTYPE).unsqueeze(2) + ) + self.register_buffer( + "c", torch.tensor([[0, 0], [1, 0]], dtype=DEFAULT_MATRIX_DTYPE).unsqueeze(2) + ) + self.register_buffer( + "d", torch.tensor([[0, 0], [0, 1]], dtype=DEFAULT_MATRIX_DTYPE).unsqueeze(2) + ) + + def unitary(self, values: dict[str, torch.Tensor] = {}) -> Operator: + phi, theta, omega = list( + map( + lambda t: t.unsqueeze(0) if len(t.size()) == 0 else t, + [values[self.phi], values[self.theta], values[self.omega]], + ) + ) + batch_size = len(theta) + + t_plus = torch.exp(-1j * (phi + omega) / 2) + t_minus = torch.exp(-1j * (phi - omega) / 2) + sin_t = torch.sin(theta / 2).unsqueeze(0).unsqueeze(1).repeat((2, 2, 1)) + cos_t = torch.cos(theta / 2).unsqueeze(0).unsqueeze(1).repeat((2, 2, 1)) + + a = self.a.repeat(1, 1, batch_size) * cos_t * t_plus + b = self.b.repeat(1, 1, batch_size) * sin_t * torch.conj(t_minus) + c = self.c.repeat(1, 1, batch_size) * sin_t * t_minus + d = self.d.repeat(1, 1, batch_size) * cos_t * torch.conj(t_plus) + return a - b + c + d + + def jacobian(self, values: dict[str, torch.Tensor] = {}) -> Operator: + raise NotImplementedError + + def digital_decomposition(self) -> list[Parametric]: + return [ + RZ(self.qubit_support[0], self.phi), + RY(self.qubit_support[0], self.theta), + RZ(self.qubit_support[0], self.omega), + ] + + def jacobian_decomposed(self, values: dict[str, torch.Tensor] = {}) -> list[Operator]: + return [op.jacobian(values) for op in self.digital_decomposition()] diff --git a/pyqtorch/primitive.py b/pyqtorch/primitive.py new file mode 100644 index 00000000..0530340d --- /dev/null +++ b/pyqtorch/primitive.py @@ -0,0 +1,135 @@ +from __future__ import annotations + +from math import log2 +from typing import Tuple + +import torch + +from pyqtorch.abstract import AbstractOperator +from pyqtorch.apply import apply_operator +from pyqtorch.matrices import OPERATIONS_DICT, _controlled, _dagger +from pyqtorch.utils import Operator, State + + +class Primitive(AbstractOperator): + def __init__(self, pauli: torch.Tensor, target: int): + super().__init__(target) + self.register_buffer("pauli", pauli) + self._param_type = None + + @property + def param_type(self) -> None: + return self._param_type + + def unitary(self, values: dict[str, torch.Tensor] | torch.Tensor = {}) -> Operator: + return self.pauli.unsqueeze(2) + + def forward(self, state: State, values: dict[str, torch.Tensor] | torch.Tensor = {}) -> State: + return apply_operator( + state, self.unitary(values), self.qubit_support, len(state.size()) - 1 + ) + + def dagger(self, values: dict[str, torch.Tensor] | torch.Tensor = {}) -> Operator: + return _dagger(self.unitary(values)) + + +class X(Primitive): + def __init__(self, target: int): + super().__init__(OPERATIONS_DICT["X"], target) + + +class Y(Primitive): + def __init__(self, target: int): + super().__init__(OPERATIONS_DICT["Y"], target) + + +class Z(Primitive): + def __init__(self, target: int): + super().__init__(OPERATIONS_DICT["Z"], target) + + +class I(Primitive): # noqa: E742 + def __init__(self, target: int): + super().__init__(OPERATIONS_DICT["I"], target) + + def forward(self, state: State, values: dict[str, torch.Tensor] = None) -> State: + return state + + +class H(Primitive): + def __init__(self, target: int): + super().__init__(OPERATIONS_DICT["H"], target) + + +class T(Primitive): + def __init__(self, target: int): + super().__init__(OPERATIONS_DICT["T"], target) + + +class S(Primitive): + def __init__(self, target: int): + super().__init__(OPERATIONS_DICT["S"], target) + + +class SDagger(Primitive): + def __init__(self, target: int): + super().__init__(OPERATIONS_DICT["SDAGGER"], target) + + +class N(Primitive): + def __init__(self, target: int): + super().__init__(OPERATIONS_DICT["N"], target) + + +class SWAP(Primitive): + def __init__(self, control: int, target: int): + super().__init__(OPERATIONS_DICT["SWAP"], target) + self.control = (control,) if isinstance(control, int) else control + self.qubit_support = self.control + (target,) + self.n_qubits = max(self.qubit_support) + + +class CSWAP(Primitive): + def __init__(self, control: int | Tuple[int, ...], target: int): + super().__init__(OPERATIONS_DICT["CSWAP"], target) + self.control = (control,) if isinstance(control, int) else control + self.target = target + self.qubit_support = self.control + (target,) + self.n_qubits = max(self.qubit_support) + + +class ControlledOperationGate(Primitive): + def __init__(self, gate: str, control: int | Tuple[int, ...], target: int): + self.control = (control,) if isinstance(control, int) else control + mat = OPERATIONS_DICT[gate] + mat = _controlled( + unitary=mat.unsqueeze(2), + batch_size=1, + n_control_qubits=len(self.control) - (int)(log2(mat.shape[0])) + 1, + ).squeeze(2) + super().__init__(mat, target) + self.qubit_support = self.control + (target,) + self.n_qubits = max(self.qubit_support) + + +class CNOT(ControlledOperationGate): + def __init__(self, control: int | Tuple[int, ...], target: int): + super().__init__("X", control, target) + + +CX = CNOT + + +class CY(ControlledOperationGate): + def __init__(self, control: int | Tuple[int, ...], target: int): + super().__init__("Y", control, target) + + +class CZ(ControlledOperationGate): + def __init__(self, control: int | Tuple[int, ...], target: int): + super().__init__("Z", control, target) + + +class Toffoli(ControlledOperationGate): + def __init__(self, control: int | Tuple[int, ...], target: int): + super().__init__("X", control, target) diff --git a/pyqtorch/utils.py b/pyqtorch/utils.py new file mode 100644 index 00000000..d7da7c7c --- /dev/null +++ b/pyqtorch/utils.py @@ -0,0 +1,114 @@ +from __future__ import annotations + +from enum import Enum +from typing import Sequence + +import torch + +from pyqtorch.matrices import DEFAULT_MATRIX_DTYPE + +State = torch.Tensor +Operator = torch.Tensor + + +def overlap(bra: torch.Tensor, ket: torch.Tensor) -> torch.Tensor: + n_qubits = len(bra.size()) - 1 + bra = bra.reshape((2**n_qubits, bra.size(-1))) + ket = ket.reshape((2**n_qubits, ket.size(-1))) + return torch.einsum("ib,ib->b", bra.conj(), ket).real + + +class StrEnum(str, Enum): + def __str__(self) -> str: + """Used when dumping enum fields in a schema.""" + ret: str = self.value + return ret + + @classmethod + def list(cls) -> list[str]: + return list(map(lambda c: c.value, cls)) # type: ignore + + +class DiffMode(StrEnum): + """ + Which Differentiation engine to use. + + Options: Automatic Differentiation - Using torch.autograd. + Adjoint Differentiation - An implementation of "Efficient calculation of gradients + in classical simulations of variational quantum algorithms", + Jones & Gacon, 2020 + """ + + AD = "ad" + ADJOINT = "adjoint" + + +def is_normalized(state: torch.Tensor, atol: float = 1e-14) -> bool: + n_qubits = len(state.size()) - 1 + batch_size = state.size()[-1] + state = state.reshape((2**n_qubits, batch_size)) + sum_probs = (state.abs() ** 2).sum(dim=0) + ones = torch.ones(batch_size, dtype=torch.double) + return torch.allclose(sum_probs, ones, rtol=0.0, atol=atol) # type: ignore[no-any-return] + + +def is_diag(H: torch.Tensor) -> bool: + """ + Returns True if Hamiltonian H is diagonal. + """ + return len(torch.abs(torch.triu(H, diagonal=1)).to_sparse().coalesce().values()) == 0 + + +def product_state( + bitstring: str, batch_size: int = 1, device: str | torch.device = "cpu" +) -> torch.Tensor: + state = torch.zeros((2 ** len(bitstring), batch_size), dtype=DEFAULT_MATRIX_DTYPE) + state[int(bitstring, 2)] = torch.tensor(1.0 + 0j, dtype=DEFAULT_MATRIX_DTYPE) + return state.reshape([2] * len(bitstring) + [batch_size]).to(device=device) + + +def zero_state( + n_qubits: int, + batch_size: int = 1, + device: str | torch.device = "cpu", + dtype: torch.dtype = DEFAULT_MATRIX_DTYPE, +) -> torch.Tensor: + return product_state("0" * n_qubits, batch_size, device) + + +def uniform_state( + n_qubits: int, + batch_size: int = 1, + device: str | torch.device = "cpu", + dtype: torch.dtype = DEFAULT_MATRIX_DTYPE, +) -> torch.Tensor: + state = torch.ones((2**n_qubits, batch_size), dtype=dtype, device=device) + state = state / torch.sqrt(torch.tensor(2**n_qubits)) + state = state.reshape([2] * n_qubits + [batch_size]) + return state.to(device=device) + + +def random_state( + n_qubits: int, + batch_size: int = 1, + device: str | torch.device = "cpu", + dtype: torch.dtype = DEFAULT_MATRIX_DTYPE, +) -> torch.Tensor: + def _normalize(wf: torch.Tensor) -> torch.Tensor: + return wf / torch.sqrt((wf.abs() ** 2).sum()) + + def _rand(n_qubits: int) -> torch.Tensor: + N = 2**n_qubits + x = -torch.log(torch.rand(N)) + sumx = torch.sum(x) + phases = torch.rand(N) * 2.0 * torch.pi + return _normalize( + (torch.sqrt(x / sumx) * torch.exp(1j * phases)).reshape(N, 1).type(dtype).to(device) + ) + + _state = torch.concat(tuple(_rand(n_qubits) for _ in range(batch_size)), dim=1) + return _state.reshape([2] * n_qubits + [batch_size]).to(device=device) + + +def param_dict(keys: Sequence[str], values: Sequence[torch.Tensor]) -> dict[str, torch.Tensor]: + return {key: val for key, val in zip(keys, values)} diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index d42ae87e..00000000 --- a/tests/conftest.py +++ /dev/null @@ -1,100 +0,0 @@ -from __future__ import annotations - -import random - -import numpy as np -import pytest -import torch -import torch.nn as nn -from torch.nn import Module, ModuleList - -from pyqtorch import QuantumCircuit, batchedRY, measurement -from pyqtorch.core.measurement import total_magnetization -from pyqtorch.core.operation import CNOT, RX, RY, RZ, H, X, Y, Z - -random.seed(42) -np.random.seed(42) -torch.manual_seed(42) -torch.use_deterministic_algorithms(not torch.cuda.is_available()) - - -class TestFM(QuantumCircuit): - def __init__(self, n_qubits: int = 4): - super().__init__(n_qubits) - self.qubits = range(n_qubits) - - def forward(self, state: torch.Tensor, x: torch.Tensor) -> torch.Tensor: - for i in self.qubits: - state = RY(x, state, [i], self.n_qubits) - return state - - -class TestBatchedFM(QuantumCircuit): - def __init__(self, n_qubits: int = 4): - super().__init__(n_qubits) - self.qubits = range(n_qubits) - - def forward(self, state: torch.Tensor, x: torch.Tensor) -> torch.Tensor: - for i in self.qubits: - state = batchedRY(x[:, 0], state, [i], self.n_qubits) - return state - - -class TestNetwork(Module): - def __init__(self, network: torch.nn.Module, n_qubits: int = 4) -> None: - super().__init__() - self.n_qubits = n_qubits - self.network = ModuleList(network) - self.state = QuantumCircuit(n_qubits) - self.operator = measurement.total_magnetization - - def forward(self, nx: torch.Tensor) -> torch.Tensor: - batch_size = len(nx) - state = self.state.init_state(batch_size=batch_size, device=nx.device) - for layer in self.network: - state = layer(state, nx) - return self.operator(state, self.n_qubits, batch_size) - - -class TestCircuit(QuantumCircuit): - def __init__(self, n_qubits: int): - super().__init__(n_qubits) - self.theta = nn.Parameter(torch.empty((self.n_qubits,))) - - def forward(self) -> torch.Tensor: - # initial state - state = self.init_state() - - # single qubit non-parametric gates - for i in range(self.n_qubits): - state = H(state, [i], self.n_qubits) - - for i in range(self.n_qubits): - state = X(state, [i], self.n_qubits) - - for i in range(self.n_qubits): - state = Y(state, [i], self.n_qubits) - - for i in range(self.n_qubits): - state = Z(state, [i], self.n_qubits) - - # single-qubit rotation parametric gates - for i, t in enumerate(self.theta): - state = RZ(t, state, [i], self.n_qubits) - - for i, t in enumerate(self.theta): - state = RY(t, state, [i], self.n_qubits) - - for i, t in enumerate(self.theta): - state = RX(t, state, [i], self.n_qubits) - - # two-qubits gates - state = CNOT(state, [0, 1], self.n_qubits) - state = CNOT(state, [2, 3], self.n_qubits) - - return total_magnetization(state, self.n_qubits, 1) - - -@pytest.fixture -def test_circuit() -> QuantumCircuit: - return TestCircuit(4) diff --git a/tests/test_module_hamevo.py b/tests/test_analog.py similarity index 56% rename from tests/test_module_hamevo.py rename to tests/test_analog.py index e1f4210c..3cf464b4 100644 --- a/tests/test_module_hamevo.py +++ b/tests/test_analog.py @@ -1,20 +1,11 @@ from __future__ import annotations -import random from math import isclose -from typing import Callable -import numpy as np import pytest import torch -import pyqtorch.modules as pyq - -random.seed(0) -np.random.seed(0) -torch.manual_seed(0) -torch.use_deterministic_algorithms(not torch.cuda.is_available()) - +import pyqtorch as pyq pi = torch.tensor(torch.pi, dtype=torch.cdouble) @@ -62,15 +53,11 @@ def Hamiltonian_diag(n_qubits: int = 2, batch_size: int = 1) -> torch.Tensor: return H_batch -@pytest.mark.parametrize( - "ham_evo", - [pyq.HamiltonianEvolution], -) -def test_ham_modules_single(ham_evo: torch.nn.Module) -> None: +def test_hamevo_single() -> None: n_qubits = 4 H = Hamiltonian(1) t_evo = torch.tensor([torch.pi / 4], dtype=torch.cdouble) - hamevo = ham_evo(range(n_qubits), n_qubits) + hamevo = pyq.HamiltonianEvolution(tuple([i for i in range(n_qubits)]), n_qubits) psi = pyq.uniform_state(n_qubits) psi_star = hamevo(H, t_evo, psi) result = overlap(psi_star, psi) @@ -78,17 +65,12 @@ def test_ham_modules_single(ham_evo: torch.nn.Module) -> None: assert isclose(result, 0.5) -@pytest.mark.parametrize( - "ham_evo", - [pyq.HamiltonianEvolution], -) -def test_hamiltonianevolution_batch(ham_evo: torch.nn.Module) -> None: +def test_hamevo_batch() -> None: n_qubits = 4 batch_size = 2 H = Hamiltonian(batch_size) t_evo = torch.tensor([torch.pi / 4], dtype=torch.cdouble) - - hamevo = ham_evo(range(n_qubits), n_qubits) + hamevo = pyq.HamiltonianEvolution(tuple([i for i in range(n_qubits)]), n_qubits) psi = pyq.uniform_state(n_qubits, batch_size) psi_star = hamevo(H, t_evo, psi) result = overlap(psi_star, psi) @@ -96,78 +78,6 @@ def test_hamiltonianevolution_batch(ham_evo: torch.nn.Module) -> None: assert map(isclose, zip(result, [0.5, 0.5])) # type: ignore [arg-type] -@pytest.mark.parametrize( - "ham_evo", - [pyq.HamEvo, pyq.HamEvoEig, pyq.HamEvoExp], -) -def test_hamevo_modules_single(ham_evo: torch.nn.Module) -> None: - n_qubits = 4 - H = Hamiltonian(1) - t_evo = torch.tensor([torch.pi / 4], dtype=torch.cdouble) - hamevo = ham_evo(H, t_evo, range(n_qubits), n_qubits) - psi = pyq.uniform_state(n_qubits) - psi_star = hamevo.forward(psi) - result = overlap(psi_star, psi) - result = result if isinstance(result, float) else result[0] - assert isclose(result, 0.5) - - -@pytest.mark.parametrize( - "ham_evo", - [pyq.HamEvo, pyq.HamEvoEig, pyq.HamEvoExp], -) -def test_hamevo_modules_batch(ham_evo: torch.nn.Module) -> None: - n_qubits = 4 - batch_size = 2 - H = Hamiltonian(batch_size) - t_evo = torch.tensor([torch.pi / 4], dtype=torch.cdouble) - - hamevo = ham_evo(H, t_evo, range(n_qubits), n_qubits) - psi = pyq.uniform_state(n_qubits, batch_size) - psi_star = hamevo.forward(psi) - result = overlap(psi_star, psi) - - assert map(isclose, zip(result, [0.5, 0.5])) # type: ignore [arg-type] - - -@pytest.mark.parametrize("get_hamiltonians", [Hamiltonian_general, Hamiltonian_diag]) -def test_hamevo_consistency(get_hamiltonians: Callable) -> None: - n_qubits = 4 - batch_size = 5 - - H_batch = get_hamiltonians(n_qubits, batch_size) - - t_evo = torch.tensor([torch.pi / 8], dtype=torch.cdouble) - psi_0 = pyq.uniform_state(batch_size=batch_size, n_qubits=n_qubits) - - hamevo_rk4 = pyq.HamEvo(H_batch, t_evo, range(n_qubits), n_qubits) - psi_rk4 = hamevo_rk4.forward(psi_0) - hamevo_eig = pyq.HamEvoEig(H_batch, t_evo, range(n_qubits), n_qubits) - psi_eig = hamevo_eig.forward(psi_0) - hamevo_exp = pyq.HamEvoExp(H_batch, t_evo, range(n_qubits), n_qubits) - psi_exp = hamevo_exp.forward(psi_0) - - hamiltonian_evolution = pyq.HamiltonianEvolution(range(n_qubits), n_qubits) - psi_ham = hamiltonian_evolution(H_batch, t_evo, psi_0) - - # assert torch.allclose(psi_rk4, psi_eig) - # assert torch.allclose(psi_rk4, psi_eig) - # assert torch.allclose(psi_eig, psi_exp) - tensors = [psi_rk4, psi_eig, psi_exp, psi_ham] - assert all(torch.allclose(tensors[i], tensors[0]) for i in range(1, len(tensors))) - - -@pytest.mark.parametrize( - "ham_evo_type, ham_evo_class", - [ - (pyq.HamEvoType.RK4, pyq.HamEvo), - (pyq.HamEvoType.EIG, pyq.HamEvoEig), - (pyq.HamEvoType.EXP, pyq.HamEvoExp), - ("rk4", pyq.HamEvo), - ("eig", pyq.HamEvoEig), - ("exp", pyq.HamEvoExp), - ], -) @pytest.mark.parametrize( "H, t_evo, target, batch_size", [ @@ -198,8 +108,6 @@ def test_hamevo_consistency(get_hamiltonians: Callable) -> None: ], ) def test_hamiltonianevolution_with_types( - ham_evo_type: pyq.HamEvoType, - ham_evo_class: torch.nn.Module, H: torch.Tensor, t_evo: torch.Tensor, target: torch.Tensor, @@ -212,10 +120,7 @@ def overlap(state1: torch.Tensor, state2: torch.Tensor) -> torch.Tensor: return torch.abs(overlap**2).flatten() n_qubits = 4 - hamevo = pyq.HamiltonianEvolution(range(n_qubits), n_qubits, hamevo_type=ham_evo_type) - ham_evo_instance = hamevo.get_hamevo_instance(H, t_evo) - assert isinstance(ham_evo_instance, ham_evo_class) - + hamevo = pyq.HamiltonianEvolution(tuple([i for i in range(n_qubits)]), n_qubits) psi = pyq.uniform_state(n_qubits) psi_star = hamevo(H, t_evo, psi) result = overlap(psi_star, psi) @@ -234,8 +139,8 @@ def test_hamevo_endianness() -> None: ] ) iszero = torch.tensor([False, True, False, True]) - op = pyq.HamEvoExp(h, t, qubits=[0, 1], n_qubits=2) - st = op(pyq.zero_state(2)).flatten() + op = pyq.HamiltonianEvolution(qubit_support=(0, 1), n_qubits=2) + st = op(h, t, pyq.zero_state(2)).flatten() assert torch.allclose(st[iszero], torch.zeros(1, dtype=torch.cdouble)) h = torch.tensor( @@ -247,6 +152,6 @@ def test_hamevo_endianness() -> None: ] ) iszero = torch.tensor([False, False, True, True]) - op = pyq.HamEvoExp(h, t, qubits=[0, 1], n_qubits=2) - st = op(pyq.zero_state(2)).flatten() + op = pyq.HamiltonianEvolution(qubit_support=(0, 1), n_qubits=2) + st = op(h, t, pyq.zero_state(2)).flatten() assert torch.allclose(st[iszero], torch.zeros(1, dtype=torch.cdouble)) diff --git a/tests/test_batched_operations.py b/tests/test_batched_operations.py deleted file mode 100644 index 3cb976f5..00000000 --- a/tests/test_batched_operations.py +++ /dev/null @@ -1,40 +0,0 @@ -from __future__ import annotations - -import torch - -from pyqtorch import QuantumCircuit -from pyqtorch.core.batched_operation import batchedCPHASE, batchedCRX, batchedCRY, batchedCRZ - -state_0 = torch.tensor([[1, 0]], dtype=torch.cdouble) -state_1 = torch.tensor([[0, 1]], dtype=torch.cdouble) - -state_00 = torch.tensor([[1, 0], [0, 0]], dtype=torch.cdouble).unsqueeze(2) -state_01 = torch.tensor([[0, 1], [0, 0]], dtype=torch.cdouble).unsqueeze(2) -state_10 = torch.tensor([[0, 0], [1, 0]], dtype=torch.cdouble).unsqueeze(2) -state_11 = torch.tensor([[0, 0], [0, 1]], dtype=torch.cdouble).unsqueeze(2) - -pi = torch.tensor(torch.pi, dtype=torch.cdouble) - - -def test_batched_ops() -> None: - n_qubits: int = 2 - batch_size: int = 10 - qc = QuantumCircuit(n_qubits) - - theta_dim = torch.Size([batch_size]) - - theta = torch.randn(theta_dim) - psi = qc.uniform_state(batch_size) - - for op in [batchedCPHASE, batchedCRX, batchedCRY, batchedCRZ, batchedCPHASE]: - res = op(theta, psi, [i for i in range(n_qubits)], n_qubits) - assert not torch.any(torch.isnan(res)) - - -def test_batched_cphase() -> None: - n_qubits: int = 2 - psi = torch.tensor([[0, 0], [0, 1]], dtype=torch.cdouble).unsqueeze(2) - psi_target = torch.tensor([[0, 0], [0, -1]], dtype=torch.cdouble).unsqueeze(2) - angle = pi.unsqueeze(0) - res = batchedCPHASE(angle, psi, [i for i in range(n_qubits)], n_qubits) - assert torch.allclose(res, psi_target, atol=1e-16) diff --git a/tests/test_circuit.py b/tests/test_circuit.py new file mode 100644 index 00000000..54538f77 --- /dev/null +++ b/tests/test_circuit.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +import pytest +import torch + +import pyqtorch as pyq +from pyqtorch.circuit import DiffMode + + +def test_adjoint_diff() -> None: + rx = pyq.RX(0, param_name="theta_0") + cry = pyq.CPHASE(0, 1, param_name="theta_1") + rz = pyq.RZ(2, param_name="theta_2") + cnot = pyq.CNOT(1, 2) + ops = [rx, cry, rz, cnot] + n_qubits = 3 + adjoint_circ = pyq.QuantumCircuit(n_qubits, ops, DiffMode.ADJOINT) + ad_circ = pyq.QuantumCircuit(n_qubits, ops, DiffMode.AD) + obs = pyq.QuantumCircuit(n_qubits, [pyq.Z(0)]) + + theta_0_value = torch.pi / 2 + theta_1_value = torch.pi + theta_2_value = torch.pi / 4 + + state = pyq.zero_state(n_qubits) + + theta_0_ad = torch.tensor([theta_0_value], requires_grad=True) + thetas_0_adjoint = torch.tensor([theta_0_value], requires_grad=True) + + theta_1_ad = torch.tensor([theta_1_value], requires_grad=True) + thetas_1_adjoint = torch.tensor([theta_1_value], requires_grad=True) + + theta_2_ad = torch.tensor([theta_2_value], requires_grad=True) + thetas_2_adjoint = torch.tensor([theta_2_value], requires_grad=True) + + values_ad = {"theta_0": theta_0_ad, "theta_1": theta_1_ad, "theta_2": theta_2_ad} + values_adjoint = { + "theta_0": thetas_0_adjoint, + "theta_1": thetas_1_adjoint, + "theta_2": thetas_2_adjoint, + } + exp_ad = ad_circ.expectation(values_ad, obs, state) + exp_adjoint = adjoint_circ.expectation(values_adjoint, obs, state) + + grad_ad = torch.autograd.grad(exp_ad, tuple(values_ad.values()), torch.ones_like(exp_ad)) + + grad_adjoint = torch.autograd.grad( + exp_adjoint, tuple(values_adjoint.values()), torch.ones_like(exp_adjoint) + ) + + assert len(grad_ad) == len(grad_adjoint) + for i in range(len(grad_ad)): + assert torch.allclose(grad_ad[i], grad_adjoint[i]) + + +@pytest.mark.parametrize("diff_mode", [DiffMode.AD, DiffMode.ADJOINT]) +@pytest.mark.parametrize("batch_size", [1, 5]) +@pytest.mark.parametrize("n_qubits", [3, 4]) +def test_differentiate_circuit(diff_mode: DiffMode, batch_size: int, n_qubits: int) -> None: + ops = [ + pyq.RX(0, "phi"), + pyq.PHASE(0, "theta"), + pyq.CSWAP((0, 1), 2), + pyq.CPHASE(1, 2, "epsilon"), + pyq.CNOT(0, 1), + pyq.Toffoli((2, 1), 0), + ] + circ = pyq.QuantumCircuit(n_qubits, ops, diff_mode=diff_mode) + state = pyq.random_state(n_qubits, batch_size) + phi = torch.rand(batch_size, requires_grad=True) + theta = torch.rand(batch_size, requires_grad=True) + epsilon = torch.rand(batch_size, requires_grad=True) + values = {"phi": phi, "theta": theta, "epsilon": epsilon} + assert circ(state, values).size() == tuple(2 for _ in range(n_qubits)) + (batch_size,) + state = pyq.random_state(n_qubits, batch_size=batch_size) + + def _fwd(phi: torch.Tensor, theta: torch.Tensor, epsilon: torch.Tensor) -> torch.Tensor: + return circ(state, {"phi": phi, "theta": theta, "epsilon": epsilon}) + + assert torch.autograd.gradcheck(_fwd, (phi, theta, epsilon)) diff --git a/tests/test_digital.py b/tests/test_digital.py new file mode 100644 index 00000000..c2610a03 --- /dev/null +++ b/tests/test_digital.py @@ -0,0 +1,214 @@ +from __future__ import annotations + +import random +from math import log2 +from typing import Callable, Tuple + +import pytest +import torch + +import pyqtorch as pyq +from pyqtorch.apply import apply_operator +from pyqtorch.matrices import IMAT, ZMAT +from pyqtorch.parametric import Parametric +from pyqtorch.utils import product_state + +state_000 = product_state("000") +state_001 = product_state("001") +state_100 = product_state("100") +state_101 = product_state("101") +state_110 = product_state("110") +state_111 = product_state("111") +state_0000 = product_state("0000") +state_1110 = product_state("1110") +state_1111 = product_state("1111") + + +def test_identity() -> None: + assert torch.allclose(product_state("0"), pyq.I(0)(product_state("0"), None)) + assert torch.allclose(product_state("1"), pyq.I(1)(product_state("1"))) + + +def test_N() -> None: + null_state = torch.zeros_like(pyq.zero_state(1)) + assert torch.allclose(null_state, pyq.N(0)(product_state("0"), None)) + assert torch.allclose(product_state("1"), pyq.N(0)(product_state("1"), None)) + + +def test_CNOT_state00_controlqubit_0() -> None: + result: torch.Tensor = pyq.CNOT(0, 1)(product_state("00"), None) + assert torch.equal(product_state("00"), result) + + +def test_CNOT_state10_controlqubit_0() -> None: + result: torch.Tensor = pyq.CNOT(0, 1)(product_state("10"), None) + assert torch.equal(product_state("11"), result) + + +def test_CNOT_state11_controlqubit_0() -> None: + result: torch.Tensor = pyq.CNOT(0, 1)(product_state("11"), None) + assert torch.equal(product_state("10"), result) + + +def test_CRY_state10_controlqubit_0() -> None: + result: torch.Tensor = pyq.CRY(0, 1, "theta")( + product_state("10"), {"theta": torch.tensor([torch.pi])} + ) + assert torch.allclose(product_state("11"), result) + + +def test_CRY_state01_controlqubit_0() -> None: + result: torch.Tensor = pyq.CRY(1, 0, "theta")( + product_state("01"), {"theta": torch.tensor([torch.pi])} + ) + assert torch.allclose(product_state("11"), result) + + +def test_CSWAP_state101_controlqubit_0() -> None: + result: torch.Tensor = pyq.CSWAP((0, 1), 2)(product_state("101"), None) + assert torch.allclose(product_state("110"), result) + + +def test_CSWAP_state110_controlqubit_0() -> None: + result: torch.Tensor = pyq.CSWAP((0, 1), 2)(product_state("101"), None) + assert torch.allclose(product_state("110"), result) + + +@pytest.mark.parametrize( + "initial_state,expected_state", + [ + (state_000, state_000), + (state_001, state_001), + (state_100, state_100), + (state_101, state_110), + (state_110, state_101), + ], +) +def test_CSWAP_controlqubits0(initial_state: torch.Tensor, expected_state: torch.Tensor) -> None: + cswap = pyq.CSWAP((0, 1), 2) + assert torch.allclose(cswap(initial_state, None), expected_state) + + +@pytest.mark.parametrize( + "initial_state,expected_state", + [ + (state_000, state_000), + (state_001, state_001), + (state_100, state_100), + (state_101, state_101), + (state_110, state_111), + (state_1110, state_1111), + ], +) +def test_Toffoli_controlqubits0(initial_state: torch.Tensor, expected_state: torch.Tensor) -> None: + n_qubits = int(log2(torch.numel(initial_state))) + qubits = tuple([i for i in range(n_qubits)]) + toffoli = pyq.Toffoli(qubits[:-1], qubits[-1]) + assert torch.allclose(toffoli(initial_state, None), expected_state) + + +@pytest.mark.parametrize( + "initial_state,expects_rotation", + [ + (state_000, False), + (state_001, False), + (state_100, False), + (state_101, False), + (state_110, True), + (state_1110, True), + ], +) +@pytest.mark.parametrize("gate", ["RX", "RY", "RZ", "PHASE"]) +@pytest.mark.parametrize("batch_size", [1, 2]) +def test_multi_controlled_gates( + initial_state: torch.Tensor, expects_rotation: bool, batch_size: int, gate: str +) -> None: + phi = "phi" + rot_gate = getattr(pyq, gate) + controlled_rot_gate = getattr(pyq, "C" + gate) + phi = torch.rand(batch_size) + n_qubits = int(log2(torch.numel(initial_state))) + qubits = tuple([i for i in range(n_qubits)]) + op = controlled_rot_gate(qubits[:-1], qubits[-1], "phi") + out = op(initial_state, {"phi": phi}) + expected_state = ( + rot_gate(qubits[-1], "phi")(initial_state, {"phi": phi}) + if expects_rotation + else initial_state + ) + assert torch.allclose(out, expected_state) + + +@pytest.mark.parametrize("state_fn", [pyq.random_state, pyq.zero_state, pyq.uniform_state]) +def test_parametric_phase_hamevo( + state_fn: Callable, batch_size: int = 1, n_qubits: int = 1 +) -> None: + target = 0 + state = state_fn(n_qubits, batch_size=batch_size) + phi = torch.rand(1, dtype=torch.cdouble) + H = (ZMAT - IMAT) / 2 + hamevo = pyq.HamiltonianEvolution(qubit_support=(target,), n_qubits=n_qubits) + phase = pyq.PHASE(target, "phi") + assert torch.allclose(phase(state, {"phi": phi}), hamevo(H, phi, state)) + + +@pytest.mark.parametrize("state_fn", [pyq.random_state, pyq.zero_state, pyq.uniform_state]) +@pytest.mark.parametrize("batch_size", [1, 2, 4]) +@pytest.mark.parametrize("n_qubits", [1, 2, 4]) +def test_parametrized_phase_gate(state_fn: Callable, batch_size: int, n_qubits: int) -> None: + target: int = torch.randint(low=0, high=n_qubits, size=(1,)).item() + state = state_fn(n_qubits, batch_size=batch_size) + phi = torch.tensor([torch.pi / 2], dtype=torch.cdouble) + phase = pyq.PHASE(target, "phi") + constant_phase = pyq.S(target) + assert torch.allclose(phase(state, {"phi": phi}), constant_phase(state, None)) + + +def test_dagger_single_qubit() -> None: + for cls in [pyq.X, pyq.Y, pyq.Z, pyq.S, pyq.H, pyq.T, pyq.RX, pyq.RY, pyq.RZ, pyq.PHASE]: + n_qubits = torch.randint(low=1, high=4, size=(1,)).item() + target = random.choice([i for i in range(n_qubits)]) + state = pyq.random_state(n_qubits) + for param_name in ["theta", ""]: + if issubclass(cls, Parametric): + op = cls(target, param_name) # type: ignore[arg-type] + else: + op = cls(target) # type: ignore[misc] + values = {param_name: torch.rand(1)} if param_name == "theta" else torch.rand(1) + new_state = apply_operator(state, op.unitary(values), [target]) + daggered_back = apply_operator(new_state, op.dagger(values), [target]) + assert torch.allclose(daggered_back, state) + + +def test_dagger_nqubit() -> None: + for cls in [pyq.SWAP, pyq.CNOT, pyq.CY, pyq.CZ, pyq.CRX, pyq.CRY, pyq.CRZ, pyq.CPHASE]: + qubit_support: Tuple[int, ...] + n_qubits = torch.randint(low=3, high=8, size=(1,)).item() + target = random.choice([i for i in range(n_qubits - 2)]) + state = pyq.random_state(n_qubits) + for param_name in ["theta", ""]: + if isinstance(cls, (pyq.CSWAP, pyq.Toffoli)): + op = cls((target - 2, target - 1), target) + qubit_support = (target + 2, target + 1, target) + elif issubclass(cls, Parametric): + op = cls(target - 1, target, param_name) # type: ignore[arg-type] + qubit_support = (target + 1, target) + else: + op = cls(target - 1, target) # type: ignore[misc] + qubit_support = (target + 1, target) + values = {param_name: torch.rand(1)} if param_name == "theta" else torch.rand(1) + new_state = apply_operator(state, op.unitary(values), qubit_support) + daggered_back = apply_operator(new_state, op.dagger(values), qubit_support) + assert torch.allclose(daggered_back, state) + + +def test_U() -> None: + n_qubits = torch.randint(low=1, high=8, size=(1,)).item() + target = random.choice([i for i in range(n_qubits)]) + params = ["phi", "theta", "omega"] + u = pyq.U(target, *params) + values = {param: torch.rand(1) for param in params} + state = pyq.random_state(n_qubits) + assert torch.allclose( + u(state, values), pyq.QuantumCircuit(n_qubits, u.digital_decomposition())(state, values) + ) diff --git a/tests/test_modules.py b/tests/test_modules.py deleted file mode 100644 index 30a7c185..00000000 --- a/tests/test_modules.py +++ /dev/null @@ -1,354 +0,0 @@ -from __future__ import annotations - -import math -from typing import Callable - -import pytest -import torch -from torch import Tensor - -import pyqtorch.core as func_pyq -import pyqtorch.modules as pyq -from pyqtorch.matrices import OPERATIONS_DICT -from pyqtorch.modules.abstract import AbstractGate - -DEVICE = "cuda" if torch.cuda.is_available() else "cpu" -DTYPE = torch.cdouble - -state_000 = pyq.zero_state(3, device=DEVICE, dtype=DTYPE) -state_001 = pyq.X(qubits=[2], n_qubits=3)(state_000) -state_100 = pyq.X(qubits=[0], n_qubits=3)(state_000) -state_101 = pyq.X(qubits=[2], n_qubits=3)(pyq.X(qubits=[0], n_qubits=3)(state_000)) -state_110 = pyq.X(qubits=[1], n_qubits=3)(pyq.X(qubits=[0], n_qubits=3)(state_000)) -state_111 = pyq.X(qubits=[2], n_qubits=3)( - pyq.X(qubits=[1], n_qubits=3)(pyq.X(qubits=[0], n_qubits=3)(state_000)) -) - -state_0000 = pyq.zero_state(4, device=DEVICE, dtype=DTYPE) -state_1110 = pyq.X(qubits=[0], n_qubits=4)( - pyq.X(qubits=[1], n_qubits=4)(pyq.X(qubits=[2], n_qubits=4)(state_0000)) -) -state_1111 = pyq.X(qubits=[0], n_qubits=4)( - pyq.X(qubits=[1], n_qubits=4)( - pyq.X(qubits=[2], n_qubits=4)(pyq.X(qubits=[3], n_qubits=4)(state_0000)) - ) -) - - -@pytest.mark.parametrize("batch_size", [i for i in range(1, 2, 10)]) -@pytest.mark.parametrize("n_qubits", [i for i in range(1, 6)]) -@pytest.mark.parametrize("gate", ["X", "Y", "Z", "H", "I", "N", "S", "T", "SDagger"]) -def test_constant_gates(batch_size: int, n_qubits: int, gate: str) -> None: - dtype = torch.cdouble - qubits = [torch.randint(low=0, high=n_qubits, size=(1,)).item()] - - state = pyq.random_state(n_qubits, batch_size=batch_size, device=DEVICE, dtype=DTYPE) - Op = getattr(pyq, gate) - FuncOp = getattr(func_pyq.operation, gate) - - func_out = FuncOp(state, qubits, n_qubits) - op = Op(qubits, n_qubits).to(device=DEVICE, dtype=dtype) - mod_out = op(state) - - assert torch.allclose(func_out, mod_out) - - -@pytest.mark.parametrize("batch_size", [i for i in range(1, 2, 10)]) -@pytest.mark.parametrize("n_qubits", [i for i in range(1, 6)]) -@pytest.mark.parametrize("gate", ["RX", "RY", "RZ"]) -def test_parametrized_gates(batch_size: int, n_qubits: int, gate: str) -> None: - qubits = [torch.randint(low=0, high=n_qubits, size=(1,)).item()] - - state = pyq.random_state(n_qubits, batch_size=batch_size, device=DEVICE, dtype=DTYPE) - phi = torch.rand(batch_size, device=DEVICE, dtype=DTYPE) - - Op = getattr(pyq, gate) - FuncOp = getattr(func_pyq.batched_operation, f"batched{gate}") - - func_out = FuncOp(phi, state, qubits, n_qubits) - op = Op(qubits, n_qubits).to(device=DEVICE, dtype=DTYPE) - mod_out = op(state, phi) - - assert torch.allclose(func_out, mod_out) - - -@pytest.mark.parametrize("batch_size", [i for i in range(1, 2, 10)]) -@pytest.mark.parametrize("n_qubits", [i for i in range(2, 6)]) -@pytest.mark.parametrize("gate", ["CRX", "CRY", "CRZ"]) -def test_controlled_parametrized_gates(batch_size: int, n_qubits: int, gate: str) -> None: - qubits = torch.randint(low=0, high=n_qubits, size=(2,)) - - while qubits[0] == qubits[1]: - qubits[1] = torch.randint(low=0, high=n_qubits, size=(1,)) - - qubits = [qubits[0].item(), qubits[1].item()] - - state = pyq.random_state(n_qubits, batch_size=batch_size, device=DEVICE, dtype=DTYPE) - phi = torch.rand(batch_size, device=DEVICE, dtype=DTYPE) - - Op = getattr(pyq, gate) - BatchedOP = getattr(func_pyq.batched_operation, f"batched{gate}") - - func_out = BatchedOP(phi, state, qubits, n_qubits) - op = Op(qubits, n_qubits).to(device=DEVICE, dtype=DTYPE) - mod_out = op(state, phi) - - assert torch.allclose(func_out, mod_out) - - -@pytest.mark.parametrize("batch_size", [i for i in range(1, 2, 10)]) -@pytest.mark.parametrize("n_qubits", [i for i in range(2, 6)]) -def test_circuit(batch_size: int, n_qubits: int) -> None: - ops = [ - pyq.X([0], n_qubits), - pyq.X([1], n_qubits), - pyq.RX([1], n_qubits), - pyq.CNOT([0, 1], n_qubits), - ] - circ = pyq.QuantumCircuit(n_qubits, ops).to(device=DEVICE, dtype=DTYPE) - - state = pyq.random_state(n_qubits, batch_size) - phi = torch.rand(batch_size, device=DEVICE, dtype=DTYPE, requires_grad=True) - - assert circ(state, phi).size() == tuple(2 for _ in range(n_qubits)) + (batch_size,) - - state = pyq.random_state(n_qubits, batch_size=batch_size, device=DEVICE, dtype=DTYPE) - - res = circ(state, phi) - assert not torch.all(torch.isnan(res)) - dres_theta = torch.autograd.grad(res, phi, torch.ones_like(res), create_graph=True)[0] - assert not torch.all(torch.isnan(dres_theta)) - - -@pytest.mark.parametrize("batch_size", [1, 2, 4, 6]) -def test_empty_circuit(batch_size: int) -> None: - n_qubits = 2 - ops: list = [] - circ = pyq.QuantumCircuit(n_qubits, ops).to(device=DEVICE, dtype=DTYPE) - - state = circ.init_state(batch_size) - phi = torch.rand(batch_size, device=DEVICE, dtype=DTYPE, requires_grad=True) - - assert circ(state, phi).size() == (2, 2, batch_size) - - state = pyq.random_state(n_qubits, batch_size=batch_size, device=DEVICE, dtype=DTYPE) - - res = circ(state, phi) - assert not torch.all(torch.isnan(res)) - - -@pytest.mark.parametrize("batch_size", [1, 2, 4, 6]) -def test_U_gate(batch_size: int) -> None: - n_qubits = 1 - u = pyq.U([0], n_qubits) - x = torch.rand(3, batch_size) - state = pyq.random_state(n_qubits, batch_size=batch_size, device=DEVICE, dtype=DTYPE) - assert not torch.all(torch.isnan(u(state, x))) - - -@pytest.mark.parametrize("batch_size", [1, 2, 4, 6]) -def test_N_gate(batch_size: int) -> None: - n_qubits = 1 - n = pyq.N([0], n_qubits) - init_state = pyq.zero_state(n_qubits, batch_size=batch_size, device=DEVICE, dtype=DTYPE) - final_state = pyq.X([0], n_qubits)(init_state) - assert torch.equal(final_state, n(final_state)) - - -@pytest.mark.parametrize( - "a,b,val", - [ - (pyq.X([0], 1), pyq.X([0], 1), True), - (pyq.RY([0], 1), pyq.RY([0], 1), True), - (pyq.RY([0], 1), pyq.RY([0], 1), True), - (pyq.X([0], 1), pyq.Y([0], 1), False), - (pyq.RX([0], 2), pyq.RX([0], 1), False), - (pyq.RX([1], 1), pyq.RX([0], 1), False), - ], -) -def test_gate_equality(a: AbstractGate, b: AbstractGate, val: bool) -> None: - x = a == b - assert x == val - - -def test_circuit_equality() -> None: - c1 = pyq.QuantumCircuit(2, [pyq.RX([0], 2), pyq.Z([1], 2)]) - c2 = pyq.QuantumCircuit(2, [pyq.RX([0], 2), pyq.Z([1], 2)]) - assert c1 == c2 - - c3 = pyq.QuantumCircuit(2, [pyq.RX([1], 2), pyq.Z([1], 2)]) - assert c1 != c3 - - c4 = pyq.QuantumCircuit(2, [pyq.Z([1], 2)]) - assert c3 != c4 - - c5 = pyq.QuantumCircuit(2, [pyq.RX([0], 2)]) - assert c1 != c5 - - -@pytest.mark.parametrize("n_qubits", [1, 2, 3]) -def test_gate_composition(n_qubits: int) -> None: - x = pyq.X(torch.randint(0, n_qubits, (1,)).tolist(), n_qubits) - y = pyq.Y(torch.randint(0, n_qubits, (1,)).tolist(), n_qubits) - circ = x * y - truth = pyq.QuantumCircuit(n_qubits, [x, y]) - assert circ == truth - - rx = pyq.RX(torch.randint(0, n_qubits, (1,)).tolist(), n_qubits) - ry = pyq.RY(torch.randint(0, n_qubits, (1,)).tolist(), n_qubits) - circ = rx * ry - truth = pyq.QuantumCircuit(n_qubits, [rx, ry]) - assert circ == truth - - r1, r2, r3 = 1, n_qubits, max(n_qubits - 1, 1) - z1 = pyq.Z(torch.randint(0, r1, (1,)).tolist(), r1) - y = pyq.Y(torch.randint(0, r2, (1,)).tolist(), r2) - z2 = pyq.Z(torch.randint(0, r3, (1,)).tolist(), r3) - circ = z1 * y * z2 - truth = pyq.QuantumCircuit(max(r1, r2, r3), [z1, y, z2]) - assert circ == truth - - rr1, rr2, rr3 = 1, n_qubits, max(n_qubits - 1, 1) - rz1 = pyq.RZ(torch.randint(0, rr1, (1,)).tolist(), rr1) - ry1 = pyq.RY(torch.randint(0, rr2, (1,)).tolist(), rr2) - rz2 = pyq.RZ(torch.randint(0, rr3, (1,)).tolist(), rr3) - - circ = rz1 * ry1 * rz2 - truth = pyq.QuantumCircuit(max(r1, r2, r3), [rz1, ry1, rz2]) - assert circ == truth - - -@pytest.mark.parametrize("n_qubits", [1, 2, 3]) -def test_circuit_composition(n_qubits: int) -> None: - r = torch.randint(1, n_qubits + 1, (1,)).tolist() - rx = pyq.RX(r, n_qubits) - - circ = rx * pyq.QuantumCircuit(1, [pyq.X([0], 1), pyq.Y([0], 1)]) - truth = pyq.QuantumCircuit(n_qubits, [rx, pyq.X([0], 1), pyq.Y([0], 1)]) - assert circ == truth - - circ = pyq.QuantumCircuit(1, [pyq.X([0], 1), pyq.Y([0], 1)]) * rx - truth = pyq.QuantumCircuit(n_qubits, [pyq.X([0], 1), pyq.Y([0], 1), rx]) - assert circ == truth - - circ = pyq.QuantumCircuit(1, [pyq.X([0], 1), pyq.Y([0], 1)]) * pyq.QuantumCircuit( - n_qubits, [rx, pyq.RY([0], 1)] - ) - truth = pyq.QuantumCircuit(n_qubits, [pyq.X([0], 1), pyq.Y([0], 1), rx, pyq.RY([0], 1)]) - assert circ == truth - - -@pytest.mark.parametrize( - "initial_state,expected_state", - [ - (state_000, state_000), - (state_001, state_001), - (state_100, state_100), - (state_101, state_110), - (state_110, state_101), - ], -) -def test_CSWAP_controlqubits0(initial_state: Tensor, expected_state: Tensor) -> None: - n_qubits = 3 - cswap = pyq.CSWAP([0, 1, 2], n_qubits) - assert torch.allclose(cswap(initial_state), expected_state) - - -@pytest.mark.parametrize( - "initial_state,expected_state", - [ - (state_000, state_000), - (state_001, state_001), - (state_100, state_100), - (state_101, state_101), - (state_110, state_111), - (state_1110, state_1111), - ], -) -def test_Toffoli_controlqubits0(initial_state: Tensor, expected_state: Tensor) -> None: - n_qubits = int(math.log2(torch.numel(initial_state))) - toffoli = pyq.Toffoli(range(n_qubits), n_qubits) - assert torch.allclose(toffoli(initial_state), expected_state) - - -@pytest.mark.parametrize( - "initial_state,expects_rotation", - [ - (state_000, False), - (state_001, False), - (state_100, False), - (state_101, False), - (state_110, True), - (state_1110, True), - ], -) -@pytest.mark.parametrize("gate", ["RX", "RY", "RZ", "PHASE"]) -@pytest.mark.parametrize("batch_size", [i for i in range(1, 2, 10)]) -def test_multi_controlled_gates( - initial_state: Tensor, expects_rotation: bool, batch_size: int, gate: str -) -> None: - rot_gate = getattr(pyq, gate) - controlled_rot_gate = getattr(pyq, "C" + gate) - phi = torch.rand(batch_size, device=DEVICE, dtype=DTYPE) - n_qubits = int(math.log2(torch.numel(initial_state))) - op = controlled_rot_gate(range(n_qubits), n_qubits).to(device=DEVICE, dtype=DTYPE) - out = op(initial_state, phi) - expected_state = ( - rot_gate([n_qubits - 1], n_qubits)(initial_state, phi) - if expects_rotation - else initial_state - ) - assert torch.allclose(out, expected_state) - - -@pytest.mark.parametrize("state_fn", [pyq.random_state, pyq.zero_state, pyq.uniform_state]) -@pytest.mark.parametrize("n_qubits", [i for i in range(1, 8)]) -@pytest.mark.parametrize("batch_size", [i for i in range(1, 8)]) -def test_isnormalized_states(state_fn: Callable, n_qubits: int, batch_size: int) -> None: - state = state_fn(n_qubits, batch_size, device=DEVICE, dtype=DTYPE) - assert pyq.is_normalized(state) - - -@pytest.mark.parametrize("n_qubits", [i for i in range(1, 8)]) -@pytest.mark.parametrize("batch_size", [i for i in range(1, 8)]) -def test_state_shapes(n_qubits: int, batch_size: int) -> None: - zero = pyq.zero_state(n_qubits, batch_size, device=DEVICE, dtype=DTYPE) - uni = pyq.uniform_state(n_qubits, batch_size, device=DEVICE, dtype=DTYPE) - rand = pyq.random_state(n_qubits, batch_size, device=DEVICE, dtype=DTYPE) - assert zero.shape == rand.shape and uni.shape == rand.shape - - -@pytest.mark.parametrize("state_fn", [pyq.random_state, pyq.zero_state, pyq.uniform_state]) -@pytest.mark.parametrize("n_qubits", [i for i in range(1, 8)]) -@pytest.mark.parametrize("batch_size", [i for i in range(1, 8)]) -def test_overlap_states_batch_nqubits(state_fn: Callable, n_qubits: int, batch_size: int) -> None: - state = state_fn(n_qubits, batch_size, device=DEVICE, dtype=DTYPE) - assert torch.allclose( - pyq.overlap(state, state), - torch.ones(batch_size), - ) - - -@pytest.mark.parametrize("state_fn", [pyq.random_state, pyq.zero_state, pyq.uniform_state]) -@pytest.mark.parametrize("batch_size", [i for i in range(1, 2, 10)]) -@pytest.mark.parametrize("n_qubits", [i for i in range(1, 6)]) -def test_parametrized_phase_gate(state_fn: Callable, batch_size: int, n_qubits: int) -> None: - qubits = [torch.randint(low=0, high=n_qubits, size=(1,)).item()] - state = state_fn(n_qubits, batch_size=batch_size, device=DEVICE, dtype=DTYPE) - phi = torch.tensor([torch.pi / 2], dtype=torch.cdouble) - phase = pyq.PHASE(qubits, n_qubits).to(device=DEVICE, dtype=DTYPE) - constant_phase = pyq.S(qubits, n_qubits).to(device=DEVICE, dtype=DTYPE) - assert torch.allclose(phase(state, phi), constant_phase(state, phi)) - - -@pytest.mark.parametrize("state_fn", [pyq.random_state, pyq.zero_state, pyq.uniform_state]) -def test_parametric_phase_hamevo( - state_fn: Callable, batch_size: int = 1, n_qubits: int = 1 -) -> None: - qubits = [0] - state = state_fn(n_qubits, batch_size=batch_size, device=DEVICE, dtype=DTYPE) - phi = torch.rand(1, dtype=torch.cdouble) - H = (OPERATIONS_DICT["Z"] - OPERATIONS_DICT["I"]) / 2 - hamevo = pyq.HamEvoExp(H, phi, qubits=qubits, n_qubits=n_qubits) - phase = pyq.PHASE(qubits, n_qubits).to(device=DEVICE, dtype=DTYPE) - assert torch.allclose(phase(state, phi), hamevo(state)) diff --git a/tests/test_notebooks.py b/tests/test_notebooks.py deleted file mode 100644 index c88ad428..00000000 --- a/tests/test_notebooks.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Test examples scripts.""" -from __future__ import annotations - -import os -import subprocess -import sys -from pathlib import Path -from typing import List - -import pytest - -expected_fail = {} # type: ignore -skip = {} # type: ignore - - -def get_ipynb_files(dir: Path) -> List[Path]: - files = [] - - for it in dir.iterdir(): - if it.suffix == ".ipynb": - files.append(it) - elif it.is_dir() and it.name != "deprecated": - files.extend(get_ipynb_files(it)) - return files - - -# FIXME: refactor choice of notebooks folders -notebooks_dir = Path(__file__).parent.parent.joinpath("docs").resolve() -assert notebooks_dir.exists() -notebooks = get_ipynb_files(notebooks_dir) -notebooks_names = [f"{example.relative_to(notebooks_dir)}" for example in notebooks] -for example, reason in expected_fail.items(): - try: - notebooks[notebooks_names.index(example)] = pytest.param( # type: ignore - example, marks=pytest.mark.xfail(reason=reason) - ) - except ValueError: - pass - -for example, reason in skip.items(): - try: - notebooks[notebooks_names.index(example)] = pytest.param( # type: ignore - example, marks=pytest.mark.skip(reason=reason) - ) - except ValueError: - pass - - -@pytest.mark.parametrize("notebook", notebooks, ids=notebooks_names) -def test_notebooks(notebook: Path) -> None: - """Execute docs notebooks as a test, passes if it returns 0.""" - jupyter_cmd = ["-m", "jupyter", "nbconvert", "--to", "python", "--execute"] - cmd = [sys.executable, *jupyter_cmd, notebook] - with subprocess.Popen( - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={**os.environ} # type: ignore - ) as run_example: - stdout, stderr = run_example.communicate() - error_string = ( - f"Notebook {notebook.name} failed\n" - f"stdout:{stdout.decode()}\n" - f"stderr: {stderr.decode()}" - ) - - if run_example.returncode != 0: - raise Exception(error_string) diff --git a/tests/test_operations.py b/tests/test_operations.py deleted file mode 100644 index 64a91d85..00000000 --- a/tests/test_operations.py +++ /dev/null @@ -1,179 +0,0 @@ -from __future__ import annotations - -import random - -import numpy as np -import torch -from torch.autograd import grad - -from pyqtorch.modules import X, zero_state - -random.seed(0) -np.random.seed(0) -torch.manual_seed(0) -torch.use_deterministic_algorithms(not torch.cuda.is_available()) - -from conftest import TestBatchedFM, TestFM, TestNetwork # noqa: E402 - -from pyqtorch.ansatz import AlternateLayerAnsatz # noqa: E402 -from pyqtorch.core import operation # noqa: E402 - -state_0 = torch.tensor([[1, 0]], dtype=torch.cdouble) -state_1 = torch.tensor([[0, 1]], dtype=torch.cdouble) - -state_00 = torch.tensor([[1, 0], [0, 0]], dtype=torch.cdouble).unsqueeze(2) -state_01 = torch.tensor([[0, 1], [0, 0]], dtype=torch.cdouble).unsqueeze(2) -state_10 = torch.tensor([[0, 0], [1, 0]], dtype=torch.cdouble).unsqueeze(2) -state_11 = torch.tensor([[0, 0], [0, 1]], dtype=torch.cdouble).unsqueeze(2) - -state_000 = zero_state(3) -state_001 = X(qubits=[2], n_qubits=3)(zero_state(3)) -state_100 = X(qubits=[0], n_qubits=3)(zero_state(3)) -state_101 = X(qubits=[2], n_qubits=3)(X(qubits=[0], n_qubits=3)(zero_state(3))) -state_110 = X(qubits=[1], n_qubits=3)(X(qubits=[0], n_qubits=3)(zero_state(3))) - -pi = torch.tensor(torch.pi, dtype=torch.cdouble) - -CNOT_mat: torch.Tensor = torch.tensor( - [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]], dtype=torch.cdouble -) - - -def test_identity() -> None: - result0: torch.Tensor = operation.I(state_0, (0,), 1) - assert torch.allclose(state_0, result0) - result1: torch.Tensor = operation.I(state_1, (0,), 1) - assert torch.allclose(state_1, result1) - - -def test_N() -> None: - result0: torch.Tensor = operation.N(state_0, (0,), 1) - assert torch.allclose(torch.tensor([[0, 0], [1, 0]], dtype=torch.cdouble), result0) - result1: torch.Tensor = operation.N(state_1, (0,), 1) - assert torch.allclose(torch.tensor([[0, 0], [0, 1]], dtype=torch.cdouble), result1) - - -# TODO: these are all the same test, would be better to parameterize a test -def test_batched_network() -> None: - ansatz = AlternateLayerAnsatz(n_qubits=4, n_layers=4) - network = TestNetwork([TestFM(), ansatz]) - batched_network = TestNetwork([TestBatchedFM(), ansatz]) - # to ensure the parameters are the same - batch_size = 2 - x = torch.linspace(-0.5, 0.5, batch_size).reshape(batch_size, 1).requires_grad_() - bx = torch.linspace(-0.5, 0.5, batch_size).reshape(batch_size, 1).requires_grad_() - y0: torch.Tensor = network(x[0]) - y1: torch.Tensor = network(x[1]) - by: torch.Tensor = batched_network(bx) - - gby = grad(by, bx, torch.ones_like(by), create_graph=True) - gy0 = grad(y0, x, torch.ones_like(y0), create_graph=True) - gy1 = grad(y1, x, torch.ones_like(y1), create_graph=True) - - assert torch.allclose(by[0], y0) - assert torch.allclose(by[1], y1) - assert torch.allclose(gby[0][0], gy0[0][0]) - assert torch.allclose(gby[0][1], gy1[0][1]) - - -def test_batched_fm() -> None: - network = TestNetwork([TestFM()]) - batched_network = TestNetwork([TestBatchedFM()]) - - batch_size = 3 - x = torch.linspace(-0.5, 0.5, batch_size).reshape(batch_size, 1).requires_grad_() - bx = torch.linspace(-0.5, 0.5, batch_size).reshape(batch_size, 1).requires_grad_() - - y0: torch.Tensor = network(x[0]) - y1: torch.Tensor = network(x[1]) - by: torch.Tensor = batched_network(bx) - - gby = grad(by, bx, torch.ones_like(by), create_graph=True) - gy0 = grad(y0, x, torch.ones_like(y0), create_graph=True) - gy1 = grad(y1, x, torch.ones_like(y1), create_graph=True) - - # Assert result values are the same for single layer - assert torch.allclose(by[0], y0) - assert torch.allclose(by[1], y1) - # Assert gradients are the same - assert torch.allclose(gby[0][0], gy0[0][0]) - assert torch.allclose(gby[0][1], gy1[0][1]) - - -def test_batched_ansatz() -> None: - network = TestNetwork(network=[AlternateLayerAnsatz(n_qubits=2, n_layers=1)], n_qubits=2) - - batch_size = 2 - x = torch.linspace(-0.5, 0.5, batch_size).reshape(batch_size, 1).requires_grad_() - bx = torch.linspace(-0.5, 0.5, batch_size).reshape(batch_size, 1).requires_grad_() - y0: torch.Tensor = network(x[0]) - y1: torch.Tensor = network(x[1]) - by: torch.Tensor = network(bx) - - assert torch.allclose(by[0], y0) - assert torch.allclose(by[1], y1) - - -def test_CNOT_state00_controlqubit_0() -> None: - result: torch.Tensor = operation.CNOT(state_00, (0, 1), 2) - assert torch.equal(state_00, result) - - -def test_CNOT_state10_controlqubit_0() -> None: - result: torch.Tensor = operation.CNOT(state_10, (0, 1), 2) - assert torch.equal(state_11, result) - - -def test_CNOT_state11_controlqubit_0() -> None: - result: torch.Tensor = operation.CNOT(state_11, (0, 1), 2) - assert torch.equal(state_10, result) - - -def test_CNOT_state00_controlqubit_1() -> None: - result: torch.Tensor = operation.CNOT(state_00, (1, 0), 2) - assert torch.equal(state_00, result) - - -def test_CNOT_state10_controlqubit_1() -> None: - result: torch.Tensor = operation.CNOT(state_10, (1, 0), 2) - assert torch.equal(state_10, result) - - -def test_CNOT_state11_controlqubit_1() -> None: - result: torch.Tensor = operation.CNOT(state_11, (1, 0), 2) - assert torch.equal(state_01, result) - - -def test_CRY_state10_controlqubit_0() -> None: - result: torch.Tensor = operation.CRY(pi, state_10, (0, 1), 2) - assert torch.allclose(state_11, result) - - -def test_CRY_state01_controlqubit_0() -> None: - result: torch.Tensor = operation.CRY(pi, state_01, (1, 0), 2) - assert torch.allclose(state_11, result) - - -def test_CSWAP_state000_controlqubit_0() -> None: - result: torch.Tensor = operation.CSWAP(state_000, (0, 1, 2), 3) - assert torch.allclose(state_000, result) - - -def test_CSWAP_state001_controlqubit_0() -> None: - result: torch.Tensor = operation.CSWAP(state_001, (0, 1, 2), 3) - assert torch.allclose(state_001, result) - - -def test_CSWAP_state100_controlqubit_0() -> None: - result: torch.Tensor = operation.CSWAP(state_100, (0, 1, 2), 3) - assert torch.allclose(state_100, result) - - -def test_CSWAP_state101_controlqubit_0() -> None: - result: torch.Tensor = operation.CSWAP(state_101, (0, 1, 2), 3) - assert torch.allclose(state_110, result) - - -def test_CSWAP_state110_controlqubit_0() -> None: - result: torch.Tensor = operation.CSWAP(state_110, (0, 1, 2), 3) - assert torch.allclose(state_101, result) diff --git a/tests/test_operations_hamevo.py b/tests/test_operations_hamevo.py deleted file mode 100644 index a0784b09..00000000 --- a/tests/test_operations_hamevo.py +++ /dev/null @@ -1,186 +0,0 @@ -from __future__ import annotations - -import random -from math import isclose - -import networkx as nx -import numpy as np -import torch - -from pyqtorch.core import batched_operation, circuit, operation -from pyqtorch.core.batched_operation import ( - batched_hamiltonian_evolution, - batched_hamiltonian_evolution_eig, -) -from pyqtorch.core.circuit import QuantumCircuit -from pyqtorch.core.operation import hamiltonian_evolution, hamiltonian_evolution_eig -from pyqtorch.matrices import generate_ising_from_graph - -random.seed(0) -np.random.seed(0) -torch.manual_seed(0) -torch.use_deterministic_algorithms(not torch.cuda.is_available()) - -state_00 = torch.tensor([[1, 0], [0, 0]], dtype=torch.cdouble).unsqueeze(2) -state_10 = torch.tensor([[0, 1], [0, 0]], dtype=torch.cdouble).unsqueeze(2) -state_01 = torch.tensor([[0, 0], [1, 0]], dtype=torch.cdouble).unsqueeze(2) -state_11 = torch.tensor([[0, 0], [0, 1]], dtype=torch.cdouble).unsqueeze(2) - -pi = torch.tensor(torch.pi, dtype=torch.cdouble) - -CNOT_mat: torch.Tensor = torch.tensor( - [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]], dtype=torch.cdouble -) - - -def test_hamevo_single() -> None: - N = 4 - qc = circuit.QuantumCircuit(N) - psi = qc.uniform_state(1) - - def overlap(state1: torch.Tensor, state2: torch.Tensor) -> float: - N = len(state1.shape) - 1 - state1_T = torch.transpose(state1, N, 0) - overlap = torch.tensordot(state1_T, state2, dims=N) - return float(torch.abs(overlap**2).flatten()) - - sigmaz = torch.diag(torch.tensor([1.0, -1.0], dtype=torch.cdouble)) - Hbase = torch.kron(sigmaz, sigmaz) - H = torch.kron(Hbase, Hbase) - t_evo = torch.tensor([torch.pi / 4], dtype=torch.cdouble) - psi_star = operation.hamiltonian_evolution(H, psi, t_evo, range(N), N) - result: float = overlap(psi_star, psi) - - assert isclose(result, 0.5) - - -def test_hamevo_eig_single() -> None: - N = 4 - qc = circuit.QuantumCircuit(N) - psi = qc.uniform_state(1) - - def overlap(state1: torch.Tensor, state2: torch.Tensor) -> float: - N = len(state1.shape) - 1 - state1_T = torch.transpose(state1, N, 0) - overlap = torch.tensordot(state1_T, state2, dims=N) - return float(torch.abs(overlap**2).flatten()) - - sigmaz = torch.diag(torch.tensor([1.0, -1.0], dtype=torch.cdouble)) - Hbase = torch.kron(sigmaz, sigmaz) - H = torch.kron(Hbase, Hbase) - t_evo = torch.tensor([torch.pi / 4], dtype=torch.cdouble) - psi_star = operation.hamiltonian_evolution_eig(H, psi, t_evo, range(N), N) - result: float = overlap(psi_star, psi) - - assert isclose(result, 0.5) - - -def test_hamevo_batch() -> None: - N = 4 - qc = circuit.QuantumCircuit(N) - psi = qc.uniform_state(batch_size=2) - - def overlap(state1: torch.Tensor, state2: torch.Tensor) -> list[float]: - N = len(state1.shape) - 1 - state1_T = torch.transpose(state1, N, 0) - overlap = torch.tensordot(state1_T, state2, dims=N) - return list(map(float, torch.abs(overlap**2).flatten())) - - sigmaz = torch.diag(torch.tensor([1.0, -1.0], dtype=torch.cdouble)) - Hbase = torch.kron(sigmaz, sigmaz) - H = torch.kron(Hbase, Hbase) - H_conj = H.conj() - - t_evo = torch.tensor([0], dtype=torch.cdouble) - psi = operation.hamiltonian_evolution(H, psi, t_evo, range(N), N) - - t_evo = torch.tensor([torch.pi / 4], dtype=torch.cdouble) - psi_star = operation.hamiltonian_evolution(H, psi, t_evo, range(N), N) - H_batch = torch.stack((H, H_conj), dim=2) - batched_operation.batched_hamiltonian_evolution(H_batch, psi, t_evo, range(N), N) - result: list[float] = overlap(psi_star, psi) - - assert map(isclose, zip(result, [0.5, 0.5])) # type: ignore [arg-type] - - -def test_hamevo_rk4_vs_eig_diag_H() -> None: - n_qubits: int = 7 - batch_size: int = 10 - graph: nx.Graph = nx.fast_gnp_random_graph(n_qubits, 0.7) - qc = QuantumCircuit(n_qubits) - psi = qc.uniform_state(batch_size) - - H_diag = generate_ising_from_graph(graph) - H = torch.diag(H_diag) - - n_trials = 10 - wf_save_rk = torch.zeros((n_trials,) + tuple(psi.shape)).to(torch.cdouble) - wf_save_eig = torch.zeros((n_trials,) + tuple(psi.shape)).to(torch.cdouble) - - for i in range(n_trials): - t_evo = torch.rand(batch_size) * 0.5 - - psi_star = hamiltonian_evolution(H, psi, t_evo, range(n_qubits), n_qubits) - wf_save_rk[i] = psi_star - - psi_star = hamiltonian_evolution_eig(H, psi, t_evo, range(n_qubits), n_qubits) - wf_save_eig[i] = psi_star - - diff = torch.tensor( - [torch.max(abs(wf_save_rk[i, ...] - wf_save_eig[i, ...])) for i in range(n_trials)] - ) - - assert torch.max(diff) <= 10 ** (-6) - - -def test_hamevo_rk4_vs_eig_general_H() -> None: - n_qubits: int = 6 - batch_size: int = 10 - - qc = QuantumCircuit(n_qubits) - psi = qc.uniform_state(batch_size) - - H_0 = torch.randn((2**n_qubits, 2**n_qubits), dtype=torch.cdouble) - H = (H_0 + torch.conj(H_0.transpose(0, 1))).to(torch.cdouble) - - n_trials = 10 - wf_save_rk = torch.zeros((n_trials,) + tuple(psi.shape)).to(torch.cdouble) - wf_save_eig = torch.zeros((n_trials,) + tuple(psi.shape)).to(torch.cdouble) - - for i in range(n_trials): - t_evo = torch.rand(batch_size) * 0.5 - psi_star = hamiltonian_evolution(H, psi, t_evo, range(n_qubits), n_qubits) - wf_save_rk[i] = psi_star - psi_star = hamiltonian_evolution_eig(H, psi, t_evo, range(n_qubits), n_qubits) - wf_save_eig[i] = psi_star - - diff = torch.tensor( - [torch.max(abs(wf_save_rk[i, ...] - wf_save_eig[i, ...])) for i in range(n_trials)] - ) - - assert torch.max(diff) <= 10 ** (-5) - - -def test_hamevo_rk4_vs_eig_general_H_batched() -> None: - n_qubits: int = 5 - batch_size: int = 20 - - qc = QuantumCircuit(n_qubits) - psi = qc.uniform_state(batch_size) - - H_batch = torch.zeros(2**n_qubits, 2**n_qubits, batch_size).to(torch.cdouble) - for i in range(batch_size): - H_0 = torch.randn((2**n_qubits, 2**n_qubits), dtype=torch.cdouble) - H_batch[..., i] = (H_0 + torch.conj(H_0.transpose(0, 1))).to(torch.cdouble) - - t_evo = (torch.rand(batch_size) * 0.5).to(torch.cdouble) - - psi_star_norm = batched_hamiltonian_evolution(H_batch, psi, t_evo, range(n_qubits), n_qubits) - - psi_star_eig = batched_hamiltonian_evolution_eig(H_batch, psi, t_evo, range(n_qubits), n_qubits) - - diff = torch.tensor( - [torch.max(abs(psi_star_norm[..., b] - psi_star_eig[..., b])) for b in range(batch_size)] - ) - - assert torch.max(diff) <= 10 ** (-6)