From 906179f71f829ed83fab135b61482bae311e0d13 Mon Sep 17 00:00:00 2001 From: "A.C.E07" Date: Wed, 18 Feb 2026 01:21:19 +0800 Subject: [PATCH 1/2] feature: Approximate State Preparation via Sweeping - Added `sweeping` module. - Added black box unit test. - Added notebook. - Updated README.md --- notebooks/textbook/Sweeping.ipynb | 199 ++++++++++++++ .../algorithms/sweeping/__init__.py | 24 ++ .../algorithms/sweeping/ansatzes.py | 54 ++++ .../algorithms/sweeping/sweeping.md | 10 + .../algorithms/sweeping/sweeping.py | 244 ++++++++++++++++++ .../algorithms/sweeping/typing.py | 7 + .../algorithms/sweeping/test_sweeping.py | 28 ++ 7 files changed, 566 insertions(+) create mode 100644 notebooks/textbook/Sweeping.ipynb create mode 100644 src/braket/experimental/algorithms/sweeping/__init__.py create mode 100644 src/braket/experimental/algorithms/sweeping/ansatzes.py create mode 100644 src/braket/experimental/algorithms/sweeping/sweeping.md create mode 100644 src/braket/experimental/algorithms/sweeping/sweeping.py create mode 100644 src/braket/experimental/algorithms/sweeping/typing.py create mode 100644 test/unit_tests/braket/experimental/algorithms/sweeping/test_sweeping.py diff --git a/notebooks/textbook/Sweeping.ipynb b/notebooks/textbook/Sweeping.ipynb new file mode 100644 index 00000000..9be13632 --- /dev/null +++ b/notebooks/textbook/Sweeping.ipynb @@ -0,0 +1,199 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ad3c306d", + "metadata": {}, + "source": [ + "# Approximate State Preparation via Sweeping" + ] + }, + { + "cell_type": "markdown", + "id": "e804b65a", + "metadata": {}, + "source": [ + "Tensor networks are a natural way to interact with quantum circuits and develop algorithms for them. A major application of tensor networks is in performing circuit optimization,\n", + "either via MPS/MPO IRs, or via sweeping. We focus on using tensor network sweeping as an algorithm to optimize arbitrary ansatzes to approximate a target statevector or unitary\n", + "in a smooth, consistently improving manner. The approach is a much better alternative for such cases compared to gradient-based and ML approaches which are prone to local minimas\n", + "and barren plateaus. For a better understanding of the technique see [1].\n", + "\n", + "In this notebook, we evaluate the performance of the sweeping algorithm for approximate state preparation, and compare its depth to exact preparation algorithms.\n", + "\n", + "\n", + "---\n", + "# References \n", + "\n", + "[1] M. S. Rudolph, J. Chen, J. Miller, A. Acharya, A. Perdomo-Ortiz (2022). \\\"Decomposition of Matrix Product States into Shallow Quantum Circuits\\\", [arXiv:2209.00595](https://arxiv.org/abs/2209.00595).\"" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "3dfde576", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "from braket.devices import LocalSimulator\n", + "from braket.experimental.algorithms.sweeping import (\n", + " generate_staircase_ansatz,\n", + " sweep_state_approximation,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b54e86ff", + "metadata": {}, + "outputs": [], + "source": [ + "def compile_with_state_sweep_pass(num_qubits: int) -> tuple[float, int]:\n", + " state = np.random.uniform(-1, 1, 2**num_qubits) + 1j * np.random.uniform(-1, 1, 2**num_qubits)\n", + " state /= np.linalg.norm(state)\n", + "\n", + " compiled_circuit = sweep_state_approximation(\n", + " target_state=state,\n", + " unitary_layers=generate_staircase_ansatz(\n", + " num_qubits=num_qubits, num_layers=int((num_qubits**2) / 2)\n", + " ),\n", + " num_sweeps=100 * num_qubits,\n", + " log=False,\n", + " )\n", + "\n", + " result = LocalSimulator(\"braket_sv\").run(compiled_circuit.state_vector(), shots=0).result()\n", + " fidelity = np.abs(np.vdot(state, result.values[0]))\n", + "\n", + " return fidelity, compiled_circuit.depth" + ] + }, + { + "cell_type": "markdown", + "id": "226f0286", + "metadata": {}, + "source": [ + "# Run on a local simulator\n", + "We run the compiled circuit on a classical simulator first. You can choose between a local simulator or an on-demand simulator." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "768cf54f", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 200/200 [00:00<00:00, 608.63it/s]\n", + "100%|██████████| 300/300 [00:00<00:00, 315.05it/s]\n", + "100%|██████████| 400/400 [00:04<00:00, 91.43it/s] \n", + "100%|██████████| 500/500 [00:09<00:00, 53.73it/s]\n", + "100%|██████████| 600/600 [00:22<00:00, 26.87it/s]\n", + "100%|██████████| 700/700 [00:43<00:00, 16.08it/s]\n", + "100%|██████████| 800/800 [01:16<00:00, 10.49it/s]\n", + "100%|██████████| 900/900 [02:16<00:00, 6.60it/s]\n" + ] + } + ], + "source": [ + "fidelities = []\n", + "depths = []\n", + "\n", + "for num_qubits in range(2, 10):\n", + " fidelity, depth = compile_with_state_sweep_pass(num_qubits)\n", + " fidelities.append(fidelity)\n", + " depths.append(depth)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "3e751645", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Average Fidelity: 1.0000\n" + ] + } + ], + "source": [ + "print(f\"Average Fidelity: {np.mean(fidelities):.4f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4df23c73", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(12, 5))\n", + "plt.subplot(1, 2, 1)\n", + "plt.plot(range(2, 10), depths, marker=\"o\", label=\"Sweeping Pass\")\n", + "plt.plot(range(2, 10), [2**n for n in range(2, 10)], marker=\"o\", label=\"Exact\")\n", + "plt.title(\"Depth of Compiled Circuit\")\n", + "plt.xlabel(\"Number of Qubits\")\n", + "plt.ylabel(\"Circuit Depth\")\n", + "plt.legend()" + ] + }, + { + "cell_type": "markdown", + "id": "3847dc04", + "metadata": {}, + "source": [ + "Note: Charges shown are estimates based on your Amazon Braket simulator and quantum processing unit (QPU) task usage. Estimated charges shown may differ from your actual charges. Estimated charges do not factor in any discounts or credits, and you may experience additional charges based on your use of other services such as Amazon Elastic Compute Cloud (Amazon EC2)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/braket/experimental/algorithms/sweeping/__init__.py b/src/braket/experimental/algorithms/sweeping/__init__.py new file mode 100644 index 00000000..97734199 --- /dev/null +++ b/src/braket/experimental/algorithms/sweeping/__init__.py @@ -0,0 +1,24 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file 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. + +__all__ = [ + "generate_brickwall_ansatz", + "generate_staircase_ansatz", + "sweep_state_approximation", +] + +from braket.experimental.algorithms.sweeping.ansatzes import ( + generate_brickwall_ansatz, + generate_staircase_ansatz, +) +from braket.experimental.algorithms.sweeping.sweeping import sweep_state_approximation diff --git a/src/braket/experimental/algorithms/sweeping/ansatzes.py b/src/braket/experimental/algorithms/sweeping/ansatzes.py new file mode 100644 index 00000000..bf390333 --- /dev/null +++ b/src/braket/experimental/algorithms/sweeping/ansatzes.py @@ -0,0 +1,54 @@ +import numpy as np + +from braket.experimental.algorithms.sweeping.typing import UNITARY_LAYER + + +def generate_staircase_ansatz(num_qubits: int, num_layers: int) -> list[UNITARY_LAYER]: + """Generate a staircase ansatz with the specified number of qubits and layers. + + Args: + num_qubits (int): The number of qubits in the ansatz. + num_layers (int): The number of layers in the ansatz. + + Returns: + list[UNITARY_LAYER]: The generated staircase ansatz. + """ + unitary_layers: list[UNITARY_LAYER] = [] + + for i in range(num_layers): + unitary_layers.append([]) + + for j in range(num_qubits - 1): + unitary_layers[i].append(([j, j + 1], np.eye(4, dtype=np.complex128))) + + return unitary_layers + + +def generate_brickwall_ansatz(num_qubits: int, num_layers: int) -> list[UNITARY_LAYER]: + """Generate a brickwall ansatz with the specified number of qubits and layers. + + Args: + num_qubits (int): The number of qubits in the ansatz. + num_layers (int): The number of layers in the ansatz. + + Returns: + list[UNITARY_LAYER]: The generated brickwall ansatz. + """ + unitary_layers: list[UNITARY_LAYER] = [] + + if num_qubits % 2 == 0: + start_1 = 1 + start_2 = 0 + else: + start_1 = 0 + start_2 = 1 + + for i in range(num_layers): + unitary_layers.append([]) + + for j in range(start_1, num_qubits - 1, 2): + unitary_layers[i].append(([j, j + 1], np.eye(4, dtype=np.complex128))) + for j in range(start_2, num_qubits - 1, 2): + unitary_layers[i].append(([j, j + 1], np.eye(4, dtype=np.complex128))) + + return unitary_layers diff --git a/src/braket/experimental/algorithms/sweeping/sweeping.md b/src/braket/experimental/algorithms/sweeping/sweeping.md new file mode 100644 index 00000000..40aff4f3 --- /dev/null +++ b/src/braket/experimental/algorithms/sweeping/sweeping.md @@ -0,0 +1,10 @@ +Tensor networks are a natural way to interact with quantum circuits and develop algorithms for them. A major application of tensor networks is in performing circuit optimization, +either via MPS/MPO IRs, or via sweeping. We focus on using tensor network sweeping as an algorithm to optimize arbitrary ansatzes to approximate a target statevector or unitary +in a smooth, consistently improving manner. The approach is a much better alternative for such cases compared to gradient-based and ML approaches which are prone to local minimas +and barren plateaus. For a better understanding of the technique see [Rudolph2022](https://arxiv.org/abs/2209.00595). + + \ No newline at end of file diff --git a/src/braket/experimental/algorithms/sweeping/sweeping.py b/src/braket/experimental/algorithms/sweeping/sweeping.py new file mode 100644 index 00000000..be6dd907 --- /dev/null +++ b/src/braket/experimental/algorithms/sweeping/sweeping.py @@ -0,0 +1,244 @@ +import numpy as np +import quimb.tensor as qtn +from numpy.typing import NDArray +from tqdm import tqdm + +from braket.circuits import Circuit +from braket.devices import LocalSimulator +from braket.experimental.algorithms.sweeping.typing import UNITARY_LAYER + + +class StateSweepApproximator: + """Initialize the StateSweepApproximator class. + + This algorithm performs sweeps over unitary layers representing + an ansatz to be trained to match the target state to improve + its approximation. Due to the difficulty in converging to good + fidelity with pure classical ML approaches, and the time it takes + to train them, sweeping is a much better alternative, running faster + and giving smooth improvement. + + This approach is inspired by Rudolph et al. [1] which was used to do + the same task under Oall approach. + + [1] https://www.nature.com/articles/s41467-023-43908-6 + """ + + @staticmethod + def get_tensor_network_from_unitary_layers( + num_qubits: int, unitary_layers: list[UNITARY_LAYER] + ) -> qtn.TensorNetwork: + """Create `qtn.TensorNetwork` from unitary layers. + + Args: + num_qubits (int): The number of qubits used by target state. + unitary_layers (list[UNITARY_LAYER]): The unitary layers. + + Returns: + tensor_network (qtn.TensorNetwork): The tensor network. + """ + circuit = qtn.Circuit(N=num_qubits) + gate_tracker: list[str] = [] + + for i, layer in enumerate(unitary_layers): + for j, (qubits, unitary) in enumerate(layer): + circuit.apply_gate_raw( + unitary.reshape(2 * len(qubits) * (2,)), + where=qubits, + contract=False, + ) + gate_tracker.append(f"{i}_{j}") + + tensor_network = qtn.TensorNetwork(circuit.psi) + + # We do not want to include the qubits, so we will + # explicitly control the iteration index + gate_index = 0 + + for gate in tensor_network: + # We only update the gates, not the qubits + if "PSI0" in gate.tags: + continue + + # Remove existing tags from the gate + gate.drop_tags(tags=gate.tags) + + # Marshal the gate with the gate tracker + # This is needed to ensure the gates are properly tagged + # for updating the unitary layers + gate.add_tag(gate_tracker[gate_index]) + gate_index += 1 + + return tensor_network + + @staticmethod + def sweep_unitary_layers( + target_mps: qtn.MatrixProductState, + tensor_network: qtn.TensorNetwork, + unitary_layers: list[UNITARY_LAYER], + ) -> list[UNITARY_LAYER]: + """Sweep the unitary layers to improve the fidelity between + tensor network created by the unitary layers and the target + state. + + Args: + target_mps (qtn.MatrixProductState): The target state represented as a MPS. + tensor_network (qtn.TensorNetwork): The tensor network created by the unitary layers. + unitary_layers (list[UNITARY_LAYER]): The unitary layers. + + Returns: + unitary_layers (list[UNITARY_LAYER]): The unitary layers which have been updated inplace. + """ + target_mps_adjoint = target_mps.conj() + + current_tn = qtn.MatrixProductState.from_dense( + tensor_network.to_dense(tensor_network.outer_inds()) + ) + + for gate in reversed(tensor_network.tensors): + num_qubits = int(len(gate.inds) / 2) + + if "PSI0" in gate.tags: + continue + + left_inds = gate.inds[:num_qubits] + right_inds = gate.inds[num_qubits:] + + # To avoid unnecessary contraction, we "move" + # through the tensor network by applying the adjoint + # of the tensor to the tensor network, and applying + # the updated tensor to MPS + current_tn = current_tn @ gate.conj() + environment_tensor: qtn.TensorNetwork = target_mps_adjoint @ current_tn + + u, _, vh = np.linalg.svd(environment_tensor.to_dense((left_inds), (right_inds))) + u_new = np.dot(u, vh) + + # To avoid unnecessary contraction, we "move" + # through the tensor network by applying the adjoint + # of the tensor to the tensor network, and applying + # the updated tensor to MPS + new_tensor = qtn.Tensor( + u_new.reshape(2 * num_qubits * (2,)).conj(), + inds=gate.inds, + tags=gate.tags, + ) + + target_mps_adjoint = target_mps_adjoint @ new_tensor + + gate_tag = list(gate.tags)[0] + layer_index, block_index = gate_tag.split("_") + + unitary_layers[int(layer_index)][int(block_index)] = ( + unitary_layers[int(layer_index)][int(block_index)][0], + u_new.conj(), + ) + + return unitary_layers + + @staticmethod + def circuit_from_unitary_layers( + num_qubits: int, unitary_layers: list[UNITARY_LAYER] + ) -> Circuit: + """Create a `Circuit` instance from + the unitary layers. + + Args: + num_qubits (int): The number of qubits used by target state. + unitary_layers (list[UNITARY_LAYER]): The unitary layers. + + Returns: + circuit (Circuit): The qiskit circuit. + """ + circuit = Circuit() + + for layer in unitary_layers: + for qubits, unitary_matrix in layer: + circuit.unitary( + matrix=unitary_matrix, + targets=qubits, + ) + + return circuit + + def __call__( + self, + target_state: NDArray[np.complex128], + unitary_layers: list[UNITARY_LAYER], + num_sweeps: int, + log: bool = False, + ) -> Circuit: + """Approximate a state via sweeping. + + Args: + target_state (NDArray[np.complex128]): The state we want to approximate. + unitary_layers (list[UNITARY_LAYER]): The initial unitary layers. + num_sweeps (int): The number of times to sweep the unitary layers. + log (bool): Whether to print logs of fidelity or not. + + Returns: + circuit (Circuit): The circuit. + """ + num_qubits = int(np.log2(target_state.shape[0])) + target_mps = qtn.MatrixProductState.from_dense(target_state) + + for i in tqdm(range(num_sweeps)): + circuit_tensor_network = self.get_tensor_network_from_unitary_layers( + num_qubits, unitary_layers + ) + + if log and i % 20 == 0: + fidelity = ( + np.abs( + np.vdot( + target_state, + circuit_tensor_network.to_dense( + circuit_tensor_network.outer_inds() + ).reshape(target_state.shape), + ) + ) + ** 2 + ) + print(f"Fidelity: {fidelity}") + + unitary_layers = self.sweep_unitary_layers( + target_mps, circuit_tensor_network, unitary_layers + ) + + circuit = self.circuit_from_unitary_layers(num_qubits, unitary_layers) + + if log: + result = LocalSimulator("braket_sv").run(circuit.state_vector(), shots=0).result() + fidelity = ( + np.abs( + np.vdot( + result.values[0], + target_state, + ) + ) + ** 2 + ) + print(f"Final Fidelity: {fidelity}") + + return circuit + + +def sweep_state_approximation( + target_state: NDArray[np.complex128], + unitary_layers: list[UNITARY_LAYER], + num_sweeps: int, + log: bool = False, +) -> Circuit: + """Approximate a state via sweeping. + + Args: + target_state (NDArray[np.complex128]): The state we want to approximate. + unitary_layers (list[UNITARY_LAYER]): The initial unitary layers. + num_sweeps (int): The number of times to sweep the unitary layers. + log (bool): Whether to print logs of fidelity or not. + + Returns: + circuit (Circuit): The circuit. + """ + approximator = StateSweepApproximator() + return approximator(target_state, unitary_layers, num_sweeps, log) diff --git a/src/braket/experimental/algorithms/sweeping/typing.py b/src/braket/experimental/algorithms/sweeping/typing.py new file mode 100644 index 00000000..f6b68946 --- /dev/null +++ b/src/braket/experimental/algorithms/sweeping/typing.py @@ -0,0 +1,7 @@ +from typing import TypeAlias + +import numpy as np +from numpy.typing import NDArray + +UNITARY_BLOCK: TypeAlias = tuple[list[int], NDArray[np.complex128]] +UNITARY_LAYER: TypeAlias = list[UNITARY_BLOCK] diff --git a/test/unit_tests/braket/experimental/algorithms/sweeping/test_sweeping.py b/test/unit_tests/braket/experimental/algorithms/sweeping/test_sweeping.py new file mode 100644 index 00000000..93394f72 --- /dev/null +++ b/test/unit_tests/braket/experimental/algorithms/sweeping/test_sweeping.py @@ -0,0 +1,28 @@ +import numpy as np +import pytest + +from braket.devices import LocalSimulator +from braket.experimental.algorithms.sweeping import ( + generate_staircase_ansatz, + sweep_state_approximation, +) + + +@pytest.mark.parametrize("num_qubits", [2, 3, 4, 5]) +def compile_with_state_sweep_pass(num_qubits: int) -> None: + state = np.random.uniform(-1, 1, 2**num_qubits) + 1j * np.random.uniform(-1, 1, 2**num_qubits) + state /= np.linalg.norm(state) + + compiled_circuit = sweep_state_approximation( + target_state=state, + unitary_layers=generate_staircase_ansatz( + num_qubits=num_qubits, num_layers=int((num_qubits**2) / 2) + ), + num_sweeps=100 * num_qubits, + log=False, + ) + + result = LocalSimulator("braket_sv").run(compiled_circuit.state_vector(), shots=0).result() + fidelity = np.abs(np.vdot(state, result.values[0])) + + assert fidelity > 0.99 From 7a4b3706166c32412ba017af7a57d6d184855d75 Mon Sep 17 00:00:00 2001 From: "A.C.E07" Date: Wed, 18 Feb 2026 01:21:39 +0800 Subject: [PATCH 2/2] Updated README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d234ca0d..c79392f3 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Running notebooks locally requires additional dependencies located in [notebooks | Quantum Walk | [Quantum_Walk.ipynb](notebooks/textbook/Quantum_Walk.ipynb) | [Childs2002](https://arxiv.org/abs/quant-ph/0209131) | |Shor's| [Shors_Algorithm.ipynb](notebooks/textbook/Shors_Algorithm.ipynb) | [Shor1998](https://arxiv.org/abs/quant-ph/9508027) | | Simon's | [Simons_Algorithm.ipynb](notebooks/textbook/Simons_Algorithm.ipynb) | [Simon1997](https://epubs.siam.org/doi/10.1137/S0097539796298637) | - +| Sweeping | [Sweeping.ipynb](notebooks/textbook/Sweeping.ipynb) | [Rudolph2022](https://arxiv.org/abs/2209.00595) | | Advanced algorithms | Notebook | References | | ----- | ----- | ----- |