View on QuantumAI | Run in Google Colab | View source on GitHub | Download notebook |

```
# @title Setup { vertical-output: true, display-mode: "form" }
try:
import cirq
except ImportError:
print("installing cirq...")
!pip install --quiet cirq
print("installed cirq.")
import cirq
import cirq_google
import sympy
import numpy as np
```

## What are Pauli observables?

Cirq provides the Pauli operators `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.

Observables 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.

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, 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.

## Pauli Operator Representations

Before starting on building `PauliString`

s, define:

- A tiny function to print an object with its type, to make clear the types being used later
- Some Pauli operations that the
`PauliString`

s and such will be built from.

```
# A small utility function to print the type and value of any number of arguments.
def typrint(*xs):
for x in xs:
print(type(x), x)
# A couple qubits.
a, b, c = cirq.LineQubit.range(3)
# A set of Pauli operations to build PauliStrings from.
Xa = cirq.X(a)
Xb = cirq.X(b)
Za = cirq.Z(a)
Zb = cirq.Z(b)
# Test the typrint function.
typrint(Xa, Xb, Za, Zb)
```

<class 'cirq.ops.pauli_string.SingleQubitPauliStringGateOperation'> X(q(0)) <class 'cirq.ops.pauli_string.SingleQubitPauliStringGateOperation'> X(q(1)) <class 'cirq.ops.pauli_string.SingleQubitPauliStringGateOperation'> Z(q(0)) <class 'cirq.ops.pauli_string.SingleQubitPauliStringGateOperation'> Z(q(1))

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.

`PauliString`

construction

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 occurring.

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`

.

```
# An empty PauliString
typrint(cirq.PauliString())
# An equivalently empty PauliString built from an identity operation
Ia = cirq.I(a)
typrint(cirq.PauliString(Ia))
print(cirq.PauliString() == cirq.PauliString(Ia))
# cirq.I is a PauliString.
typrint(Ia)
print(issubclass(Ia.__class__, cirq.PauliString))
# cirq.I has qubits, but a PauliString drops qubits that are identity.
print(Ia.qubits)
print(cirq.PauliString(Ia).qubits)
# Two consecutive Xa cancel to the identity and are dropped.
print(cirq.PauliString(Xa, Xa).qubits)
```

<class 'cirq.ops.pauli_string.PauliString'> I <class 'cirq.ops.pauli_string.PauliString'> I True <class 'cirq.ops.gate_operation.GateOperation'> I(q(0)) False (cirq.LineQubit(0),) () ()

`cirq.SingleQubitPauliStringGateOperation`

s are themselves `PauliString`

s, and are representative of one of the Pauli gates applied to some qubit.

```
typrint(Xa)
print(issubclass(Xa.__class__, cirq.PauliString))
```

<class 'cirq.ops.pauli_string.SingleQubitPauliStringGateOperation'> X(q(0)) True

Larger `PauliString`

s can be built with the `*`

operator. Be careful with this operator, as it can be used in three distinct ways:

- Scalar Multiplication:
`complex * PauliString`

produces a`PauliString`

with a complex scalar coefficient attached. - 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. - 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 \(⨂\)).

```
# Complex scalar multiplication.
typrint((4 + 5j) * Xa)
# Composition
typrint(Xa * Xa)
typrint(Xa * Za)
# Tensor
typrint(Xa * Xb)
typrint(Xa * Zb)
```

<class 'cirq.ops.pauli_string.PauliString'> (4+5j)*X(q(0)) <class 'cirq.ops.pauli_string.PauliString'> I <class 'cirq.ops.pauli_string.PauliString'> -1j*Y(q(0)) <class 'cirq.ops.pauli_string.PauliString'> X(q(0))*X(q(1)) <class 'cirq.ops.pauli_string.PauliString'> X(q(0))*Z(q(1))

In the composition examples, cancellation and anti-commutation 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.

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**

```
# The two PauliStrings from before
typrint(Xa * Za)
typrint(Xa * Zb)
# Correct order of operations on qubit a, which merge to a single -Z operation.
typrint(Xa * Za * Xa)
# Combined together with a coefficient.
typrint((3 + 6j) * (Xa * Za) * (Xa * Zb))
# The same PauliString with different ordering and a split coefficient.
typrint(Xa * Zb * Za * (3 + 0j) * Xa * (1 + 2j))
# A different PauliString where the terms applied to qubit a have changed order.
typrint(Za * Zb * Xa * (3 + 0j) * Xa * (1 + 2j))
```

