From 3aa441b4977e6e5ebf1b1c64bf1e19e62a1841de Mon Sep 17 00:00:00 2001 From: augustehirth Date: Mon, 18 Jul 2022 16:57:47 -0700 Subject: [PATCH] Add Observables and PauliStrings (#5750) Add documentation on observables and PauliStrings. @tanujkhattar PTAL. --- docs/_book.yaml | 2 + docs/simulate/_index.yaml | 3 + docs/simulate/pauli_observables.ipynb | 714 ++++++++++++++++++++++++++ 3 files changed, 719 insertions(+) create mode 100644 docs/simulate/pauli_observables.ipynb diff --git a/docs/_book.yaml b/docs/_book.yaml index 78b417f8880..b3f435b3dec 100644 --- a/docs/_book.yaml +++ b/docs/_book.yaml @@ -84,6 +84,8 @@ upper_tabs: path: /cirq/simulate/params - title: "State Histograms" path: /cirq/simulate/state_histograms + - title: "Observables and PauliStrings" + path: /cirq/simulate/pauli_observables - name: "Transform" contents: diff --git a/docs/simulate/_index.yaml b/docs/simulate/_index.yaml index 27bd97b49af..b56c0b10928 100644 --- a/docs/simulate/_index.yaml +++ b/docs/simulate/_index.yaml @@ -21,3 +21,6 @@ landing_page: - heading: State Histograms description: Visualize the results of simulation as a histogram over basis states. path: /cirq/simulate/state_histograms + - heading: Observables and PauliStrings + description: Build and measure observables from sums and products of Pauli operators. + path: /cirq/simulate/pauli_observables diff --git a/docs/simulate/pauli_observables.ipynb b/docs/simulate/pauli_observables.ipynb new file mode 100644 index 00000000000..99988e0cc7e --- /dev/null +++ b/docs/simulate/pauli_observables.ipynb @@ -0,0 +1,714 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "DS9Z5y3JTml-" + }, + "outputs": [], + "source": [ + "# @title Copyright 2022 The Cirq Developers\n", + "# 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": "1br2EqucTqmc" + }, + "source": [ + "# PauliStrings and Observables" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "wvMgYPZ0Ts2C" + }, + "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": "code", + "execution_count": null, + "metadata": { + "id": "Jz-NPA9QpIHQ" + }, + "outputs": [], + "source": [ + "# @title Setup { vertical-output: true, display-mode: \"form\" }\n", + "try:\n", + " import cirq\n", + "except ImportError:\n", + " print(\"installing cirq...\")\n", + " !pip install --quiet cirq\n", + " print(\"installed cirq.\")\n", + " import cirq\n", + "\n", + "import cirq_google\n", + "import sympy\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HzLKE88MUAOf" + }, + "source": [ + "## What are Pauli observables?\n", + "\n", + "Cirq provides the [Pauli operators](https://en.wikipedia.org/wiki/Pauli_matrices){:.external} `X`, `Y` and `Z` as `cirq.X`, `cirq.Y` and `cirq.Z` respectively. Together with the identity operator `cirq.I`, these three operators form a complete basis for the set of all unitary transformations on a single qubit. That is, any quantum circuit on a single qubit can be represented by a linear combination (weighted sum) of the `X`,`Y`,`Z` and `I` operators applied to that qubit. This extends to quantum circuits of any number of qubits in the sense that any multi-qubit quantum circuit that doesn't entangle qubits can be represented by a linear combination of tensor products of Pauli operators.\n", + "\n", + "[Observables](https://en.wikipedia.org/wiki/Observable){:.external} are, in general, some sort of measurable property of a circuit. At its very simplest, this could be whether a qubit measures to be $|0\\rangle$ or $|1\\rangle$ in the standard computational basis. In the Pauli basis, this corresponds to the `Z` observable. In general, this is roughly a way to measure qubit state in a basis other than the computational one, by applying basis-changing operations before measurement. \n", + "\n", + "In Cirq, compositions, linear combinations, and tensor products of Pauli operators are represented with `cirq.PauliString` and `cirq.PauliSum`, which this tutorial will demonstrate next. Fundamentally, these objects are still [Operations](/cirq/bild/operators), and can be added to circuits like any other operation. The second half of this tutorial will cover the second use of `PauliString`s, as observables in measurement. " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LpoOVMXWFQT2" + }, + "source": [ + "## Pauli Operator Representations\n", + "\n", + "Before starting on building `PauliString`s, define: \n", + "1. A tiny function to print an object with its type, to make clear the types being used later\n", + "2. Some Pauli operations that the `PauliString`s and such will be built from. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "N_eA7cYcFQEN" + }, + "outputs": [], + "source": [ + "# A small utility function to print the type and value of any number of arguments.\n", + "def typrint(*xs):\n", + " for x in xs:\n", + " print(type(x), x)\n", + "\n", + "\n", + "# A couple qubits.\n", + "a, b, c = cirq.LineQubit.range(3)\n", + "# A set of Pauli operations to build PauliStrings from.\n", + "Xa = cirq.X(a)\n", + "Xb = cirq.X(b)\n", + "Za = cirq.Z(a)\n", + "Zb = cirq.Z(b)\n", + "\n", + "# Test the typrint function.\n", + "typrint(Xa, Xb, Za, Zb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "gtGuQDbOGMZI" + }, + "source": [ + "Note that all of these are operations applied to qubits of type `cirq.SingleQubitPauliStringGateOperation`. Even when you use `cirq.X`, `cirq.Y` and `cirq.Z` in other places in Cirq, they are still this type, which is representative of the simplest component of a `PauliString`, a single Pauli operation. " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_sarplp9UFF4" + }, + "source": [ + "## `PauliString` construction\n", + "\n", + "An empty `cirq.PauliString` by itself is representative of the identity operation `I`, applied to any and all available qubits. A no-op, where no transformation of any qubits is occuring. \n", + "\n", + "This also means that `PauliString`s only represent combinations of the non-identity operations `cirq.X`, `cirq.Y` and `cirq.Z`. Any `cirq.I` operations added are dropped from a `PauliString`. Additionally, any qubits in the expression that have operations that cancel out to the identity are completely dropped. To reinforce this, `cirq.I` is not a `SingleQubitPauliStringGateOperation`, unlike `X`, `Y` and `Z`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "O58uiVqOUJyM" + }, + "outputs": [], + "source": [ + "# An empty PauliString\n", + "typrint(cirq.PauliString())\n", + "# An equivalently empty PauliString built from an identity operation\n", + "Ia = cirq.I(a)\n", + "typrint(cirq.PauliString(Ia))\n", + "print(cirq.PauliString() == cirq.PauliString(Ia))\n", + "# cirq.I is a PauliString.\n", + "typrint(Ia)\n", + "print(issubclass(Ia.__class__, cirq.PauliString))\n", + "# cirq.I has qubits, but a PauliString drops qubits that are identity.\n", + "print(Ia.qubits)\n", + "print(cirq.PauliString(Ia).qubits)\n", + "# Two consecutive Xa cancel to the identity and are dropped.\n", + "print(cirq.PauliString(Xa, Xa).qubits)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "A92GI4G_Hnl9" + }, + "source": [ + "`cirq.SingleQubitPauliStringGateOperation`s are themselves `PauliString`s, and are representative of one of the Pauli gates applied to some qubit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "hjUu2I_MHnWJ" + }, + "outputs": [], + "source": [ + "typrint(Xa)\n", + "print(issubclass(Xa.__class__, cirq.PauliString))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "SxCamm3EILau" + }, + "source": [ + "Larger `PauliString`s can be built with the `*` operator. Be careful with this operator, as it can be used in three distinct ways: \n", + "1. Scalar Multiplication: `complex * PauliString` produces a `PauliString` with a complex scalar coefficient attached. \n", + "2. Composition: `PauliString(q(a)) * PauliString(q(a))` takes two `PauliString`s that are applied to _the same qubit(s)_ and composes them together by standard matrix multiplication. \n", + "3. Tensor: `PauliString(q(a)) * PauliString(q(b))` takes two `PauliString`s that are applied to _different qubits_ and combines them with the tensor product operation (usually $⨂$)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "0ro8BG-xILJY" + }, + "outputs": [], + "source": [ + "# Complex scalar multiplication.\n", + "typrint((4 + 5j) * Xa)\n", + "# Composition\n", + "typrint(Xa * Xa)\n", + "typrint(Xa * Za)\n", + "# Tensor\n", + "typrint(Xa * Xb)\n", + "typrint(Xa * Zb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jpFLEg8QLMHO" + }, + "source": [ + "In the composition examples, cancellation and [anti-commutation](https://en.wikipedia.org/wiki/Pauli_matrices#(Anti-)Commutation_relations){:.external} occurred according to the properties of the Pauli operators. Any Pauli operator composed with itself cancels into the identity `I`, and any two distinct Pauli operators composed together are equivalent to the third, with a $\\pm 1$ coefficient. \n", + "\n", + "These three uses of the `*` operator fluidly work together in larger expressions. Interestingly, due to the associativity of these operators, it doesn't matter where each term is **as long as the operations on the same qubit are applied in the same order**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "a8ZBNF_HLL31" + }, + "outputs": [], + "source": [ + "# The two PauliStrings from before\n", + "typrint(Xa * Za)\n", + "typrint(Xa * Zb)\n", + "# Correct order of operations on qubit a, which merge to a single -Z operation.\n", + "typrint(Xa * Za * Xa)\n", + "# Combined together with a coefficient.\n", + "typrint((3 + 6j) * (Xa * Za) * (Xa * Zb))\n", + "# The same PauliString with different ordering and a split coefficient.\n", + "typrint(Xa * Zb * Za * (3 + 0j) * Xa * (1 + 2j))\n", + "# A different PauliString where the terms applied to qubit a have changed order.\n", + "typrint(Za * Zb * Xa * (3 + 0j) * Xa * (1 + 2j))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LL8QM43mpvKT" + }, + "source": [ + "It is also possible to build `cirq.PauliString`s explicitly with its constructor. This may be useful in generative code, but is occasionally less readable. Each argument and each element in that argument (if it is iterable) is combined with the same `*` operator as before. \n", + "\n", + "Note: If using the dictionary syntax below, the dictionary values must be of type `cirq.SingleQubitPauliGateOperation`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "E01uAo3fpu56" + }, + "outputs": [], + "source": [ + "# Compose two Xa and a coefficient, as a list.\n", + "typrint(cirq.PauliString([4, Xa, 5j * Za]))\n", + "# Compose Xa and Za and a coefficient, as arguments.\n", + "typrint(cirq.PauliString(4, Xa, 5j * Za))\n", + "# Compose Xa and Za and a coefficient, as dictionary arguments.\n", + "typrint(cirq.PauliString(20j, {a: cirq.X}, {a: cirq.Z}))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "eD70SWyWvnU-" + }, + "source": [ + "Note: \n", + "- `PauliString`s are immutable and should be treated as such at all times. `cirq.MutablePauliString` exists, but there are few use cases where this should be necessary.\n", + "- The qubits in a `PauliString` are kept track of in the `qubits` property, since it is an `Operation`, and the `with_qubits` function can re-map the `PauliString` to new qubits. \n", + "- As an operation, `PauliString` has a `cirq.Gate` object (`cirq.DensePauliString`) to represent the operation when not applied to any particular qubits." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "VQuCUxVQvnEy" + }, + "outputs": [], + "source": [ + "pauli_string = -1 * cirq.X(a) * cirq.Y(b) * cirq.Z(c)\n", + "typrint(pauli_string)\n", + "# The PauliString's qubits.\n", + "print(pauli_string.qubits)\n", + "# Remap the PauliString to new qubits.\n", + "new_qubits = cirq.LineQubit.range(3, 6)\n", + "new_pauli_string = pauli_string.with_qubits(*new_qubits)\n", + "typrint(new_pauli_string)\n", + "print(new_pauli_string.qubits)\n", + "# The PauliString's gate.\n", + "typrint(pauli_string.gate)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "B5zr_Ic_UKVX" + }, + "source": [ + "### Linear combinations of Pauli operators as `cirq.PauliSum`s\n", + "\n", + "Only scalar multiplication, composition and tensor product are possible with the `*` operator notation used so far. The final ingredient necessary is the sum `+`, which fittingly produces an object of type `cirq.PauliSum`. This is a lower precedence operator than `*`. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Au34KJ0dUVb3" + }, + "outputs": [], + "source": [ + "# Numbers are treated as coefficients on the identity I (on a unique bias qubit)\n", + "typrint(Xa + 4 + 5j)\n", + "# Sums of single qubit PauliStrings\n", + "typrint(Xa + Xa)\n", + "typrint(Xa + Za)\n", + "typrint(Xa + Zb)\n", + "# Sums of more complex PauliStrings\n", + "typrint(-2 * Xa + 3 * Za)\n", + "typrint(-2 * Xa * Xa + 3 * Za * Zb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "dJf0PKAAaiMA" + }, + "source": [ + "The `PauliString` terms will be simplified in the final version of the sum, and sums of the same term will combine together by adding their exponents. \n", + "\n", + "Arbitrary combinations and parenthesizations of `+` and `*` are supported as you would expect with distribution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "cBaF9SzIah6D" + }, + "outputs": [], + "source": [ + "typrint(-2 * Xa * (Xa + Xb))\n", + "typrint(-2 * Xa * (Za + Zb))\n", + "typrint(-2 * Xa * (Za + Xb * Zb))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "g9iLmoZ4dIBL" + }, + "source": [ + "It may be useful for you to think of this as normal algebra where each Pauli operator applied to each distinct qubit is a different variable, but with the Pauli anti-commutation relations between the three varaibles for each qubit." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "fcWK2dmOUWBH" + }, + "source": [ + "### Exponentials of Pauli operators as `cirq.PauliStringPhasor`s\n", + "\n", + "Cirq also supports [exponentials](https://en.wikipedia.org/wiki/Matrix_exponential){:.external} of Pauli strings with the `cirq.PauliStringPhasor` class. Any number can be exponentiated with a `PauliString`, but most typically it will be Euler's constant $e$ as `np.exp`. Critically, only `PauliString`s are supported by `PauliStringPhasor`, not `PauliSum`s. \n", + "\n", + "Note: When exponentiating, the complex coefficient must be entirely imaginary/have zero for its real component. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "l29R11hGUblq" + }, + "outputs": [], + "source": [ + "# When the PauliString simplifies to a single Pauli term, produce GateOperations\n", + "typrint(np.exp(1j * Xa))\n", + "typrint(np.exp(Xa * Za)) # XZ = -1j*Y\n", + "# When the PauliString doesn't simplify to a single Pauli term, produce PauliStringPhasors\n", + "typrint(np.exp(1j * Xa * Xa)) # I doesn't count as a Pauli term\n", + "typrint(np.exp(1j * Xa * Zb))\n", + "# All integer/float bases are supported with an imaginary-coefficient PauliString.\n", + "typrint(3 ** (1j * Xa * Zb))\n", + "# Powers of unitary PauliStrings work...\n", + "typrint((Xa * Zb) ** 3)\n", + "# but non-unitary PauliStrings don't.\n", + "try:\n", + " typrint((3j * Xa * Zb) ** 3)\n", + "except TypeError as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ujpfb_NnQJKc" + }, + "source": [ + "Note: `PauliStringPhasor`s can't be used as components in larger expressions composed with `+` or `*`. However, you can take powers of them to affect the exponent." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "wDnFXVlhPncH" + }, + "outputs": [], + "source": [ + "typrint(np.exp(1j * Xa * Xb))\n", + "typrint(np.exp(1j * Xa * Xb) ** 5)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RB7tQNcmSh9v" + }, + "source": [ + "Note: The printed representation of a `PauliStringPhasor` can sometimes change based on the level of simplification possible. This doesn't change the interface for the object. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "uYAq2PbRShsE" + }, + "outputs": [], + "source": [ + "typrint(np.exp(2j * Xa * Zb))\n", + "typrint(np.exp(3j * Xa * Zb))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RfTzXka7Svh9" + }, + "source": [ + "`PauliStringPhasor` has additional, more general use patterns than just those presented here. See the reference page for `cirq.PauliStringPhasor` for specifics about the class. The docstrings for it discuss \"phasing an eigenstate\", which is a strategy for efficiently exponentiating an alternative but equivalent representation of Pauli strings. See [this post](https://quantumcomputing.stackexchange.com/a/15383){:.external} for more details." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "fhAiozDeUcCo" + }, + "source": [ + "### Exponentials of **commuting** Pauli operators as `cirq.PauliSumExponential`s. \n", + "\n", + "`cirq.PauliStringPhasor` only supports the exponentiation of `PauliString`s, but doesn't work for sums with `PauliSum`. The reason for this is that only some Pauli sum expressions can be exponentiated: expressions where the operators commute. \n", + "\n", + "Cirq expresses this type of expression with `cirq.PauliSumExponential`. For the sake of clarity, these expressions are only ever created with the class initializer, instead of with the exponentiation operator `**` or with `np.exp`. The initializer takes: \n", + "- A `PauliSum` object or something that can be instantiated into one.\n", + "- An (optional) exponent. \n", + "\n", + "The result is an expression that represents `exp(1j * exponent * pauli_sum)`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "usNMlHHbUyHV" + }, + "outputs": [], + "source": [ + "# Instantiated with PauliStrings.\n", + "typrint(cirq.PauliSumExponential(Xa))\n", + "typrint(cirq.PauliSumExponential(Xa * Zb, exponent=3))\n", + "# Instantiated with PauliSums.\n", + "typrint(cirq.PauliSumExponential(Xa + Zb, exponent=3 + 5j))\n", + "typrint(cirq.PauliSumExponential(2 * (3 * Xa + 4 * Zb), exponent=3))\n", + "# Doesn't work with other bases than e.\n", + "try:\n", + " typrint(6 ** (Xa + Xb))\n", + "except TypeError as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "85jvbRHHV7um" + }, + "source": [ + "Note: Again, these expressions can't be used as components in larger expressions with `+` or `*`, but can have powers taken of them to multiply the exponent. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "UySj-ZOTWHcA" + }, + "outputs": [], + "source": [ + "typrint(cirq.PauliSumExponential(Xa * Zb, exponent=3))\n", + "typrint(cirq.PauliSumExponential(Xa * Zb, exponent=3) ** 5)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3UODyy0vUyfU" + }, + "source": [ + "## Using `PauliString` as observables\n", + "\n", + "All of the `PauliString`s and compositions thereof are still `cirq.Operation`s, meaning they can be used in circuits like any other `Operation`. However, they have unique ability to be used as [observables](https://en.wikipedia.org/wiki/Observable){:.external} during measurement. Observables are typically some sort of measurable property (of a quantum state). \n", + "\n", + "\"Measuring an observable\" usually amounts to applying the observable to the final quantum state and measuring in the standard computational basis, but is representative of measuring whether that observable's property holds. It is equivalent to (in many cases) or conceptually similar to a change of basis, meaning \"measuring an observable\" is roughly the same as measuring in some basis other than the computational one.\n", + "\n", + "### Measure a single observable\n", + "\n", + "`cirq.measure_single_paulistring` serves to package the observable into a `cirq.MeasurementGate`-line object, a `cirq.PauliMeasurementgate`, which applies the observable as if it were an operation, and then measures all of the qubits that appear in the observable. \n", + "\n", + "There is one critical, additional step that `measure_single_paulistring` performs beyond simply applying the observable and measuring. It identifies the [eigenstates](https://en.wikipedia.org/wiki/Introduction_to_eigenstates){:.external} of the observable and returns a single bit of information, whether the final state of the qubits in question is in one of those eigenstates (`0`) or not (`1`). For example, the eigenstates of the `ZZ` observable (aka. `cirq.Z(a) * cirq.Z(b)`), are $|00\\rangle$ and $|11\\rangle$ for the two qubits `a` and `b`. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ddhecQm2VRrZ" + }, + "outputs": [], + "source": [ + "sim = cirq.Simulator()\n", + "observable = Za * Zb\n", + "# A PauliMeasurementGate.\n", + "typrint(cirq.measure_single_paulistring(observable, key='m'))\n", + "# Measure the observable on the bell state.\n", + "circuit = cirq.Circuit(\n", + " cirq.H(a), cirq.CNOT(a, b), cirq.measure_single_paulistring(observable, key='m')\n", + ")\n", + "print(f\"dirac notation: {sim.simulate(circuit).dirac_notation()}\")\n", + "print(f\"measurements: {sim.run(circuit, repetitions=100).histogram(key='m')}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mvBzBQdZjBca" + }, + "source": [ + "A single simulation of the circuit produces a result that is not in the computational basis, as seen in the `dirac notation` printout. This also shows that the qubits can only be in one of the two mentioned eigenstates of `ZZ`, $|00\\rangle$ or $|11\\rangle$. \n", + "\n", + "The value of the measurement itself is always `0`, because the states that are really measured under the hood, $|00\\rangle$ and $|11\\rangle$, are eigenstates of the observable. \n", + "\n", + "The difference in behavior can be seen by appending the observable and measuring separately:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "-m_xGp3zd8_P" + }, + "outputs": [], + "source": [ + "# The same circuit, but applying the observable as an operator and measuring separately.\n", + "circuit = cirq.Circuit(cirq.H(a), cirq.CNOT(a, b), observable, cirq.measure([a, b], key='m'))\n", + "print(f\"dirac notation: {sim.simulate(circuit).dirac_notation()}\")\n", + "print(f\"measurements: {sim.run(circuit, repetitions=100).histogram(key='m')}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "pY1BSrwsi_eF" + }, + "source": [ + "A single simulation of the circuit now produces a dirac notation state in the computational basis, but it is representative of only one of the possible two states to measure. Additionally, but $|00\\rangle$ and $|11\\rangle$ are recorded in the measurements (as `0` and `3`). It would take you an extra step to determine which of those are eigenstates of `ZZ`, and see that the observable holds in all cases. `cirq.measure_single_paulistring` takes care of this for you. \n", + "\n", + "For more information on eigenstates, see [Quantum Theory, Groups and Representations:An Introduction](https://doi.org/10.1007/978-3-319-64612-1), by Peter Woit." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8qsOhYcsVR_T" + }, + "source": [ + "### Estimate expectation values of a `PauliSum` observable\n", + "\n", + "As mentioned, `cirq.measure_single_paulistring` only works for `PauliString`s. In order to \"measure\" a linear combination of Pauli operators, Cirq provides the `cirq.PauliSumCollector` class to estimate a `PauliSum`. This class provides a utility feature to sample a circuit in parallel, measure each `PauliString` observable term in the sum, and add them back together in a weighted sum based on their coefficients. Note that there may be more efficient case-specific ways to do this." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "VQwPC526VaIw" + }, + "outputs": [], + "source": [ + "# A helper function to create a collector, collet, and estimate energy.\n", + "def show_energy(circuit, observable):\n", + " collector = cirq.PauliSumCollector(circuit=circuit, observable=observable, samples_per_term=100)\n", + " collector.collect(sampler=cirq.Simulator())\n", + " energy = collector.estimated_energy()\n", + " typrint(energy)\n", + "\n", + "\n", + "circuit = cirq.Circuit(cirq.H(a), cirq.CNOT(a, b))\n", + "observable = Za * Zb\n", + "show_energy(circuit, observable)\n", + "observable = 4 * Xa\n", + "show_energy(circuit, observable)\n", + "observable = Za * Zb + 4 * Xa\n", + "show_energy(circuit, observable)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xUJ1Z53TVapX" + }, + "source": [ + "### Measure a sequence of observables in a circuit\n", + "\n", + "If you need to measure many different `PauliString`s (not `PauliSum`s), for a circuit, `cirq.measure_observables` may fit your needs. It serves to estimate each observable in a provided iterable by computing the mean and variance over a number of repetitions defined by the `stopping_criteria` argument. In the example below, this stopping criteria is fixed at `50,000` repetitions. \n", + "\n", + "The function also supports the following optional arguments, which expand its functionality: \n", + "- circuit_sweep: A parameter sweep as in [Parameter Sweeps](/cirq/simulate/params)\n", + "- readout_calibrations: An input to make use of previously-collected readout error data.\n", + "- grouper: A strategy to group the observables so multiple observables can be measured in the same run (uses default greedy strategy).\n", + "- readout_symmetrization: Applies a bit flip after half of the runs to make readout error seem symmetric" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "A8UO9vqbVfIV" + }, + "outputs": [], + "source": [ + "from cirq.work.observable_measurement import measure_observables, RepetitionsStoppingCriteria\n", + "\n", + "observables = [Za * Zb, 4 * Xa]\n", + "results = measure_observables(\n", + " circuit, observables, cirq.Simulator(), stopping_criteria=RepetitionsStoppingCriteria(100)\n", + ")\n", + "# Print the mean and variance measured for each observable\n", + "for result in results:\n", + " print(result.observable, result.mean, result.variance)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "fQV7J-IRVfhA" + }, + "source": [ + "# Summary\n", + "\n", + "Building `PauliStrings` and more:\n", + "- The Pauli operatiors `cirq.X`, `cirq.Y` and `cirq.Z` can be combined into `cirq.PauliString`s with `*` and `cirq.PauliSum`s with `+`. \n", + "- The `*` operator is used simultaneously for scalar multiplication, composition and tensor product, but only the order of operators applied to the same qubit matters. \n", + "- `PauliString`s can be exponentiated with `int**PauliString` or `np.exp(PauliString)` to produce a `cirq.PauliStringPhasor`, and `PauliSums` can be exponentiated when they commute with `PauliSumExponential(PauliSum, exponent)` to produce a `cirq.PauliSumExponential`.\n", + "\n", + "Measuring observables:\n", + "- Measure a single `PauliString` term with `cirq.measure_single_paulistring`, which takes care of determining eigenstates for you. \n", + "- Estimate `PauliSum` expressions by calculating the weighted average of each term with `cirq.PauliSumCollector`\n", + "- Efficiently estimate the mean and variance of many different `PauliString` observables for a single circuit with the flexible and powerful `cirq.measure_observables`. " + ] + } + ], + "metadata": { + "colab": { + "collapsed_sections": [], + "name": "pauli_observables.ipynb", + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +}