This tutorial will teach the basics of how to use cirq. This tutorial will walk through how to use qubits, gates, and operations to create and simulate your first quantum circuit using cirq. It will briefly introduce devices, unitary matrices, decompositions, and optimizers.
Note that this tutorial isn’t a quantum computing 101 tutorial, we assume familiarity of quantum computing at about the level of the textbook “Quantum Computation and Quantum Information” by Nielsen and Chuang.
For more in-depth examples closer to those found in current work, check out our tutorials page.
To begin, please follow the instructions for installing Cirq.
In [0]:
!pip install cirq --quiet
The first part of creating a quantum circuit is to define a set of qubits (also known as a quantum registers) to act on.
Cirq has three main ways of defining qubits:
cirq.NamedQubit
: used to label qubits by an abstract namecirq.LineQubit
: qubits labelled by number in a linear array cirq.GridQubit
: qubits labelled by two numbers in a rectangular lattice.Here are some examples of defining each type of qubit.
In [0]:
import cirq
# Using named qubits can be useful for abstract algorithms
# as well as algorithms not yet mapped onto hardware.
q0 = cirq.NamedQubit('source')
q1 = cirq.NamedQubit('target')
# Line qubits can be created individually
q3 = cirq.LineQubit(3)
# Or created in a range
# This will create LineQubit(0), LineQubit(1), LineQubit(2)
q0, q1, q2 = cirq.LineQubit.range(3)
# Grid Qubits can also be referenced individually
q4_5 = cirq.GridQubit(4,5)
# Or created in bulk in a square
# This will create 16 qubits from (0,0) to (3,3)
qubits = cirq.GridQubit.square(4)
There are also pre-packaged sets of qubits called Devices. These are qubits along with a set of rules of how they can be used. A cirq.Device
can be used to apply adjacency rules and other hardware constraints to a quantum circuit. For our example, we will use the cirq.google.Foxtail
device that comes with cirq. It is a 2x11 grid that mimics early hardware released by Google.
In [56]:
print(cirq.google.Foxtail)
The next step is to use the qubits to create operations that can be used in our circuit. Cirq has two concepts that are important to understand here:
Gate
is an effect that can be applied to a set of qubits. Operation
is a gate applied to a set of qubits.For instance, cirq.H
is the quantum Hadamard_gate) and is a Gate
object. cirq.H(cirq.LineQubit(1))
is an Operation
object and is the Hadamard gate applied to a specific qubit (line qubit number 1).
Many textbook gates are included within cirq. cirq.X
, cirq.Y
, and cirq.Z
refer to the single-qubit Pauli gates. cirq.CZ
, cirq.CNOT
, cirq.SWAP
are a few of the common two-qubit gates. cirq.measure
is a macro to apply a MeasurementGate
to a set of qubits. You can find more, as well as instructions on how to creats your own custom gates, on the Gates documentation page.
Many arithmetic operations can also be applied to gates. Here are some examples:
In [0]:
# Example gates
not_gate = cirq.CNOT
pauli_z = cirq.Z
# Using exponentiation to get square root gates
sqrt_x_gate = cirq.X**0.5
sqrt_iswap = cirq.ISWAP**0.5
# Some gates can also take parameters
sqrt_sqrt_y = cirq.YPowGate(exponent=0.25)
# Example operations
q0, q1 = cirq.LineQubit.range(2)
z_op = cirq.Z(q0)
not_op = cirq.CNOT(q0, q1)
sqrt_iswap_op = sqrt_iswap(q0, q1)
We are now ready to construct a quantum circuit. A Circuit
is a collection of Moment
s. A Moment
is a collection of Operation
s that all act during the same abstract time slice. Each Operation
must have a disjoint set of qubits from the other Operation
s in the Moment
. A Moment
can be thought of as a vertical slice of a quantum circuit diagram.
Circuits can be constructed in several different ways. By default, cirq will attempt to slide your operation into the earliest possible Moment
when you insert it.
In [58]:
circuit = cirq.Circuit()
# You can create a circuit by appending to it
circuit.append(cirq.H(q) for q in cirq.LineQubit.range(3))
# All of the gates are put into the same Moment since none overlap
print(circuit)
In [59]:
# We can also create a circuit directly as well:
print(cirq.Circuit(cirq.SWAP(q, q+1) for q in cirq.LineQubit.range(3)))
Sometimes, you may not want cirq to automatically shift operations all the way to the left. To construct a circuit without doing this, you can create the circuit moment-by-moment or use a different InsertStrategy
, explained more in the Circuit documentation.
In [60]:
# Creates each gate in a separate moment.
print(cirq.Circuit(cirq.Moment([cirq.H(q)]) for q in cirq.LineQubit.range(3)))
One important comnsideration when using real quantum devices is that there are often hardware constraints on the circuit. Creating a circuit with a Device
will allow you to capture some of these requirements. These Device
objects will validate the operations you add to the circuit to make sure that no illegal operations are added.
Let's look at an example using the Foxtail device.
In [61]:
q0 = cirq.GridQubit(0, 0)
q1 = cirq.GridQubit(0, 1)
q2 = cirq.GridQubit(0, 2)
adjacent_op = cirq.CZ(q0, q1)
nonadjacent_op = cirq.CZ(q0, q2)
# This is an unconstrained circuit with no device
free_circuit = cirq.Circuit()
# Both operations are allowed:
free_circuit.append(adjacent_op)
free_circuit.append(nonadjacent_op)
print('Unconstrained device:')
print(free_circuit)
print()
# This is a circuit on the Foxtail device
# only adjacent operations are allowed.
print('Foxtail device:')
foxtail_circuit = cirq.Circuit(device=cirq.google.Foxtail)
foxtail_circuit.append(adjacent_op)
try:
# Not allowed, will throw exception
foxtail_circuit.append(nonadjacent_op)
except ValueError as e:
print('Not allowed. %s' % e)
The results of the application of a quantum circuit can be calculated by a Simulator
. Cirq comes bundled with a simulator that can calculate the results of circuits up to about a limit of 20 qubits. It can be initialized with cirq.Simulator()
.
There are two different approaches to using a simulator:
simulate()
: Since we are classically simulating a circuit, a simulator can directly access and view the resulting wave function. This is useful for debugging, learning, and understanding how circuits will function. run()
: When using actual quantum devices, we can only access the end result of a computation and must sample the results to get a distribution of results. Running the simulator as a sampler mimics this behavior and only returns bit strings as output.Let's try to simulate a 2-qubit "Bell State":
In [62]:
# Create a circuit to generate a Bell State:
# sqrt(2) * ( |00> + |11> )
bell_circuit = cirq.Circuit()
q0, q1 = cirq.LineQubit.range(2)
bell_circuit.append(cirq.H(q0))
bell_circuit.append(cirq.CNOT(q0,q1))
# Initialize Simulator
s=cirq.Simulator()
print('Simulate the circuit:')
results=s.simulate(bell_circuit)
print(results)
print()
# For sampling, we need to add a measurement at the end
bell_circuit.append(cirq.measure(q0, q1, key='result'))
print('Sample the circuit:')
samples=s.run(bell_circuit, repetitions=1000)
# Print a histogram of results
print(samples.histogram(key='result'))
Cirq circuits allow for gates to have symbols as free parameters within the circuit. This is especially useful for variational algorithms, which vary parameters within the circuit in order to optimize a cost function, but it can be useful in a variety of circumstances.
For parameters, cirq uses the library sympy
to add sympy.Symbol
as parameters to gates and operations.
Once the circuit is complete, you can fill in the possible values of each of these parameters with a Sweep
. There are several possibilities that can be used as a sweep:
cirq.Points
: A list of manually specified values for one specific symbol as a sequence of floatscirq.Linspace
: A linear sweep from a starting value to an ending value.cirq.ListSweep
: A list of manually specified values for several different symbols, specified as a list of dictionaries.cirq.Zip
and cirq.Product
: Sweeps can be combined list-wise by zipping them together or through their Cartesian product.A parameterized circuit and sweep together can be run using the simulator or other sampler by changing run()
to run_sweep()
and adding the sweep as a parameter.
Here is an example of sweeping an exponent of a X gate:
In [63]:
import matplotlib.pyplot as plt
import sympy
# Perform an X gate with variable exponent
q = cirq.GridQubit(1,1)
circuit = cirq.Circuit(cirq.X(q) ** sympy.Symbol('t'),
cirq.measure(q, key='m'))
# Sweep exponent from zero (off) to one (on) and back to two (off)
param_sweep = cirq.Linspace('t', start=0, stop=2, length=200)
# Simulate the sweep
s = cirq.Simulator()
trials = s.run_sweep(circuit, param_sweep, repetitions=1000)
# Plot all the results
x_data = [trial.params['t'] for trial in trials]
y_data = [trial.histogram(key='m')[1] / 1000.0 for trial in trials]
plt.scatter('t','p', data={'t': x_data, 'p': y_data})
Out[63]:
In [64]:
print('Unitary of the X gate')
print(cirq.unitary(cirq.X))
print('Unitary of SWAP operator on two qubits.')
q0, q1 = cirq.LineQubit.range(2)
print(cirq.unitary(cirq.SWAP(q0, q1)))
print('Unitary of a sample circuit')
print(cirq.unitary(cirq.Circuit(cirq.X(q0), cirq.SWAP(q0, q1))))
In [65]:
print(cirq.decompose(cirq.H(cirq.LineQubit(0))))
Another example is the 3-qubit Toffoli gate, which is equivalent to a controlled-controlled-X gate. Many devices do not support a three qubit gate, so it is important
In [66]:
q0, q1, q2 = cirq.LineQubit.range(3)
print(cirq.Circuit(cirq.decompose(cirq.TOFFOLI(q0, q1, q2))))
The above decomposes the Toffoli into a simpler set of one-qubit gates and CZ gates at the cost of lengthening the circuit considerably.
Some devices will automatically decompose gates that they do not support. For instance, if we use the Foxtail
device from above, we can see this in action by adding an unsupported SWAP gate:
In [67]:
swap = cirq.SWAP(cirq.GridQubit(0, 0), cirq.GridQubit(0, 1))
print(cirq.Circuit(swap, device=cirq.google.Foxtail))
The last concept in this tutorial is the optimizer. An optimizer can take a circuit and modify it. Usually, this will entail combining or modifying operations to make it more efficient and shorter, though an optimizer can, in theory, do any sort of circuit manipulation.
For example, the MergeSingleQubitGates
optimizer will take consecutive single-qubit operations and merge them into a single PhasedXZ
operation.
In [68]:
q=cirq.GridQubit(1, 1)
optimizer=cirq.MergeSingleQubitGates()
c=cirq.Circuit(cirq.X(q) ** 0.25, cirq.Y(q) ** 0.25, cirq.Z(q) ** 0.25)
print(c)
optimizer.optimize_circuit(c)
print(c)
Other optimizers can assist in transforming a circuit into operations that are native operations on specific hardware devices. You can find more about optimizers and how to create your own elsewhere in the documentation.
After completing this tutorial, you should be able to use gates and operations to construct your own quantum circuits, simulate them, and to use sweeps. It should give you a brief idea of the commonly used
There is much more to learn and try out for those who are interested:
Also, join our cirq-announce mailing list to hear about changes and releases or go to the cirq github to file issues.