{ "cells": [ { "cell_type": "markdown", "id": "1cd24680", "metadata": {}, "source": [ "## A quick introduction to HSMPO\n", "\n", "The Hybrid Stabilizer MPO representation of a quantum circuit combines the efficiency of stabilizers with the flexibility of a tensor network representation. The effectivness of such a representation is more pronounced when the amount of non-Clifford gates in the circuit is small, or when we don't have too much entanglement stored in the final MPO representation. Let's code some examples of quantum circuits to practice with this formalism.\n", "\n", "### A first simple circuit\n", "\n", "To start, we need to recall the quantum circuit simulation interface required by Qibo. To prepare a quantum circuit we will need a few Qibo's modules" ] }, { "cell_type": "code", "execution_count": 49, "id": "0cf49182", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "from qibo import Circuit, gates\n", "\n", "def construct_circuit(nqubits):\n", " \"\"\"Simple function to construct a dummy quantum circuit.\"\"\"\n", " # We first define an empty circuit of nqubits\n", " circ = Circuit(nqubits)\n", " # We then add some gates\n", " for _ in range(3):\n", " for q in range(nqubits):\n", " circ.add(gates.H(q))\n", " # We add some random rotations\n", " if np.random.uniform(0,1) <= 0.8:\n", " circ.add(gates.RY(q, theta=np.random.uniform(0., 2 * np.pi)))\n", " for q in range(nqubits):\n", " # We add some entanglement\n", " circ.add(gates.CNOT(q0=q%nqubits, q1=(q+1)%nqubits))\n", " return circ" ] }, { "cell_type": "markdown", "id": "8d0d4411", "metadata": {}, "source": [ "Now we know how to prepare a quantum circuit using Qibo. Let's construct its HSMPO." ] }, { "cell_type": "code", "execution_count": 50, "id": "69e4c7a3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0: ─H─RY─o───────────────X─H─RY─o───────────────X─H─RY─o───────────────X─\n", "1: ─H─RY─X─o─────────────|─H────X─o─────────────|─H─RY─X─o─────────────|─\n", "2: ─H─RY───X─o───────────|─H─RY───X─o───────────|─H─RY───X─o───────────|─\n", "3: ─H─RY─────X─o─────────|─H────────X─o─────────|─H─RY─────X─o─────────|─\n", "4: ─H─RY───────X─o───────|─H─RY───────X─o───────|─H─RY───────X─o───────|─\n", "5: ─H─RY─────────X─o─────|─H─RY─────────X─o─────|─H─RY─────────X─o─────|─\n", "6: ─H─RY───────────X─o───|─H─RY───────────X─o───|─H─RY───────────X─o───|─\n", "7: ─H─RY─────────────X─o─|─H─RY─────────────X─o─|─H─RY─────────────X─o─|─\n", "8: ─H─RY───────────────X─o─H──────────────────X─o─H─RY───────────────X─o─\n" ] } ], "source": [ "from mpstab import HSMPO\n", "\n", "# Set the number of qubits\n", "nqubits = 9\n", "# Construct a circuit\n", "circ = construct_circuit(nqubits)\n", "\n", "# Print the circuit structure\n", "print(circ)\n", "\n", "# Construct its hybrid stabilizer MPO\n", "surrogate = HSMPO(circ)" ] }, { "cell_type": "markdown", "id": "b2c36f77", "metadata": {}, "source": [ "Now, we may be interested in computing the expectation value of some observable over the state prepared by executing the given quantum circuit. " ] }, { "cell_type": "code", "execution_count": 52, "id": "a7acc93f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Chosen observable: IIIIXIIII\n", "Computed expectation value: 0.06246974547469883\n" ] } ], "source": [ "# Defining an observable\n", "obs_str = \"I\" * int(nqubits / 2) + \"X\" + \"I\" * int(nqubits / 2)\n", "print(f\"Chosen observable: {obs_str}\")\n", "\n", "mpstab_expval = surrogate.expectation(observable=obs_str)\n", "print(f\"Computed expectation value: {mpstab_expval}\")" ] }, { "cell_type": "markdown", "id": "94798e06", "metadata": {}, "source": [ "Let's check if this value matched a statevector simulator, like the ones implemented in Qibo!" ] }, { "cell_type": "code", "execution_count": 54, "id": "4a94724e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.06246974547470002\n" ] } ], "source": [ "# We have implemented a function to convert strings into Qibo observables\n", "from mpstab.utils import obs_string_to_qibo_hamiltonian\n", "\n", "qibo_hamiltonian = obs_string_to_qibo_hamiltonian(obs_str)\n", "# Compute expectation value\n", "qibo_expval = qibo_hamiltonian.expectation_from_state(\n", " circ().state()\n", ")\n", "print(qibo_expval)" ] }, { "cell_type": "markdown", "id": "8b93169f", "metadata": {}, "source": [ "This match is expected, since the HSMPO representation is exact in this simulation regime. We can always decide to lighten the MPO representation of the circuit by setting a maximum bond dimension in our tensor network representation! If the approximation is too extreme, we expect the final expectation value carrying some error. " ] }, { "cell_type": "code", "execution_count": 55, "id": "709f8f4b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.21156020226400218\n" ] } ], "source": [ "approx_surrogate = HSMPO(circ, max_bond_dimension=2)\n", "approx_mpstab_expval = approx_surrogate.expectation(obs_str)\n", "\n", "print(approx_mpstab_expval)" ] }, { "cell_type": "markdown", "id": "ea614ea4", "metadata": {}, "source": [ "And the error decreases if we set an higher bond dimension." ] }, { "cell_type": "code", "execution_count": 59, "id": "76640047", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.06256210325489374\n" ] } ], "source": [ "approx_surrogate = HSMPO(circ, max_bond_dimension=8)\n", "approx_mpstab_expval = approx_surrogate.expectation(obs_str)\n", "\n", "print(approx_mpstab_expval)" ] }, { "cell_type": "markdown", "id": "9b1b77d8", "metadata": {}, "source": [ "### Simulating a Clifford circuit\n", "\n", "When we simulate a circuit which is composed solely by Clifford gates, then the simulation capabilities become the same of a pure stabilizers simulator, allowing us to scale up to hundreds of qubits. As a simple example, let's prepare the GHZ state. " ] }, { "cell_type": "code", "execution_count": 60, "id": "af9d9771", "metadata": {}, "outputs": [], "source": [ "def ghz_circuit(nqubits):\n", " \"\"\"Prepare the GHZ circuit.\"\"\"\n", " circ = Circuit(nqubits)\n", " circ.add(gates.H(0))\n", " for q in range(nqubits - 1):\n", " circ.add(gates.CNOT(q, q + 1))\n", " return circ" ] }, { "cell_type": "code", "execution_count": 68, "id": "2f109d37", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 2 µs, sys: 1 µs, total: 3 µs\n", "Wall time: 4.29 µs\n", "1.0\n" ] } ], "source": [ "# Let's compute the expval of \"ZZZ....ZZZ\" for 500 qubits\n", "%time\n", "ghz_qubits = 500\n", "\n", "ghz_surr = HSMPO(ghz_circuit(ghz_qubits)) \n", "ghz_expval = ghz_surr.expectation(\"Z\" * ghz_qubits)\n", "print(ghz_expval)" ] }, { "cell_type": "markdown", "id": "52136fcd", "metadata": {}, "source": [ "In summary, mpstab offers its maximum benefit in regimes where we have a good amount of Clifford gates or a reasonable amount of entanglement stored in our final MPO. " ] }, { "cell_type": "markdown", "id": "253ca795", "metadata": {}, "source": [] } ], "metadata": { "kernelspec": { "display_name": "tac (3.12.12)", "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.12" } }, "nbformat": 4, "nbformat_minor": 5 }