<class 'cirq.ops.pauli_string.PauliString'> -1j*Y(q(0)) <class 'cirq.ops.pauli_string.PauliString'> X(q(0))*Z(q(1)) <class 'cirq.ops.pauli_string.PauliString'> -Z(q(0)) <class 'cirq.ops.pauli_string.PauliString'> (-3-6j)*Z(q(0))*Z(q(1)) <class 'cirq.ops.pauli_string.PauliString'> (-3-6j)*Z(q(0))*Z(q(1)) <class 'cirq.ops.pauli_string.PauliString'> (3+6j)*Z(q(0))*Z(q(1))

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.

```
# Compose two Xa and a coefficient, as a list.
typrint(cirq.PauliString([4, Xa, 5j * Za]))
# Compose Xa and Za and a coefficient, as arguments.
typrint(cirq.PauliString(4, Xa, 5j * Za))
# Compose Xa and Za and a coefficient, as dictionary arguments.
typrint(cirq.PauliString(20j, {a: cirq.X}, {a: cirq.Z}))
```

<class 'cirq.ops.pauli_string.PauliString'> (20+0j)*Y(q(0)) <class 'cirq.ops.pauli_string.PauliString'> (20+0j)*Y(q(0)) <class 'cirq.ops.pauli_string.PauliString'> (20+0j)*Y(q(0))

`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.- 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. - As an operation,
`PauliString`

has a`cirq.Gate`

object (`cirq.DensePauliString`

) to represent the operation when not applied to any particular qubits.

```
pauli_string = -1 * cirq.X(a) * cirq.Y(b) * cirq.Z(c)
typrint(pauli_string)
# The PauliString's qubits.
print(pauli_string.qubits)
# Remap the PauliString to new qubits.
new_qubits = cirq.LineQubit.range(3, 6)
new_pauli_string = pauli_string.with_qubits(*new_qubits)
typrint(new_pauli_string)
print(new_pauli_string.qubits)
# The PauliString's gate.
typrint(pauli_string.gate)
```

<class 'cirq.ops.pauli_string.PauliString'> -X(q(0))*Y(q(1))*Z(q(2)) (cirq.LineQubit(0), cirq.LineQubit(1), cirq.LineQubit(2)) <class 'cirq.ops.pauli_string.PauliString'> -X(q(3))*Y(q(4))*Z(q(5)) (cirq.LineQubit(3), cirq.LineQubit(4), cirq.LineQubit(5)) <class 'cirq.ops.dense_pauli_string.DensePauliString'> -XYZ

### Linear combinations of Pauli operators as `cirq.PauliSum`

s

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 `*`

.

```
# Numbers are treated as coefficients on the identity I (on a unique bias qubit)
typrint(Xa + 4 + 5j)
# Sums of single qubit PauliStrings
typrint(Xa + Xa)
typrint(Xa + Za)
typrint(Xa + Zb)
# Sums of more complex PauliStrings
typrint(-2 * Xa + 3 * Za)
typrint(-2 * Xa * Xa + 3 * Za * Zb)
```

<class 'cirq.ops.linear_combinations.PauliSum'> 1.000*X(q(0))+(4.000+5.000j)*I <class 'cirq.ops.linear_combinations.PauliSum'> 2.000*X(q(0)) <class 'cirq.ops.linear_combinations.PauliSum'> 1.000*X(q(0))+1.000*Z(q(0)) <class 'cirq.ops.linear_combinations.PauliSum'> 1.000*X(q(0))+1.000*Z(q(1)) <class 'cirq.ops.linear_combinations.PauliSum'> -2.000*X(q(0))+3.000*Z(q(0)) <class 'cirq.ops.linear_combinations.PauliSum'> -2.000*I+3.000*Z(q(0))*Z(q(1))

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.

Arbitrary combinations and parenthesizations of `+`

and `*`

are supported as you would expect with distribution.

```
typrint(-2 * Xa * (Xa + Xb))
typrint(-2 * Xa * (Za + Zb))
typrint(-2 * Xa * (Za + Xb * Zb))
```

<class 'cirq.ops.linear_combinations.PauliSum'> -2.000*I-2.000*X(q(0))*X(q(1)) <class 'cirq.ops.linear_combinations.PauliSum'> 2.000j*Y(q(0))-2.000*X(q(0))*Z(q(1)) <class 'cirq.ops.linear_combinations.PauliSum'> 2.000j*Y(q(0))+2.000j*X(q(0))*Y(q(1))

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 variables for each qubit.

### Exponentials of Pauli operators as `cirq.PauliStringPhasor`

s

Cirq also supports exponentials 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.

```
# When the PauliString simplifies to a single Pauli term, produce GateOperations
typrint(np.exp(1j * Xa))
typrint(np.exp(Xa * Za)) # XZ = -1j*Y
# When the PauliString doesn't simplify to a single Pauli term, produce PauliStringPhasors
typrint(np.exp(1j * Xa * Xa)) # I doesn't count as a Pauli term
typrint(np.exp(1j * Xa * Zb))
# All integer/float bases are supported with an imaginary-coefficient PauliString.
typrint(3 ** (1j * Xa * Zb))
# Powers of unitary PauliStrings work...
typrint((Xa * Zb) ** 3)
# but non-unitary PauliStrings don't.
try:
typrint((3j * Xa * Zb) ** 3)
except TypeError as e:
print(e)
```

<class 'cirq.ops.gate_operation.GateOperation'> XPowGate(exponent=-0.6366197723675814, global_shift=-0.5)(q(0)) <class 'cirq.ops.gate_operation.GateOperation'> YPowGate(exponent=0.6366197723675814, global_shift=-0.5)(q(0)) <class 'cirq.ops.pauli_string_phasor.PauliStringPhasor'> (I)**-0.6366197723675815 <class 'cirq.ops.pauli_string_phasor.PauliStringPhasor'> (X(q(0))*Z(q(1)))**-0.6366197723675815 <class 'cirq.ops.pauli_string_phasor.PauliStringPhasor'> (X(q(0))*Z(q(1)))**-0.6993983051321195 <class 'cirq.ops.pauli_string_phasor.PauliStringPhasor'> (X(q(0))*Z(q(1)))**1.0 unsupported operand type(s) for ** or pow(): 'PauliString' and 'int'

```
typrint(np.exp(1j * Xa * Xb))
typrint(np.exp(1j * Xa * Xb) ** 5)
```

<class 'cirq.ops.pauli_string_phasor.PauliStringPhasor'> (X(q(0))*X(q(1)))**-0.6366197723675815 <class 'cirq.ops.pauli_string_phasor.PauliStringPhasor'> (X(q(0))*X(q(1)))**0.8169011381620928

```
typrint(np.exp(2j * Xa * Zb))
typrint(np.exp(3j * Xa * Zb))
```

<class 'cirq.ops.pauli_string_phasor.PauliStringPhasor'> (X(q(0))*Z(q(1)))**0.726760455264837 <class 'cirq.ops.pauli_string_phasor.PauliStringPhasor'> exp(iπ0.954929658551372*X(q(0))*Z(q(1)))

`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 for more details.

### Exponentials of **commuting** Pauli operators as `cirq.PauliSumExponential`

s.

`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.

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:

- A
`PauliSum`

object or something that can be instantiated into one. - An (optional) exponent.

The result is an expression that represents `exp(1j * exponent * pauli_sum)`

.

```
# Instantiated with PauliStrings.
typrint(cirq.PauliSumExponential(Xa))
typrint(cirq.PauliSumExponential(Xa * Zb, exponent=3))
# Instantiated with PauliSums.
typrint(cirq.PauliSumExponential(Xa + Zb, exponent=3 + 5j))
typrint(cirq.PauliSumExponential(2 * (3 * Xa + 4 * Zb), exponent=3))
# Doesn't work with other bases than e.
try:
typrint(6 ** (Xa + Xb))
except TypeError as e:
print(e)
```

<class 'cirq.ops.pauli_sum_exponential.PauliSumExponential'> exp(j * 1 * (1.000*X(q(0)))) <class 'cirq.ops.pauli_sum_exponential.PauliSumExponential'> exp(j * 3 * (1.000*X(q(0))*Z(q(1)))) <class 'cirq.ops.pauli_sum_exponential.PauliSumExponential'> exp(j * (3+5j) * (1.000*X(q(0))+1.000*Z(q(1)))) <class 'cirq.ops.pauli_sum_exponential.PauliSumExponential'> exp(j * 3 * (6.000*X(q(0))+8.000*Z(q(1)))) unsupported operand type(s) for ** or pow(): 'int' and 'PauliSum'

```
typrint(cirq.PauliSumExponential(Xa * Zb, exponent=3))
typrint(cirq.PauliSumExponential(Xa * Zb, exponent=3) ** 5)
```

<class 'cirq.ops.pauli_sum_exponential.PauliSumExponential'> exp(j * 3 * (1.000*X(q(0))*Z(q(1)))) <class 'cirq.ops.pauli_sum_exponential.PauliSumExponential'> exp(j * 15 * (1.000*X(q(0))*Z(q(1))))

## Using `PauliString`

as observables

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 during measurement. Observables are typically some sort of measurable property (of a quantum state).

"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.

### Measure a single observable

`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.

