{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "V4zfNSMd0zwZ" }, "source": [ "##### Copyright 2020 The OpenFermion Developers" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "cellView": "form", "execution": { "iopub.execute_input": "2025-05-10T09:45:22.551888Z", "iopub.status.busy": "2025-05-10T09:45:22.551312Z", "iopub.status.idle": "2025-05-10T09:45:22.555802Z", "shell.execute_reply": "2025-05-10T09:45:22.555161Z" }, "id": "lgoZIZsn02PV" }, "outputs": [], "source": [ "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", "#\n", "# https://www.apache.org/licenses/LICENSE-2.0\n", "#\n", "# Unless required by applicable law or agreed to in writing, software\n", "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", "# See the License for the specific language governing permissions and\n", "# limitations under the License." ] }, { "cell_type": "markdown", "metadata": { "id": "cIiW50gV07qV" }, "source": [ "# The Jordan-Wigner and Bravyi-Kitaev Transforms" ] }, { "cell_type": "markdown", "metadata": { "id": "dsmK__RC08h-" }, "source": [ "\n", " \n", " \n", " \n", " \n", "
\n", " View on QuantumAI\n", " \n", " Run in Google Colab\n", " \n", " View source on GitHub\n", " \n", " Download notebook\n", "
" ] }, { "cell_type": "markdown", "metadata": { "id": "klChkhbu1GtK" }, "source": [ "## Setup\n", "\n", "Install the OpenFermion package:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2025-05-10T09:45:22.558796Z", "iopub.status.busy": "2025-05-10T09:45:22.558522Z", "iopub.status.idle": "2025-05-10T09:45:43.349584Z", "shell.execute_reply": "2025-05-10T09:45:43.348444Z" }, "id": "5cbe6b680387" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Collecting openfermion\r\n", " Cloning https://github.com/quantumlib/OpenFermion.git (to revision master) to /tmpfs/tmp/pip-install-9u97iq7u/openfermion_89d286b7b04f4ab0b6cba3810da7bf73\r\n", " Running command git clone --filter=blob:none --quiet https://github.com/quantumlib/OpenFermion.git /tmpfs/tmp/pip-install-9u97iq7u/openfermion_89d286b7b04f4ab0b6cba3810da7bf73\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " Resolved https://github.com/quantumlib/OpenFermion.git to commit 9423933ed05a150f9681e9a1e9cac40e29c81ead\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " Installing build dependencies ... \u001b[?25l-" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b \b\\" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b \bdone\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\u001b[?25h Getting requirements to build wheel ... \u001b[?25l-" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b \bdone\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\u001b[?25h Preparing metadata (pyproject.toml) ... \u001b[?25l-" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b \bdone\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\u001b[?25hCollecting cirq-core (from openfermion)\r\n", " Using cached cirq_core-1.5.0-py3-none-any.whl.metadata (4.9 kB)\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Collecting deprecation (from openfermion)\r\n", " Using cached deprecation-2.1.0-py2.py3-none-any.whl.metadata (4.6 kB)\r\n", "Requirement already satisfied: h5py>=3.10.0 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from openfermion) (3.13.0)\r\n", "Requirement already satisfied: networkx in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from openfermion) (3.4.2)\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Collecting numpy<2.0,>=1.24 (from openfermion)\r\n", " Using cached numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Collecting pubchempy (from openfermion)\r\n", " Using cached pubchempy-1.0.4-py3-none-any.whl\r\n", "Requirement already satisfied: requests~=2.32.2 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from openfermion) (2.32.3)\r\n", "Requirement already satisfied: scipy~=1.15 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from openfermion) (1.15.3)\r\n", "Collecting sympy (from openfermion)\r\n", " Using cached sympy-1.14.0-py3-none-any.whl.metadata (12 kB)\r\n", "Requirement already satisfied: charset-normalizer<4,>=2 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from requests~=2.32.2->openfermion) (3.4.2)\r\n", "Requirement already satisfied: idna<4,>=2.5 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from requests~=2.32.2->openfermion) (3.10)\r\n", "Requirement already satisfied: urllib3<3,>=1.21.1 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from requests~=2.32.2->openfermion) (2.4.0)\r\n", "Requirement already satisfied: certifi>=2017.4.17 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from requests~=2.32.2->openfermion) (2025.4.26)\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: attrs>=21.3.0 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from cirq-core->openfermion) (25.3.0)\r\n", "Collecting duet>=0.2.8 (from cirq-core->openfermion)\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " Using cached duet-0.2.9-py3-none-any.whl.metadata (2.3 kB)\r\n", "Requirement already satisfied: matplotlib~=3.7 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from cirq-core->openfermion) (3.10.3)\r\n", "Requirement already satisfied: pandas~=2.0 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from cirq-core->openfermion) (2.2.3)\r\n", "Collecting sortedcontainers~=2.0 (from cirq-core->openfermion)\r\n", " Using cached sortedcontainers-2.4.0-py2.py3-none-any.whl.metadata (10 kB)\r\n", "Requirement already satisfied: typing_extensions>=4.2 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from cirq-core->openfermion) (4.13.2)\r\n", "Requirement already satisfied: tqdm>=4.12 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from cirq-core->openfermion) (4.67.1)\r\n", "Requirement already satisfied: contourpy>=1.0.1 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from matplotlib~=3.7->cirq-core->openfermion) (1.3.2)\r\n", "Requirement already satisfied: cycler>=0.10 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from matplotlib~=3.7->cirq-core->openfermion) (0.12.1)\r\n", "Requirement already satisfied: fonttools>=4.22.0 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from matplotlib~=3.7->cirq-core->openfermion) (4.57.0)\r\n", "Requirement already satisfied: kiwisolver>=1.3.1 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from matplotlib~=3.7->cirq-core->openfermion) (1.4.8)\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: packaging>=20.0 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from matplotlib~=3.7->cirq-core->openfermion) (25.0)\r\n", "Requirement already satisfied: pillow>=8 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from matplotlib~=3.7->cirq-core->openfermion) (11.2.1)\r\n", "Requirement already satisfied: pyparsing>=2.3.1 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from matplotlib~=3.7->cirq-core->openfermion) (3.2.3)\r\n", "Requirement already satisfied: python-dateutil>=2.7 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from matplotlib~=3.7->cirq-core->openfermion) (2.9.0.post0)\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: pytz>=2020.1 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from pandas~=2.0->cirq-core->openfermion) (2025.2)\r\n", "Requirement already satisfied: tzdata>=2022.7 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from pandas~=2.0->cirq-core->openfermion) (2025.2)\r\n", "Requirement already satisfied: six>=1.5 in /tmpfs/src/tf_docs_env/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib~=3.7->cirq-core->openfermion) (1.17.0)\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Collecting mpmath<1.4,>=1.1.0 (from sympy->openfermion)\r\n", " Using cached mpmath-1.3.0-py3-none-any.whl.metadata (8.6 kB)\r\n", "Using cached numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (18.2 MB)\r\n", "Using cached cirq_core-1.5.0-py3-none-any.whl (2.0 MB)\r\n", "Using cached sortedcontainers-2.4.0-py2.py3-none-any.whl (29 kB)\r\n", "Using cached duet-0.2.9-py3-none-any.whl (29 kB)\r\n", "Using cached deprecation-2.1.0-py2.py3-none-any.whl (11 kB)\r\n", "Using cached sympy-1.14.0-py3-none-any.whl (6.3 MB)\r\n", "Using cached mpmath-1.3.0-py3-none-any.whl (536 kB)\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Building wheels for collected packages: openfermion\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " Building wheel for openfermion (pyproject.toml) ... \u001b[?25l-" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b \b\\" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b \b|" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b \b/" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b \b-" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b \b\\" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b \b|" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b \bdone\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\u001b[?25h Created wheel for openfermion: filename=openfermion-1.7.1.dev0-py3-none-any.whl size=44717459 sha256=05ce760dfaa1744e9078691f773f2c61f495abeb36014e7242b27889e4469424\r\n", " Stored in directory: /tmpfs/tmp/pip-ephem-wheel-cache-qu8c4msg/wheels/7a/93/a6/5746203c4afd3b6e8d40a4edb587d608c51290286c5680f422\r\n", "Successfully built openfermion\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Installing collected packages: sortedcontainers, pubchempy, mpmath, sympy, numpy, duet, deprecation, cirq-core, openfermion\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " Attempting uninstall: numpy\r\n", " Found existing installation: numpy 2.1.3\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " Uninstalling numpy-2.1.3:\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " Successfully uninstalled numpy-2.1.3\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Successfully installed cirq-core-1.5.0 deprecation-2.1.0 duet-0.2.9 mpmath-1.3.0 numpy-1.26.4 openfermion-1.7.1.dev0 pubchempy-1.0.4 sortedcontainers-2.4.0 sympy-1.14.0\r\n" ] } ], "source": [ "try:\n", " import openfermion\n", "except ImportError:\n", " !pip install git+https://github.com/quantumlib/OpenFermion.git@master#egg=openfermion" ] }, { "cell_type": "markdown", "metadata": { "id": "87a7d8066173" }, "source": [ "## Ladder operators and the canonical anticommutation relations\n", "\n", "A system of $N$ fermionic modes is\n", "described by a set of fermionic *annihilation operators*\n", "$\\{a_p\\}_{p=0}^{N-1}$ satisfying the *canonical anticommutation relations*\n", "$$\\begin{align}\n", " \\{a_p, a_q\\} &= 0, \\label{eq:car1} \\\\\n", " \\{a_p, a^\\dagger_q\\} &= \\delta_{pq}, \\label{eq:car2}\n", " \\end{align}$$ where $\\{A, B\\} := AB + BA$. The adjoint\n", "$a^\\dagger_p$ of an annihilation operator $a_p$ is called a *creation\n", "operator*, and we refer to creation and annihilation operators as\n", "fermionic *ladder operators*.\n", "In a finite-dimensional vector space the anticommutation relations have the following consequences:\n", "\n", "- The operators $\\{a^\\dagger_p a_p\\}_{p=0}^{N-1}$ commute with each\n", " other and have eigenvalues 0 and 1. These are called the *occupation\n", " number operators*.\n", "\n", "- There is a normalized vector $\\lvert{\\text{vac}}\\rangle$, called the *vacuum\n", " state*, which is a mutual 0-eigenvector of all\n", " the $a^\\dagger_p a_p$.\n", "\n", "- If $\\lvert{\\psi}\\rangle$ is a 0-eigenvector of $a_p^\\dagger a_p$, then\n", " $a_p^\\dagger\\lvert{\\psi}\\rangle$ is a 1-eigenvector of $a_p^\\dagger a_p$.\n", " This explains why we say that $a_p^\\dagger$ creates a fermion in\n", " mode $p$.\n", "\n", "- If $\\lvert{\\psi}\\rangle$ is a 1-eigenvector of $a_p^\\dagger a_p$, then\n", " $a_p\\lvert{\\psi}\\rangle$ is a 0-eigenvector of $a_p^\\dagger a_p$. This\n", " explains why we say that $a_p$ annihilates a fermion in mode $p$.\n", "\n", "- $a_p^2 = 0$ for all $p$. One cannot create or annihilate a fermion\n", " in the same mode twice.\n", " \n", "- The set of $2^N$ vectors\n", " $$\\lvert n_0, \\ldots, n_{N-1} \\rangle :=\n", " (a^\\dagger_0)^{n_0} \\cdots (a^\\dagger_{N-1})^{n_{N-1}} \\lvert{\\text{vac}}\\rangle,\n", " \\qquad n_0, \\ldots, n_{N-1} \\in \\{0, 1\\}$$\n", " are orthonormal. We can assume they form a basis for the entire vector space.\n", " \n", "- The annihilation operators $a_p$ act on this basis as follows:\n", " $$\\begin{aligned} a_p \\lvert n_0, \\ldots, n_{p-1}, 1, n_{p+1}, \\ldots, n_{N-1} \\rangle &= (-1)^{\\sum_{q=0}^{p-1} n_q} \\lvert n_0, \\ldots, n_{p-1}, 0, n_{p+1}, \\ldots, n_{N-1} \\rangle \\,, \\\\ a_p \\lvert n_0, \\ldots, n_{p-1}, 0, n_{p+1}, \\ldots, n_{N-1} \\rangle &= 0 \\,.\\end{aligned}$$\n", " \n", "See [here](https://futureofmatter.com/assets/fermions_and_jordan_wigner.pdf) for a derivation and discussion of these\n", "consequences.\n", "\n", "## Mapping fermions to qubits with transforms\n", "\n", "To simulate a system of fermions on a quantum computer, we must choose a representation of the ladder operators on the Hilbert space of the qubits. In other words, we must designate a set of qubit operators (matrices) which satisfy the canonical anticommutation relations. Qubit operators are written in terms of the Pauli matrices $X$, $Y$, and $Z$. In OpenFermion a representation is specified by a transform function which maps fermionic operators (typically instances of FermionOperator) to qubit operators (instances of QubitOperator). In this demo we will discuss the Jordan-Wigner and Bravyi-Kitaev transforms, which are implemented by the functions `jordan_wigner` and `bravyi_kitaev`.\n", "\n", "### The Jordan-Wigner Transform\n", "Under the Jordan-Wigner Transform (JWT), the annihilation operators are mapped to qubit operators as follows:\n", "$$\\begin{aligned}\n", " a_p &\\mapsto \\frac{1}{2} (X_p + \\mathrm{i}Y_p) Z_1 \\cdots Z_{p - 1} \\\\\n", " &= (\\lvert{0}\\rangle\\langle{1}\\rvert)_p Z_1 \\cdots Z_{p - 1} \\\\\n", " &=: \\tilde{a}_p.\n", "\\end{aligned}$$\n", "\n", "This operator has the following action on a computational basis vector\n", "$\\lvert z_0, \\ldots, z_{N-1} \\rangle$:\n", "$$\\begin{aligned}\n", " \\tilde{a}_p \\lvert z_0 \\ldots, z_{p-1}, 1, z_{p+1}, \\ldots, z_{N-1} \\rangle &=\n", " (-1)^{\\sum_{q=0}^{p-1} z_q} \\lvert z_0 \\ldots, z_{p-1}, 0, z_{p+1}, \\ldots, z_{N-1} \\rangle \\\\\n", " \\tilde{a}_p \\lvert z_0 \\ldots, z_{p-1}, 0, z_{p+1}, \\ldots, z_{N-1} \\rangle &= 0.\n", " \\end{aligned}$$\n", "\n", "Note that $\\lvert n_0, \\ldots, n_{N-1} \\rangle$ is a basis vector in the Hilbert space of fermions, while $\\lvert z_0, \\ldots, z_{N-1} \\rangle$ is a basis vector in the Hilbert space of qubits. Similarly, in OpenFermion $a_p$ is a FermionOperator while $\\tilde{a}_p$ is a QubitOperator.\n", "\n", "Let's instantiate some FermionOperators, map them to QubitOperators using the JWT, and check that the resulting operators satisfy the expected relations." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2025-05-10T09:45:43.353516Z", "iopub.status.busy": "2025-05-10T09:45:43.353196Z", "iopub.status.idle": "2025-05-10T09:45:45.309113Z", "shell.execute_reply": "2025-05-10T09:45:45.308347Z" }, "id": "acafe41c2734" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "annihilate_2_jw = \n", "0.5 [Z0 Z1 X2] +\n", "0.5j [Z0 Z1 Y2]\n", "\n", "create_2_jw = \n", "0.5 [Z0 Z1 X2] +\n", "-0.5j [Z0 Z1 Y2]\n", "\n", "annihilate_5_jw = \n", "0.5 [Z0 Z1 Z2 Z3 Z4 X5] +\n", "0.5j [Z0 Z1 Z2 Z3 Z4 Y5]\n", "\n", "create_5_jw = \n", "0.5 [Z0 Z1 Z2 Z3 Z4 X5] +\n", "-0.5j [Z0 Z1 Z2 Z3 Z4 Y5]\n", "\n", "num_2_jw = \n", "(0.5+0j) [] +\n", "(-0.5+0j) [Z2]\n", "\n", "num_5_jw = \n", "(0.5+0j) [] +\n", "(-0.5+0j) [Z5]\n" ] } ], "source": [ "from openfermion import *\n", "\n", "# Create some ladder operators\n", "annihilate_2 = FermionOperator('2')\n", "create_2 = FermionOperator('2^')\n", "annihilate_5 = FermionOperator('5')\n", "create_5 = FermionOperator('5^')\n", "\n", "# Construct occupation number operators\n", "num_2 = create_2 * annihilate_2\n", "num_5 = create_5 * annihilate_5\n", "\n", "# Map FermionOperators to QubitOperators using the JWT\n", "annihilate_2_jw = jordan_wigner(annihilate_2)\n", "create_2_jw = jordan_wigner(create_2)\n", "annihilate_5_jw = jordan_wigner(annihilate_5)\n", "create_5_jw = jordan_wigner(create_5)\n", "num_2_jw = jordan_wigner(num_2)\n", "num_5_jw = jordan_wigner(num_5)\n", "\n", "# Create QubitOperator versions of zero and identity\n", "zero = QubitOperator()\n", "identity = QubitOperator(())\n", "\n", "# Check the canonical anticommutation relations\n", "assert anticommutator(annihilate_5_jw, annihilate_2_jw) == zero\n", "assert anticommutator(annihilate_5_jw, annihilate_5_jw) == zero\n", "assert anticommutator(annihilate_5_jw, create_2_jw) == zero\n", "assert anticommutator(annihilate_5_jw, create_5_jw) == identity\n", "\n", "# Check that the occupation number operators commute\n", "assert commutator(num_2_jw, num_5_jw) == zero\n", "\n", "# Print some output\n", "print(\"annihilate_2_jw = \\n{}\".format(annihilate_2_jw))\n", "print('')\n", "print(\"create_2_jw = \\n{}\".format(create_2_jw))\n", "print('')\n", "print(\"annihilate_5_jw = \\n{}\".format(annihilate_5_jw))\n", "print('')\n", "print(\"create_5_jw = \\n{}\".format(create_5_jw))\n", "print('')\n", "print(\"num_2_jw = \\n{}\".format(num_2_jw))\n", "print('')\n", "print(\"num_5_jw = \\n{}\".format(num_5_jw))" ] }, { "cell_type": "markdown", "metadata": { "id": "2281a1a8dafd" }, "source": [ "### The parity transform\n", "\n", "By comparing the action of $\\tilde{a}_p$ on $\\lvert z_0, \\ldots, z_{N-1} \\rangle$ in the JWT with the action of $a_p$ on $\\lvert n_0, \\ldots, n_{N-1} \\rangle$ (described in the first section of this demo), we can see that the JWT is associated with a particular mapping of bitstrings $e: \\{0, 1\\}^N \\to \\{0, 1\\}^N$, namely, the identity map $e(x) = x$. In other words, under the JWT, the fermionic basis vector $\\lvert n_0, \\ldots, n_{N-1} \\rangle$ is represented by the computational basis vector $\\lvert z_0, \\ldots, z_{N-1} \\rangle$, where $z_p = n_p$ for all $p$. We can write this as\n", "$$\\lvert x \\rangle \\mapsto \\lvert e(x) \\rangle,$$\n", "where the vector on the left is fermionic and the vector on the right is qubit. We call the mapping $e$ an *encoder*.\n", "\n", "There are other transforms which are associated with different encoders. To see why we might be interested in these other transforms, observe that under the JWT, $\\tilde{a}_p$ acts not only on qubit $p$ but also on qubits $0, \\ldots, p-1$. This means that fermionic operators with low weight can get mapped to qubit operators with high weight, where by weight we mean the number of modes or qubits an operators acts on. There are some disadvantages to having high-weight operators; for instance, they may require more gates to simulate and are more expensive to measure on some near-term hardware platforms. In the worst case, the annihilation operator on the last mode will map to an operator which acts on all the qubits. To emphasize this point let's apply the JWT to the annihilation operator on mode 99:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2025-05-10T09:45:45.312708Z", "iopub.status.busy": "2025-05-10T09:45:45.311755Z", "iopub.status.idle": "2025-05-10T09:45:45.316832Z", "shell.execute_reply": "2025-05-10T09:45:45.316129Z" }, "id": "4ada8be6a86c" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.5 [Z0 Z1 Z2 Z3 Z4 Z5 Z6 Z7 Z8 Z9 Z10 Z11 Z12 Z13 Z14 Z15 Z16 Z17 Z18 Z19 Z20 Z21 Z22 Z23 Z24 Z25 Z26 Z27 Z28 Z29 Z30 Z31 Z32 Z33 Z34 Z35 Z36 Z37 Z38 Z39 Z40 Z41 Z42 Z43 Z44 Z45 Z46 Z47 Z48 Z49 Z50 Z51 Z52 Z53 Z54 Z55 Z56 Z57 Z58 Z59 Z60 Z61 Z62 Z63 Z64 Z65 Z66 Z67 Z68 Z69 Z70 Z71 Z72 Z73 Z74 Z75 Z76 Z77 Z78 Z79 Z80 Z81 Z82 Z83 Z84 Z85 Z86 Z87 Z88 Z89 Z90 Z91 Z92 Z93 Z94 Z95 Z96 Z97 Z98 X99] +\n", "0.5j [Z0 Z1 Z2 Z3 Z4 Z5 Z6 Z7 Z8 Z9 Z10 Z11 Z12 Z13 Z14 Z15 Z16 Z17 Z18 Z19 Z20 Z21 Z22 Z23 Z24 Z25 Z26 Z27 Z28 Z29 Z30 Z31 Z32 Z33 Z34 Z35 Z36 Z37 Z38 Z39 Z40 Z41 Z42 Z43 Z44 Z45 Z46 Z47 Z48 Z49 Z50 Z51 Z52 Z53 Z54 Z55 Z56 Z57 Z58 Z59 Z60 Z61 Z62 Z63 Z64 Z65 Z66 Z67 Z68 Z69 Z70 Z71 Z72 Z73 Z74 Z75 Z76 Z77 Z78 Z79 Z80 Z81 Z82 Z83 Z84 Z85 Z86 Z87 Z88 Z89 Z90 Z91 Z92 Z93 Z94 Z95 Z96 Z97 Z98 Y99]\n" ] } ], "source": [ "print(jordan_wigner(FermionOperator('99')))" ] }, { "cell_type": "markdown", "metadata": { "id": "aa41f1a05739" }, "source": [ "The purpose of the string of Pauli $Z$'s is to introduce the phase factor $(-1)^{\\sum_{q=0}^{p-1} n_q}$ when acting on a computational basis state; when $e$ is the identity encoder, the modulo-2 sum $\\sum_{q=0}^{p-1} n_q$ is computed as $\\sum_{q=0}^{p-1} z_q$, which requires reading $p$ bits and leads to a Pauli $Z$ string with weight $p$. A simple solution to this problem is to consider instead the encoder defined by\n", "$$e(x)_p = \\sum_{q=0}^p x_q \\quad (\\text{mod 2}),$$\n", "which is associated with the mapping of basis vectors\n", "$\\lvert n_0, \\ldots, n_{N-1} \\rangle \\mapsto \\lvert z_0, \\ldots, z_{N-1} \\rangle,$\n", "where $z_p = \\sum_{q=0}^p n_q$ (again addition is modulo 2). With this encoding, we can compute the sum $\\sum_{q=0}^{p-1} n_q$ by reading just one bit because this is the value stored by $z_{p-1}$. The associated transform is called the parity transform because the $p$-th qubit is storing the parity (modulo-2 sum) of modes $0, \\ldots, p$. Under the parity transform, annihilation operators are mapped as follows:\n", "$$\\begin{aligned}\n", " a_p &\\mapsto \\frac{1}{2} (X_p Z_{p - 1} + \\mathrm{i}Y_p) X_{p + 1} \\cdots X_{N} \\\\\n", " &= \\frac{1}{4} [(X_p + \\mathrm{i} Y_p) (I + Z_{p - 1}) -\n", " (X_p - \\mathrm{i} Y_p) (I - Z_{p - 1})]\n", " X_{p + 1} \\cdots X_{N} \\\\\n", " &= [(\\lvert{0}\\rangle\\langle{1}\\rvert)_p (\\lvert{0}\\rangle\\langle{0}\\rvert)_{p - 1} -\n", " (\\lvert{0}\\rangle\\langle{1}\\rvert)_p (\\lvert{1}\\rangle\\langle{1}\\rvert)_{p - 1}]\n", " X_{p + 1} \\cdots X_{N} \\\\\n", "\\end{aligned}$$\n", "\n", "The term in brackets in the last line means \"if $z_p = n_p$ then annihilate in mode $p$; otherwise, create in mode $p$ and attach a minus sign\". The value stored by $z_{p-1}$ contains the information needed to determine whether a minus sign should be attached or not. However, now there is a string of Pauli $X$'s acting on modes $p+1, \\ldots, N-1$ and hence using the parity transform also yields operators with high weight. These Pauli $X$'s perform the necessary update to $z_{p+1}, \\ldots, z_{N-1}$ which is needed if the value of $n_{p}$ changes. In the worst case, the annihilation operator on the first mode will map to an operator which acts on all the qubits.\n", "\n", "Since the parity transform does not offer any advantages over the JWT, OpenFermion does not include a standalone function to perform it. However, there is functionality for defining new transforms by specifying an encoder and decoder pair, also known as a binary code (in our examples the decoder is simply the inverse mapping), and the binary code which defines the parity transform is included in the library as an example. See [Lowering qubit requirements using binary codes](./binary_code_transforms_demo.ipynb) for a demonstration of this functionality and how it can be used to reduce the qubit resources required for certain applications.\n", "\n", "Let's use this functionality to map our previously instantiated FermionOperators to QubitOperators using the parity transform with 10 total modes and check that the resulting operators satisfy the expected relations." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2025-05-10T09:45:45.319785Z", "iopub.status.busy": "2025-05-10T09:45:45.319165Z", "iopub.status.idle": "2025-05-10T09:45:45.334134Z", "shell.execute_reply": "2025-05-10T09:45:45.333445Z" }, "id": "a38415103b77" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "annihilate_2_parity = \n", "0.5 [Z1 X2 X3 X4 X5 X6 X7 X8 X9] +\n", "0.5j [Y2 X3 X4 X5 X6 X7 X8 X9]\n", "\n", "create_2_parity = \n", "0.5 [Z1 X2 X3 X4 X5 X6 X7 X8 X9] +\n", "(-0-0.5j) [Y2 X3 X4 X5 X6 X7 X8 X9]\n", "\n", "annihilate_5_parity = \n", "0.5 [Z4 X5 X6 X7 X8 X9] +\n", "0.5j [Y5 X6 X7 X8 X9]\n", "\n", "create_5_parity = \n", "0.5 [Z4 X5 X6 X7 X8 X9] +\n", "(-0-0.5j) [Y5 X6 X7 X8 X9]\n", "\n", "num_2_parity = \n", "0.5 [] +\n", "-0.5 [Z1 Z2]\n", "\n", "num_5_parity = \n", "0.5 [] +\n", "-0.5 [Z4 Z5]\n" ] } ], "source": [ "# Set the number of modes in the system\n", "n_modes = 10\n", "\n", "# Define a function to perform the parity transform\n", "def parity(fermion_operator, n_modes):\n", " return binary_code_transform(fermion_operator, parity_code(n_modes))\n", "\n", "# Map FermionOperators to QubitOperators using the parity transform\n", "annihilate_2_parity = parity(annihilate_2, n_modes)\n", "create_2_parity = parity(create_2, n_modes)\n", "annihilate_5_parity = parity(annihilate_5, n_modes)\n", "create_5_parity = parity(create_5, n_modes)\n", "num_2_parity = parity(num_2, n_modes)\n", "num_5_parity = parity(num_5, n_modes)\n", "\n", "# Check the canonical anticommutation relations\n", "assert anticommutator(annihilate_5_parity, annihilate_2_parity) == zero\n", "assert anticommutator(annihilate_5_parity, annihilate_5_parity) == zero\n", "assert anticommutator(annihilate_5_parity, create_2_parity) == zero\n", "assert anticommutator(annihilate_5_parity, create_5_parity) == identity\n", "\n", "# Check that the occupation number operators commute\n", "assert commutator(num_2_parity, num_5_parity) == zero\n", "\n", "# Print some output\n", "print(\"annihilate_2_parity = \\n{}\".format(annihilate_2_parity))\n", "print('')\n", "print(\"create_2_parity = \\n{}\".format(create_2_parity))\n", "print('')\n", "print(\"annihilate_5_parity = \\n{}\".format(annihilate_5_parity))\n", "print('')\n", "print(\"create_5_parity = \\n{}\".format(create_5_parity))\n", "print('')\n", "print(\"num_2_parity = \\n{}\".format(num_2_parity))\n", "print('')\n", "print(\"num_5_parity = \\n{}\".format(num_5_parity))" ] }, { "cell_type": "markdown", "metadata": { "id": "a1025a11bdbd" }, "source": [ "Now let's map one of the FermionOperators again but with the total number of modes set to 100." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2025-05-10T09:45:45.337242Z", "iopub.status.busy": "2025-05-10T09:45:45.336618Z", "iopub.status.idle": "2025-05-10T09:45:45.350616Z", "shell.execute_reply": "2025-05-10T09:45:45.349904Z" }, "id": "5e702e7340a1" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.5 [Z1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X27 X28 X29 X30 X31 X32 X33 X34 X35 X36 X37 X38 X39 X40 X41 X42 X43 X44 X45 X46 X47 X48 X49 X50 X51 X52 X53 X54 X55 X56 X57 X58 X59 X60 X61 X62 X63 X64 X65 X66 X67 X68 X69 X70 X71 X72 X73 X74 X75 X76 X77 X78 X79 X80 X81 X82 X83 X84 X85 X86 X87 X88 X89 X90 X91 X92 X93 X94 X95 X96 X97 X98 X99] +\n", "0.5j [Y2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X27 X28 X29 X30 X31 X32 X33 X34 X35 X36 X37 X38 X39 X40 X41 X42 X43 X44 X45 X46 X47 X48 X49 X50 X51 X52 X53 X54 X55 X56 X57 X58 X59 X60 X61 X62 X63 X64 X65 X66 X67 X68 X69 X70 X71 X72 X73 X74 X75 X76 X77 X78 X79 X80 X81 X82 X83 X84 X85 X86 X87 X88 X89 X90 X91 X92 X93 X94 X95 X96 X97 X98 X99]\n" ] } ], "source": [ "print(parity(annihilate_2, 100))" ] }, { "cell_type": "markdown", "metadata": { "id": "d82be6e57032" }, "source": [ "Note that with the JWT, it is not necessary to specify the total number of modes in the system because $\\tilde{a}_p$ only acts on qubits $0, \\ldots, p$ and not any higher ones." ] }, { "cell_type": "markdown", "metadata": { "id": "c6e4bc777e97" }, "source": [ "### The Bravyi-Kitaev transform\n", "\n", "The discussion above suggests that we can think of the action of a transformed annihilation operator $\\tilde{a}_p$ on a computational basis vector $\\lvert z \\rangle$ as a 4-step classical algorithm:\n", "1. Check if $n_p = 0$. If so, then output the zero vector. Otherwise,\n", "2. Update the bit stored by $z_p$.\n", "3. Update the rest of the bits $z_q$, $q \\neq p$.\n", "4. Multiply by the parity $\\sum_{q=0}^{p-1} n_p$.\n", "\n", "Under the JWT, Steps 1, 2, and 3 are represented by the operator $(\\lvert{0}\\rangle\\langle{1}\\rvert)_p$ and Step 4 is accomplished by the operator $Z_{0} \\cdots Z_{p-1}$ (Step 3 actually requires no action).\n", "Under the parity transform, Steps 1, 2, and 4 are represented by the operator\n", "$(\\lvert{0}\\rangle\\langle{1}\\rvert)_p (\\lvert{0}\\rangle\\langle{0}\\rvert)_{p - 1} -\n", "(\\lvert{0}\\rangle\\langle{1}\\rvert)_p (\\lvert{1}\\rangle\\langle{1}\\rvert)_{p - 1}$ and Step 3 is accomplished by the operator $X_{p+1} \\cdots X_{N-1}$.\n", "\n", "To obtain a simpler description of these and other transforms (with an aim at generalizing), it is better to put aside the ladder operators and work with an alternative set of $2N$ operators defined by\n", "$$c_p = a_p + a_p^\\dagger\\,, \\qquad d_p = -\\mathrm{i} (a_p - a_p^\\dagger)\\,.$$\n", "These operators are known as Majorana operators. Note that if we describe how Majorana operators should be transformed, then we also know how the annihilation operators should be transformed, since\n", "$$a_p = \\frac{1}{2} (c_p + \\mathrm{i} d_p).$$\n", "\n", "For simplicity, let's consider just the $c_p$; the $d_p$ are treated similarly. The action of $c_p$ on a fermionic basis vector is given by\n", "$$c_p \\lvert n_0, \\ldots, n_{p-1}, n_p, n_{p+1}, \\ldots, n_{N-1} \\rangle =\n", "(-1)^{\\sum_{q=0}^{p-1} n_q} \\lvert n_0, \\ldots, n_{p-1}, 1 - n_p, n_{p+1}, \\ldots, n_{N-1} \\rangle$$\n", "\n", "In words, $c_p$ flips the occupation of mode $p$ and multiplies by the ever-present parity factor. If we transform $c_p$ to a qubit operator $\\tilde{c}_p$, we should be able to describe the action of $\\tilde{c}_p$ on a computational basis vector $\\lvert z \\rangle$ with a 2-step classical algorithm:\n", "1. Update the string $z$ to a new string $z'$.\n", "2. Multiply by the parity $\\sum_{q=0}^{p-1} n_q$.\n", "\n", "Step 1 amounts to flipping some bits, so it will be performed by some Pauli $X$'s, and Step 2 will be performed by some Pauli $Z$'s. So $\\tilde{c}_p$ should take the form\n", "$$\\tilde{c}_p = X_{U(p)} Z_{P(p - 1)},$$\n", "where $U(j)$ is the set of bits that need to be updated upon flipping $n_j$, and $P(j)$ is a set of bits that stores the sum $\\sum_{q=0}^{j} n_q$ (let's define $P(-1)$ to be the empty set). Let's see how this looks under the JWT and parity transforms." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2025-05-10T09:45:45.353690Z", "iopub.status.busy": "2025-05-10T09:45:45.353223Z", "iopub.status.idle": "2025-05-10T09:45:45.359926Z", "shell.execute_reply": "2025-05-10T09:45:45.359228Z" }, "id": "015dfec251d6" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "c_5_jw = \n", "1.0 [Z0 Z1 Z2 Z3 Z4 X5]\n", "\n", "c_5_parity = \n", "1.0 [Z4 X5 X6 X7 X8 X9]\n" ] } ], "source": [ "# Create a Majorana operator from our existing operators\n", "c_5 = annihilate_5 + create_5\n", "\n", "# Set the number of modes (required for the parity transform)\n", "n_modes = 10\n", "\n", "# Transform the Majorana operator to a QubitOperator in two different ways\n", "c_5_jw = jordan_wigner(c_5)\n", "c_5_parity = parity(c_5, n_modes)\n", "\n", "# Print some output\n", "print(\"c_5_jw = \\n{}\".format(c_5_jw))\n", "print('')\n", "print(\"c_5_parity = \\n{}\".format(c_5_parity))" ] }, { "cell_type": "markdown", "metadata": { "id": "46d3051b2d8a" }, "source": [ "For the JWT, $U(j) = \\{j\\}$ and $P(j) = \\{0, \\ldots, j\\}$, whereas for the parity transform, $U(j) = \\{j, \\ldots, N-1\\}$ and $P(j) = \\{j\\}$. The size of these sets can be as large as $N$, the total number of modes. These sets are determined by the encoding function $e$.\n", "\n", "It is possible to pick a clever encoder with the property that these sets have size $O(\\log N)$. The corresponding transform will map annihilation operators to qubit operators with weight $O(\\log N)$, which is much smaller than the $\\Omega(N)$ weight associated with the JWT and parity transforms. This fact was noticed by [Bravyi and Kitaev](https://arxiv.org/abs/quant-ph/0003137), and later [Havlíček and others](https://arxiv.org/abs/1701.07072) pointed out that the encoder which achieves this is implemented by a classical data structure called a Fenwick tree. The transforms described in these two papers actually correspond to different variants of the Fenwick tree data structure and give different results when the total number of modes is not a power of 2. OpenFermion implements the one from the first paper as `bravyi_kitaev` and the one from the second paper as `bravyi_kitaev_tree`. Generally, the first one (`bravyi_kitaev`) is preferred because it results in operators with lower weight and is faster to compute.\n", "\n", "Let's transform our previously instantiated Majorana operator using the Bravyi-Kitaev transform." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2025-05-10T09:45:45.363040Z", "iopub.status.busy": "2025-05-10T09:45:45.362349Z", "iopub.status.idle": "2025-05-10T09:45:45.366504Z", "shell.execute_reply": "2025-05-10T09:45:45.365856Z" }, "id": "194346f319ab" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "c_5_bk = \n", "1.0 [Z3 Z4 X5 X7]\n" ] } ], "source": [ "c_5_bk = bravyi_kitaev(c_5, n_modes)\n", "print(\"c_5_bk = \\n{}\".format(c_5_bk))" ] }, { "cell_type": "markdown", "metadata": { "id": "2769a6ac2001" }, "source": [ "The advantage of the Bravyi-Kitaev transform is not apparent in a system with so few modes. Let's look at a system with 100 modes." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2025-05-10T09:45:45.369032Z", "iopub.status.busy": "2025-05-10T09:45:45.368786Z", "iopub.status.idle": "2025-05-10T09:45:45.405482Z", "shell.execute_reply": "2025-05-10T09:45:45.404718Z" }, "id": "396cba1df4f1" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Jordan-Wigner\n", "-------------\n", "c_17_jw = \n", "1.0 [Z0 Z1 Z2 Z3 Z4 Z5 Z6 Z7 Z8 Z9 Z10 Z11 Z12 Z13 Z14 Z15 Z16 X17]\n", "\n", "c_50_jw = \n", "1.0 [Z0 Z1 Z2 Z3 Z4 Z5 Z6 Z7 Z8 Z9 Z10 Z11 Z12 Z13 Z14 Z15 Z16 Z17 Z18 Z19 Z20 Z21 Z22 Z23 Z24 Z25 Z26 Z27 Z28 Z29 Z30 Z31 Z32 Z33 Z34 Z35 Z36 Z37 Z38 Z39 Z40 Z41 Z42 Z43 Z44 Z45 Z46 Z47 Z48 Z49 X50]\n", "\n", "c_73_jw = \n", "1.0 [Z0 Z1 Z2 Z3 Z4 Z5 Z6 Z7 Z8 Z9 Z10 Z11 Z12 Z13 Z14 Z15 Z16 Z17 Z18 Z19 Z20 Z21 Z22 Z23 Z24 Z25 Z26 Z27 Z28 Z29 Z30 Z31 Z32 Z33 Z34 Z35 Z36 Z37 Z38 Z39 Z40 Z41 Z42 Z43 Z44 Z45 Z46 Z47 Z48 Z49 Z50 Z51 Z52 Z53 Z54 Z55 Z56 Z57 Z58 Z59 Z60 Z61 Z62 Z63 Z64 Z65 Z66 Z67 Z68 Z69 Z70 Z71 Z72 X73]\n", "\n", "Parity\n", "------\n", "c_17_parity = \n", "1.0 [Z16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X27 X28 X29 X30 X31 X32 X33 X34 X35 X36 X37 X38 X39 X40 X41 X42 X43 X44 X45 X46 X47 X48 X49 X50 X51 X52 X53 X54 X55 X56 X57 X58 X59 X60 X61 X62 X63 X64 X65 X66 X67 X68 X69 X70 X71 X72 X73 X74 X75 X76 X77 X78 X79 X80 X81 X82 X83 X84 X85 X86 X87 X88 X89 X90 X91 X92 X93 X94 X95 X96 X97 X98 X99]\n", "\n", "c_50_parity = \n", "1.0 [Z49 X50 X51 X52 X53 X54 X55 X56 X57 X58 X59 X60 X61 X62 X63 X64 X65 X66 X67 X68 X69 X70 X71 X72 X73 X74 X75 X76 X77 X78 X79 X80 X81 X82 X83 X84 X85 X86 X87 X88 X89 X90 X91 X92 X93 X94 X95 X96 X97 X98 X99]\n", "\n", "c_73_parity = \n", "1.0 [Z72 X73 X74 X75 X76 X77 X78 X79 X80 X81 X82 X83 X84 X85 X86 X87 X88 X89 X90 X91 X92 X93 X94 X95 X96 X97 X98 X99]\n", "\n", "Bravyi-Kitaev\n", "-------------\n", "c_17_bk = \n", "1.0 [Z15 Z16 X17 X19 X23 X31 X63]\n", "\n", "c_50_bk = \n", "1.0 [Z31 Z47 Z49 X50 X51 X55 X63]\n", "\n", "c_73_bk = \n", "1.0 [Z63 Z71 Z72 X73 X75 X79 X95]\n" ] } ], "source": [ "n_modes = 100\n", "\n", "# Initialize some Majorana operators\n", "c_17 = FermionOperator('[17] + [17^]')\n", "c_50 = FermionOperator('[50] + [50^]')\n", "c_73 = FermionOperator('[73] + [73^]')\n", "\n", "# Map to QubitOperators\n", "c_17_jw = jordan_wigner(c_17)\n", "c_50_jw = jordan_wigner(c_50)\n", "c_73_jw = jordan_wigner(c_73)\n", "c_17_parity = parity(c_17, n_modes)\n", "c_50_parity = parity(c_50, n_modes)\n", "c_73_parity = parity(c_73, n_modes)\n", "c_17_bk = bravyi_kitaev(c_17, n_modes)\n", "c_50_bk = bravyi_kitaev(c_50, n_modes)\n", "c_73_bk = bravyi_kitaev(c_73, n_modes)\n", "\n", "# Print some output\n", "print(\"Jordan-Wigner\\n\"\n", " \"-------------\")\n", "print(\"c_17_jw = \\n{}\".format(c_17_jw))\n", "print('')\n", "print(\"c_50_jw = \\n{}\".format(c_50_jw))\n", "print('')\n", "print(\"c_73_jw = \\n{}\".format(c_73_jw))\n", "print('')\n", "print(\"Parity\\n\"\n", " \"------\")\n", "print(\"c_17_parity = \\n{}\".format(c_17_parity))\n", "print('')\n", "print(\"c_50_parity = \\n{}\".format(c_50_parity))\n", "print('')\n", "print(\"c_73_parity = \\n{}\".format(c_73_parity))\n", "print('')\n", "print(\"Bravyi-Kitaev\\n\"\n", " \"-------------\")\n", "print(\"c_17_bk = \\n{}\".format(c_17_bk))\n", "print('')\n", "print(\"c_50_bk = \\n{}\".format(c_50_bk))\n", "print('')\n", "print(\"c_73_bk = \\n{}\".format(c_73_bk))" ] }, { "cell_type": "markdown", "metadata": { "id": "7391ad24f3f0" }, "source": [ "Now let's go back to a system with 10 modes and check that the Bravyi-Kitaev transformed operators satisfy the expected relations." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2025-05-10T09:45:45.408799Z", "iopub.status.busy": "2025-05-10T09:45:45.408148Z", "iopub.status.idle": "2025-05-10T09:45:45.416188Z", "shell.execute_reply": "2025-05-10T09:45:45.415515Z" }, "id": "1e95c8c945bd" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "annihilate_2_bk = \n", "0.5 [Z1 X2 X3 X7] +\n", "0.5j [Z1 Y2 X3 X7]\n", "\n", "create_2_bk = \n", "0.5 [Z1 X2 X3 X7] +\n", "-0.5j [Z1 Y2 X3 X7]\n", "\n", "annihilate_5_bk = \n", "0.5 [Z3 Z4 X5 X7] +\n", "0.5j [Z3 Y5 X7]\n", "\n", "create_5_bk = \n", "0.5 [Z3 Z4 X5 X7] +\n", "-0.5j [Z3 Y5 X7]\n", "\n", "num_2_bk = \n", "(0.5+0j) [] +\n", "(-0.5+0j) [Z2]\n", "\n", "num_5_bk = \n", "(0.5+0j) [] +\n", "(-0.5+0j) [Z4 Z5]\n" ] } ], "source": [ "# Set the number of modes in the system\n", "n_modes = 10\n", "\n", "# Map FermionOperators to QubitOperators using the Bravyi-Kitaev transform\n", "annihilate_2_bk = bravyi_kitaev(annihilate_2, n_modes)\n", "create_2_bk = bravyi_kitaev(create_2, n_modes)\n", "annihilate_5_bk = bravyi_kitaev(annihilate_5, n_modes)\n", "create_5_bk = bravyi_kitaev(create_5, n_modes)\n", "num_2_bk = bravyi_kitaev(num_2, n_modes)\n", "num_5_bk = bravyi_kitaev(num_5, n_modes)\n", "\n", "# Check the canonical anticommutation relations\n", "assert anticommutator(annihilate_5_bk, annihilate_2_bk) == zero\n", "assert anticommutator(annihilate_5_bk, annihilate_5_bk) == zero\n", "assert anticommutator(annihilate_5_bk, create_2_bk) == zero\n", "assert anticommutator(annihilate_5_bk, create_5_bk) == identity\n", "\n", "# Check that the occupation number operators commute\n", "assert commutator(num_2_bk, num_5_bk) == zero\n", "\n", "# Print some output\n", "print(\"annihilate_2_bk = \\n{}\".format(annihilate_2_bk))\n", "print('')\n", "print(\"create_2_bk = \\n{}\".format(create_2_bk))\n", "print('')\n", "print(\"annihilate_5_bk = \\n{}\".format(annihilate_5_bk))\n", "print('')\n", "print(\"create_5_bk = \\n{}\".format(create_5_bk))\n", "print('')\n", "print(\"num_2_bk = \\n{}\".format(num_2_bk))\n", "print('')\n", "print(\"num_5_bk = \\n{}\".format(num_5_bk))" ] } ], "metadata": { "colab": { "name": "jordan_wigner_and_bravyi_kitaev_transforms.ipynb", "toc_visible": true }, "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.17" } }, "nbformat": 4, "nbformat_minor": 1 }