View on QuantumAI  Run in Google Colab  View source on GitHub  Download notebook 
try:
import cirq
except ImportError:
print("installing cirq...")
!pip install quiet cirq
print("installed cirq.")
import cirq
import numpy as np
import sympy.parsing.sympy_parser as sympy_parser
This guide is directed at those already familiar with quantum operations (operators) and observables who want to know how to use them in Cirq. The following table shows an overview of operators.
Operator  Cirq representation  Guides  Examples 

Unitary operators  Any class implementing the _unitary_ protocol 
Protocols, Gates and operations, Custom gates  cirq.Gate cirq.X(qubit) cirq.CNOT(q0, q1) cirq.MatrixGate.on(qubit) cirq.Circuit (if it only contains unitary operations) 
Measurements  cirq.measure and cirq.MeasurementGate 
Gates and operations  cirq.measure(cirq.LineQubit(0)) 
Quantum channels 

Protocols  cirq.DepolarizingChannel(p=0.2)(q0) cirq.X.with_probability(0.5) 
Cirq also supports observables on qubits that can be used to calculate expectation values on a given state.
 You can use
cirq.PauliString
to express them,  Or you can use
cirq.PauliSum
with real coefficients.
Operators
Quantum operations (or just operators) include unitary gates, measurements, and noisy channels. Operators that act on a given set of qubits implement cirq.Operation
which supports the Kraus operator representation
\[ \rho \mapsto \sum_{k} A_k \rho A_k^\dagger . \]
Here, \(\sum_{k} A_k^\dagger A_k = I\) and \(\rho\) is a quantum state. Operators are defined in the cirq.ops
module.
Unitary operators
Standard unitary operators used in quantum information can be found in cirq.ops
, for example Pauli\(X\) as shown below.
qubit = cirq.LineQubit(0)
unitary_operation = cirq.ops.X.on(qubit) # cirq.X can also be used for cirq.ops.X
print(unitary_operation)
X(0)
Cirq makes a distinction between gates (independent of qubits) and operations (gates acting on qubits). Thus cirq.X
is a gate where cirq.X.on(qubit)
is an operation. See the guide on gates for more details and additional common unitaries defined in Cirq.
Every cirq.Operation
supports the cirq.channel
protocol which returns its Kraus operators. (Read more about protocols in Cirq.)
kraus_ops = cirq.kraus(unitary_operation)
print(f"Kraus operators of {unitary_operation.gate} are:", *kraus_ops, sep="\n")
Kraus operators of X are: [[0.+0.j 1.+0.j] [1.+0.j 0.+0.j]]
Unitary operators also support the cirq.unitary
protocol.
unitary = cirq.unitary(cirq.ops.X)
print(f"Unitary of {unitary_operation.gate} is:\n", unitary)
Unitary of X is: [[0.+0.j 1.+0.j] [1.+0.j 0.+0.j]]
Unitary gates can be raised to powers, for example to implement a \(\sqrt{X}\) operation.
sqrt_not = cirq.X ** (1 / 2)
print(cirq.unitary(sqrt_not))
[[0.5+0.5j 0.50.5j] [0.50.5j 0.5+0.5j]]
Any gate can be controlled via cirq.ControlledGate
as follows.
controlled_hadamard = cirq.ControlledGate(sub_gate=cirq.H, num_controls=1)
print(cirq.unitary(controlled_hadamard).round(3))
[[ 1. +0.j 0. +0.j 0. +0.j 0. +0.j] [ 0. +0.j 1. +0.j 0. +0.j 0. +0.j] [ 0. +0.j 0. +0.j 0.707+0.j 0.707+0.j] [ 0. +0.j 0. +0.j 0.707+0.j 0.707+0.j]]
Custom gates can be defined as described in this guide. Some common subroutines which consist of several operations are predefined  e.g., cirq.qft
returns the operations to implement the quantum Fourier transform.
Measurements
Cirq supports measurements in the computational basis.
measurement = cirq.MeasurementGate(num_qubits=1, key="key")
print("Measurement:", measurement)
Measurement: cirq.MeasurementGate(1, 'key', ())
The key
can be used to identify results of measurements when simulating circuits. A measurement gate acting on a qubit forms an operation.
measurement_operation = measurement.on(qubit)
print(measurement_operation)
cirq.MeasurementGate(1, 'key', ())(0)
Again measurement operations implement cirq.Operation
so the cirq.channel
protocol can be used to get the Kraus operators.
kraus_ops = cirq.kraus(measurement)
print(f"Kraus operators of {measurement} are:", *kraus_ops, sep="\n\n")
Kraus operators of cirq.MeasurementGate(1, 'key', ()) are: [[1. 0.] [0. 0.]] [[0. 0.] [0. 1.]]
The functions cirq.measure_state_vector
and cirq.measure_density_matrix
can be used to perform computational basis measurements on state vectors and density matrices, respectively, represented by NumPy arrays.
psi = np.ones(shape=(2,)) / np.sqrt(2)
print("Wavefunction:\n", psi.round(3))
Wavefunction: [0.707 0.707]
results, psi_prime = cirq.measure_state_vector(psi, indices=[0])
print("Measured:", results[0])
print("Resultant state:\n", psi_prime)
Measured: 1 Resultant state: [0. 1.]
rho = np.ones(shape=(2, 2)) / 2.0
print("State:\n", rho)
State: [[0.5 0.5] [0.5 0.5]]
measurements, rho_prime = cirq.measure_density_matrix(rho, indices=[0])
print("Measured:", measurements[0])
print("Resultant state:\n", rho_prime)
Measured: 0 Resultant state: [[1. 0.] [0. 0.]]
These functions do not modify the input state (psi
or rho
) unless the optional argument out
is provided as the input state.
Noisy channels
Like common unitary gates, Cirq defines many common noisy channels, for example the depolarizing channel below.
depo_channel = cirq.DepolarizingChannel(p=0.01, n_qubits=1)
print(depo_channel)
depolarize(p=0.01)
Just like unitary gates and measurements, noisy channels implement cirq.Operation
, and we can always use cirq.channel
to get the Kraus operators.
kraus_ops = cirq.kraus(depo_channel)
print(f"Kraus operators of {depo_channel} are:", *[op.round(2) for op in kraus_ops], sep="\n\n")
Kraus operators of depolarize(p=0.01) are: [[0.99 0. ] [0. 0.99]] [[0. +0.j 0.06+0.j] [0.06+0.j 0. +0.j]] [[0.+0.j 0.0.06j] [0.+0.06j 0.+0.j ]] [[ 0.06+0.j 0. +0.j] [ 0. +0.j 0.06+0.j]]
Some channels can be written
\[ \rho \mapsto \sum_k p_k U_k \rho U_k ^\dagger \]
where real numbers \(p_k\) form a probability distribution and \(U_k\) are unitary. Such a probabilistic mixture of unitaries supports the cirq.mixture
protocol which returns \(p_k\) and \(U_k\). An example is shown below for the bitflip channel \(\rho \mapsto (1  p) \rho + p X \rho X\).
bit_flip = cirq.bit_flip(p=0.05)
probs, unitaries = cirq.mixture(bit_flip)
for prob, unitary in cirq.mixture(bit_flip):
print(f"With probability {prob}, apply \n{unitary}\n")
With probability 0.95, apply [[1. 0.] [0. 1.]] With probability 0.05, apply [[0.+0.j 1.+0.j] [1.+0.j 0.+0.j]]
Custom noisy channels can be defined as described in this guide.
In circuits
Any cirq.Operation
(predefined or userdefined) can be placed in a cirq.Circuit
. An example with a unitary, noisy channel, and measurement is shown below.
circuit = cirq.Circuit(
cirq.H(qubit),
cirq.depolarize(p=0.01).on(qubit),
cirq.measure(qubit)
)
print(circuit)
0: ───H───D(0.01)───M───
The general input to the circuit constructor is a cirq.OP_TREE
, i.e., an operation or nested collection of operations. Circuits can be manipulated as described in the circuits guide and simulated as described in the simulation guide.
Observables
Cirq supports observables which are Pauli strings or linear combinations of Pauli strings. Such objects can be used to compute expectation values.
Pauli strings
Pauli products or strings are supported via cirq.PauliString
. For example, the Pauli string \(Z_0 Z_1\) (where subscripts denote qubit indices) can be represented as follows.
# Qubit register
qreg = cirq.NamedQubit.range(2, prefix="q")
# PauliString Z_0 Z_1
zz = cirq.PauliString(cirq.Z(q) for q in qreg)
print(zz)
Z(q0)*Z(q1)
The matrix of a Pauli string can be returned via:
zz.matrix()
array([[ 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], [ 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j], [ 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j], [ 0.+0.j, 0.+0.j, 0.+0.j, 1.0.j]])
Pauli strings can also have arbitrary coefficients.
new = (1.0  0.1j) * zz
print(new)
(10.1j)*Z(q0)*Z(q1)
Pauli sums
A cirq.PauliSum
is a linear combination of cirq.PauliString
s and represents a Hamiltonian (or general observable) in the Pauli basis. To represent the observable \(O = 1.5 Z_0 Z_1  0.7 X_0 X_1\), we can first define the \(X_0 X_1\) Pauli string:
xx = cirq.PauliString(cirq.X(q) for q in qreg)
print(xx)
X(q0)*X(q1)
Then form the linear combination.
psum = 1.5 * zz  0.7 * xx
print(psum)
1.500*Z(q0)*Z(q1)0.700*X(q0)*X(q1)
Like Pauli strings, we can get the matrix of a cirq.PauliSum
:
psum.matrix()
array([[ 1.5+0.j, 0. +0.j, 0. +0.j, 0.7+0.j], [ 0. +0.j, 1.5+0.j, 0.7+0.j, 0. +0.j], [ 0. +0.j, 0.7+0.j, 1.5+0.j, 0. +0.j], [0.7+0.j, 0. +0.j, 0. +0.j, 1.5+0.j]])
A Pauli sum can also be constructed from a Sympy
Boolean expression. This is based on "On the representation of Boolean and real functions as Hamiltonians for quantum computing" by Stuart Hadfield (https://arxiv.org/abs/1804.09130)
psum = cirq.PauliSum.from_boolean_expression(
sympy_parser.parse_expr('x0 ^ x1'),
{'x0': cirq.NamedQubit('q0'), 'x1': cirq.NamedQubit('q1')})
Expectation values
Given a state \(\rho\) that is prepared by a circuit, expectation values \(\text{Tr} [ \rho O ]\) where \(O\) is an observable can be computed as follows. First, an example circuit:
circuit = cirq.Circuit(cirq.ops.H.on_each(qreg))
print(circuit)
q0: ───H─── q1: ───H───
The pattern for computing \(\text{Tr} [ \rho O ]\) is shown below.
# Install nestasyncio to run collector inside jupyter.
# This will no longer be necessary in cirq 0.13+.
!pip install nestasyncio
import nest_asyncio
nest_asyncio.apply()
# Define a PauliSumCollector.
collector = cirq.PauliSumCollector(circuit, psum, samples_per_term=10_000)
# Provide a sampler. See also: collector.collect(...).
collector.collect(sampler=cirq.Simulator())
# Estimate the observable.
energy = collector.estimated_energy()
print("Energy:", energy)
Energy: 0.5045
Note that this method uses sampling with a number of samples given by samples_per_term
.
Expectation values can also be computed from NumPy array representations of quantum states. For example, given a state vector we can do the following.
# Get the state vector.
psi = circuit.final_state_vector()
# Compute the expectation value.
energy = psum.expectation_from_state_vector(
state_vector=psi, qubit_map={q: i for i, q in enumerate(qreg)}
)
print("Energy:", energy.real)
Energy: 0.5000000000000002
And given a density matrix, we can compute the expectation via:
# Get the density matrix.
dsim = cirq.DensityMatrixSimulator()
rho = dsim.simulate(circuit).final_density_matrix
# Compute the expectation value.
energy = psum.expectation_from_density_matrix(
state=rho, qubit_map={q: i for i, q in enumerate(qreg)}
)
print("Energy:", energy.real)
Energy: 0.4999999403953552
Pauli expansions
The function cirq.pauli_expansion
can return Pauli basis representations of certain objects. For example, given the circuit \(H \otimes H\) from above:
circuit
The Pauli expansion can be obtained via:
psum = cirq.pauli_expansion(circuit)
print(psum)
0.500*XX+0.500*XZ+0.500*ZX+0.500*ZZ
Generally, the argument to cirq.pauli_expansion
must define the _pauli_expansion_
method or have a small unitary representation. The argument default
can be provided to return a default value if no expansion can be computed.