There is one critical, additional step that `measure_single_paulistring`

performs beyond simply applying the observable and measuring. It identifies the eigenstates 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`

.

```
sim = cirq.Simulator()
observable = Za * Zb
# A PauliMeasurementGate.
typrint(cirq.measure_single_paulistring(observable, key='m'))
# Measure the observable on the bell state.
circuit = cirq.Circuit(
cirq.H(a), cirq.CNOT(a, b), cirq.measure_single_paulistring(observable, key='m')
)
print(f"dirac notation: {sim.simulate(circuit).dirac_notation()}")
print(f"measurements: {sim.run(circuit, repetitions=100).histogram(key='m')}")
```

<class 'cirq.ops.gate_operation.GateOperation'> cirq.PauliMeasurementGate(cirq.DensePauliString('ZZ', coefficient=(1+0j)), cirq.MeasurementKey(name='m'))(q(0), q(1)) dirac notation: 0.71|00⟩ + 0.71|11⟩ measurements: Counter({0: 100})

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\).

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.

The difference in behavior can be seen by appending the observable and measuring separately:

```
# The same circuit, but applying the observable as an operator and measuring separately.
circuit = cirq.Circuit(cirq.H(a), cirq.CNOT(a, b), observable, cirq.measure([a, b], key='m'))
print(f"dirac notation: {sim.simulate(circuit).dirac_notation()}")
print(f"measurements: {sim.run(circuit, repetitions=100).histogram(key='m')}")
```

dirac notation: |11⟩ measurements: Counter({3: 50, 0: 50})

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.

For more information on eigenstates, see Quantum Theory, Groups and Representations:An Introduction, by Peter Woit.

### Estimate expectation values of a `PauliSum`

observable

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.

```
# A helper function to create a collector, collet, and estimate energy.
def show_energy(circuit, observable):
collector = cirq.PauliSumCollector(circuit=circuit, observable=observable, samples_per_term=100)
collector.collect(sampler=cirq.Simulator())
energy = collector.estimated_energy()
typrint(energy)
circuit = cirq.Circuit(cirq.H(a), cirq.CNOT(a, b))
observable = Za * Zb
show_energy(circuit, observable)
observable = 4 * Xa
show_energy(circuit, observable)
observable = Za * Zb + 4 * Xa
show_energy(circuit, observable)
```

<class 'float'> 1.0 <class 'float'> -0.24 <class 'float'> 1.24

### Measure a sequence of observables in a circuit

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.

The function also supports the following optional arguments, which expand its functionality:

- circuit_sweep: A parameter sweep as in Parameter Sweeps
- readout_calibrations: An input to make use of previously-collected readout error data.
- grouper: A strategy to group the observables so multiple observables can be measured in the same run (uses default greedy strategy).
- readout_symmetrization: Applies a bit flip after half of the runs to make readout error seem symmetric

```
from cirq.work.observable_measurement import measure_observables, RepetitionsStoppingCriteria
observables = [Za * Zb, 4 * Xa]
results = measure_observables(
circuit, observables, cirq.Simulator(), stopping_criteria=RepetitionsStoppingCriteria(100)
)
# Print the mean and variance measured for each observable
for result in results:
print(result.observable, result.mean, result.variance)
```

Z(q(0))*Z(q(1)) 1.0 0.0 (4+0j)*X(q(0)) 0.72 0.15637979797979795

# Summary

Building `PauliStrings`

and more:

- The Pauli operatiors
`cirq.X`

,`cirq.Y`

and`cirq.Z`

can be combined into`cirq.PauliString`

s with`*`

and`cirq.PauliSum`

s with`+`

. - The
`*`

operator is used simultaneously for scalar multiplication, composition and tensor product, but only the order of operators applied to the same qubit matters. `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`

.

Measuring observables:

- Measure a single
`PauliString`

term with`cirq.measure_single_paulistring`

, which takes care of determining eigenstates for you. - Estimate
`PauliSum`

expressions by calculating the weighted average of each term with`cirq.PauliSumCollector`

- Efficiently estimate the mean and variance of many different
`PauliString`

observables for a single circuit with the flexible and powerful`cirq.measure_observables`

.