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_ and _has_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(q(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.5-0.5j] [0.5-0.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 pre-defined - 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, cirq.MeasurementKey(name='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, cirq.MeasurementKey(name='key'), ())(q(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, cirq.MeasurementKey(name='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: 0 Resultant state: [1. 0.]
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 bit-flip 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
(pre-defined or user-defined) 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.
Alternate representations
In addition to the above representations for operators. Cirq also supports some more non-standard representations as well. To convert a set of kraus operators to a choi representation you can do:
depo_channel = cirq.DepolarizingChannel(p=0.01, n_qubits=1)
kraus_rep = cirq.kraus(depo_channel)
print(kraus_rep)
(array([[0.99498744, 0. ], [0. , 0.99498744]]), array([[0. +0.j, 0.05773503+0.j], [0.05773503+0.j, 0. +0.j]]), array([[0.+0.j , 0.-0.05773503j], [0.+0.05773503j, 0.+0.j ]]), array([[ 0.05773503+0.j, 0. +0.j], [ 0. +0.j, -0.05773503+0.j]]))
choi_rep = cirq.kraus_to_choi(kraus_rep)
print(choi_rep)
[[0.99333333+0.j 0. +0.j 0. +0.j 0.98666667+0.j] [0. +0.j 0.00666667+0.j 0. +0.j 0. +0.j] [0. +0.j 0. +0.j 0.00666667+0.j 0. +0.j] [0.98666667+0.j 0. +0.j 0. +0.j 0.99333333+0.j]]
And to get the superoperator representation you can do:
super_rep = cirq.kraus_to_superoperator(kraus_rep)
print(super_rep)
[[0.99333333+0.j 0. +0.j 0. +0.j 0.00666667+0.j] [0. +0.j 0.98666667+0.j 0. +0.j 0. +0.j] [0. +0.j 0. +0.j 0.98666667+0.j 0. +0.j] [0.00666667+0.j 0. +0.j 0. +0.j 0.99333333+0.j]]