Practical Quantum Computing with Cirq
Ryan LaRose
Department of Computational Mathematics, Science, and Engineering, Michigan State University
Department of Physics and Astronomy, Michigan State University
Abstract
In July 2018, the Quantum Computing Report published an in-depth analysis of four major gate-model quantum software platforms: Forest by Rigetti, QISKit by IBM, ProjectQ by ETH Zurich, and the Quantum Developer Kit by Microsoft. Shortly after, Google announced the release of their own quantum software platform: Cirq. In this paper, we provide an overview and comparative analysis of this newly-released platform with respect to the previously-reviewed quantum software platforms. Our analysis proceeds similarly by covering requirements and installation, language syntax through example programs, quantum simulators, and quantum computer capabilities. We additionally cover more advanced features of the platform including quantum circuit manipulation/optimization, device-oriented circuits and quantum compiling, and strong support for variational quantum algorithms and near-term quantum computing.
I. Introduction
Quantum computing is transitioning from a theoretical to practical phase. Historically, researchers have asked questions about the possibilities of speedups through black-box access to abstract, idealized quantum computers. Recently, small, imperfect quantum computers have been fabricated and made available over the cloud. A significant body of literature is emerging as researchers use these devices to solve problems in nuclear physics [Du2018], quantum chemistry [Pe2014], condensed matter [La2018], optimization [Fa2014], number theory [An2018], graph theory [Wa2018], and even quantum computing itself [Kh2018]. While these problems are small and easily handled by conventional computers, the prospect of large-scale quantum computers could quickly change this. Even on current quantum computers, certain contrived problems may soon demonstrate “quantum supremacy” [Ne2017, Ma2018], an exciting landmark in the history of computation.
This theoretical-to-practical progression of the field necessitates access to quantum computers that is much different from black box access on pen and paper. Many institutions in industry and academia have recognized this and began building tools for this purpose, and a slew of startup companies has emerged to fill the gaps in the transition to practical quantum computing. The following diagram shows a partial snapshot of this rapidly evolving landscape:
Schematic diagram showing quantum computing companies and their position in the quantum software stack. “Full-stack providers” focus on all areas of the stack whereas “target providers” focus on particular areas. Because companies that do algorithms/applications frequently do software as well, we have combined these two categories on the right. (Similarly for hardware & compilation and control.)
For most “full-stack” quantum computing companies, access to quantum computers is granted over the cloud, and the interaction between research scientists and quantum computers is mediated by a software platform with API access. To an outside observer, this interaction does not warrant much thought: it just needs to give users the ability to implement quantum gates on qubits. However, many practical considerations emerge when using a software interface to communicate with a real or simulated quantum computer. Among these are the following:
- What gate operations are natively built-in?
- Can a gate that’s not native be implemented into a quantum algorithm? How difficult is it to do this?
- How many quantum computers does the platform give access to?
- Is compilation handled automatically by the software? And to what degree of optimality?
- How do job requests to quantum computers get handled? In a queue, dedicated time?
- What quantum computer simulators can be used to test algorithms? How many qubits can be simulated? Is the simulator noisy or noiseless?
- Does every quantum algorithm have to be programmed manually? Or are some common subroutines built-in?
- What classical programming language is the software written in?
- How easy is it to create, work with, and manipulate quantum circuits?
- How easy is it to parametrize algorithms for near-term quantum computing?
Each of these considerations is important from a practical perspective. For example, an algorithm on many qubits may be better implemented on a platform with a higher-performance simulator; an algorithm with many gates may be better for a platform with higher fidelity qubits; an algorithm with many non-standard gates may be better for a platform with an optimal compiler. Other considerations such as examples, tutorials, and documentation are equally important as they help bring in new users and answer questions of experienced users.
For these reasons, it is both valid and important to evaluate quantum software platforms as more than the simple interface they may appear to be. To this end, the Quantum Computing Report published an article [LaR2018] comparing Forest by Rigetti, QISKit by IBM, ProjectQ by ETH Zurich, and the Quantum Developer Kit by Microsoft. (Subsequently, other articles on quantum software have also been written, e.g. [Fi2018].) Each platform was found to have different strengths and different emphases that determined the set of problems best-suited for the environment. The purpose of this article is to introduce and analyze Cirq in a similar fashion.
To this end, the rest of the article is organized as follows. After briefly commenting on the format of this article, we cover installation, documentation, language syntax, and quantum computer/simulator support in Cirq. We then transition into more advanced features like circuit manipulation and optimization and circuits for a partcular quantum computer. Lastly, we conclude with example programs including a variational quantum algorithm to demonstrate the near-term capabilities of Cirq. Throughout, we refer back to previously covered software platforms to maintain the comparative analysis in our previous installation.
A. Format of the Article
This article was written as a Jupyter Notebook and exported as an HTML file. The HTML file is hosted on the Quantum Computing Report website and the Jupyter notebook is hosted on GitHub. In the HTML version, all code and all outputs are visible in the article, but the code is not able to run. To interactively run the code while reading through the article, see the Jupyter Notebook version. In order to run the code, a working installation of Cirq is required (see Installation below). The Cirq code in the notebook version will be kept up-to-date with future versions/releases of Cirq. The article assumes basic familiarity with quantum computing, for which many good resources now exist.
II. The Basics of Cirq
Institution | Version | GitHub | Documentation | OS |
Google Quantum AI | v0.4.0 | Git | Docs | Mac, Windows, Linux |
Requirements | Classical Language | Quantum Language | Quantum Hardware | Simulator |
Python 3.5 or greater (else Python 2.7) | Python | —- | Foxtail (22 qubits), Bristlecone (72 qubits) | ~20-30 qubits |
Cirq is an open-source Python framework for “creating, editing, and invoking Noisy Intermediate-Scale Quantum (NISQ) circuits” [Ho2018]. The first version of the software was publicly announced in July 2018; the code is still in alpha testing and under development. As such, some features or code included in this article may need modifications for future versions of Cirq. This article should be considered a review for the version of Cirq at the time of writing, which is listed in the table above. (As mentioned above, the Jupyter Notebook version of this article, hosted online at [GitHub](https://github.com/rmlarose), will be kept up to date as future versions of Cirq are released.)
The components of Cirq. When installed onto a computer, Cirq provides a library for working with quantum circuits and a high-performance local quantum circuit simulator. As of December 2018, connection to hardware devices or the “Quantum Engine”/”Quantum Cloud Services” is unavailable to general users, but this is expected to change in the future.
A. Installation
The easiest way to install Cirq
is by using pip via
pip install cirq
at a command line. Without leaving the notebook, executing the cell below will try to install Cirq v0.4.0 on the user’s computer. Alternatively, the source code for Cirq can be obtained from https://github.com/quantumlib/Cirq. For complete installation instructions on multiple platforms, see the documentation at https://cirq.readthedocs.io/en/latest/install.html. Readers who simply wish to read the article without using Cirq can ignore this step.
"""Attempts to pip install Cirq 0.4.0."""
#!pip install --upgrade pip
#!pip install cirq==0.4.0
B. Documentation and Tutorials
The documentation for Cirq contains instructions on installation for all three major operating systems, an in-depth tutorial for the variational quantum eigensolver, and details on three major components of the Cirq library: circuits, gates, and simulation. In addition, the section on Schedules and Devices details how Cirq can be used with specific quantum hardware and reflects the emphasis on near-term quantum computing. The documentation also contains a detailed API reference for the entire library and development guidelines for those who may want to contribute to the source code.
C. Language Syntax
As in our previous coverage of quantum software platforms, we include example programs to demonstrate the language syntax. Below, we implement the “quantum random bit generator” algorithm, which produces either zero or one output that is random by the laws of quantum mechanics. To use the functionality of Cirq, we first import the library (and additional libraries we’ll use throughout the article).
"""Library imports for the article."""
import cirq
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
In what follows we create a circuit and instantiate it with the operations for the algorithm (Hadamard and measure).
"""Create a random number generator circuit using Cirq."""
# get a qubit register
qbits = [cirq.LineQubit(0)]
# get a quantum circuit
circ = cirq.Circuit()
# add the instructions to the circuit
circ.append([cirq.H(qbits[0]),
cirq.measure(qbits[0], key="z")])
Note that Cirq defines qubits to be `LineQubit`s or `GridQubit`s, since these are common constructions in NISQ computers. The former is indexed by one integer, as we have done in Line 4 above, and the latter by two (x, y coordinates). Qubits are commonly defined in lists (or generally iterables) for easy indexing in algorithms. In line 6 above we instantiate a circuit, and in lines 9-10 we append the instructions for the algorithm. (There are multiple ways to add instructions to an algorithm in Cirq. For most of this article, we’ll stick to the above method for simplicity. See Section III.A for alternatives.)
The Cirq library provides text diagram representation of quantum circuits, which can be visualized by printing out the circuit:
"""Print out the random number generator circuit."""
print(circ)
D. Quantum Computers
Cirq does not currently provide cloud access to Google’s quantum computers for general users. Indeed, as per the documentation in Cirq’s engine class:
“In order to run on[e] must have access to the Quantum Engine API. Access to this
API is (as of June 22, 2018) restricted to invitation only.”
Nonetheless, it is known that Google has quantum computers that have been stated to be made available over the cloud in the near future [Ho2018], using Cirq as an interface. Indeed, Cirq already provides details on these devices. For instance, the architecture of the 22-qubit FoxTail computer can be printed out in Cirq by doing:
"""Print out the architecture of the FoxTail quantum computer."""
s = "FoxTail has {} qubits arranged as follows:\n"
print(s.format(len(cirq.google.Foxtail.qubits)))
print(cirq.google.Foxtail)
The 72-qubit Bristlecone computer, with which quantum supremacy may be demonstrated on, can be displayed similarly:
"""Print out the architecture of the Bristlecone quantum computer."""
s = "Bristlecone has {} qubits arranged as follows:\n"
print(s.format(len(cirq.google.Bristlecone.qubits)))
print(cirq.google.Bristlecone)
The Foxtail and Bristlecone processors implement a Controlled-Z gate as their two-qubit unitary (as opposed to a CNOT gate as is common with other architectures, e.g. IBM), but it is unclear to the author which particular single qubit gates are in the native gate set. (This statement is made based off of experiments compiling arbitrary gates into Foxtail/Bristlecone. See Section III.D below.) For reference, the Controlled-Z gate has the following matrix representation:
"""Generate OpenQASM code for the random number generator circuit."""
print(circ.to_qasm())
This functionality makes it very simple to run algorithms generated in Cirq on IBM’s quantum computers. (For instance, by navigating to the IBM Q Experience website and using the online QASM editor. See the Figure below.)
Screenshot from the IBM Q Experience online QASM Editor using Cirq to generate QASM code. The code input into the editor is the same code obtained above by executing `circ.to_qasm()`. By selecting “run” or “simulate” on the IBM Q Experience website, one can execute the quantum algorithm on a real or simulated quantum computer, respectively.
E. Quantum Simulators
Although access to Google’s quantum computers is currently restricted, Cirq provides two quantum computer simulators, the `Simulator` and the `XmonSimulator`, to locally execute quantum algorithms. The `Simulator` works for generic gates that implement their unitary matrix. The `XmonSimulator` is specialized for the native gate set of Google’s quantum computers and can utilize multi-threading to improve performance in certain cases.
To run the random bit generator circuit above with the `Simulator`, we can do the following:
"""Run the random number generator on the XmonSimulator."""
# get the simulator
simulator = cirq.Simulator()
# run the circuit
out = simulator.run(circ, repetitions=50)
# get the results and display them
results = out.histogram(key="z")
print(results)
"""Display the output distribution of a cirq.TrialResult."""
counts = cirq.plot_state_histogram(out)
print("counts =", counts)
For such a simple quantum algorithm, any quantum computer simulator is essentially equivalent. However, as algorithms scale to larger numbers of qubits and larger numbers of gates, runtime of the simulator can become important. The best current methods for classically simulating quantum circuits peak somewhere around 50 qubits due to memory limitations. (Note that this is the memory limitation of the world’s best supercomputers, personal computers are typically much lower around 30 qubits.) For a fixed number of qubits, better simulators can simulate circuits with lower overall runtime and potentially even lower memory usage, which are desirable features for many applications.
Below we test the performance of Cirq’s simulators along these lines. After, we discuss other important capabilities of quantum computer simulators such as noise modeling.
1. Performance of the Simulators
Here we test the performance of Cirq’s simulators using random quantum circuits with different numbers of qubits and total depth. The particular form of the circuit we will consider consists of random single qubit rotations on all qubits, then a layer of entangling CNOT gates with one qubit randomly selected as the control. We define the layer of single qubit rotations plus the layer of entangling CNOTs to have a depth of one. (A circuit diagram is shown below in the article.)
The code for testing the simulator is contained in a separate Python file called `cirq_code.py`. Within this file is a function called `sim_test` which inputs the number of qubits, depth of the circuit, number of times to run the circuit (also called shots or repetitions), and which simulator in Cirq to use. This function creates a random circuit of the form described above, runs it for the desired number of times, then returns the wall clock time for how long it took. An example of using this function is shown in the code cell below.
"""Simulator performance test for a small circuit.
Note that the circuit structure will be random.
"""
# import the simulator test function
from cirq_code import sim_test
# inputs to sim_test
nqubits = 4 # number of qubits
depth = 1 # depth of circuit
nreps = 1 # number of repetitions
verbose = True # verbose output (prints circuit)
sim_type = 0 # 0 <==> XmonSimulator, 1 <==> Simulator
# do the timing test
time = sim_test(nqubits, depth, nreps,
verbose=verbose, sim_type=sim_type)
# display the output
print("\nIt took %0.2f seconds to run the circuit above." % time)
For such a small circuit, the runtime is almost instantaneous. As we scale to larger and larger circuits, however, the complexity of simulating the circuit increases. Below we up the scale to 20 qubits with a depth of 10.
"""Simulator performance test for a larger circuit."""
# inputs to sim_test
nqubits = 20 # number of qubits
depth = 10 # depth of circuit
nreps = 1 # number of repetitions
verbose=False # verbose output (prints circuit)
# do the timing test
time = sim_test(nqubits, depth, nreps, verbose=verbose)
# display the output
print("\nIt took %0.2f seconds to run the circuit." % time)
Plots showing the performance of the `Simulator` (top) and `XmonSimulator` (bottom). The horizontal axis shows the number of qubits and the vertical axis shows the depth as defined above. Dark green represents faster simulation time and bright yellow represents longer simulation time. Values in each square show total overall runtime in seconds, averaged over many runs. Color scales are different for each plot.
As can be seen, the performance of the `XmonSimulator` and `Simulator` are similar in terms of overall runtime. Depending on the computer architecture, increasing the number of threads or shards could increase the performance of the `XmonSimulator`. (This is discussed below in Section 2.) The maximum number of qubits that can be simulated depends on memory of the user’s computer (more RAM implies larger circuits). The largest circuit the author attempted to run with the `Simulator` contained 26 qubits and a depth of 10 as defined above. This circuit finished running in just over one hour. Larger circuit instances throw `MemoryError`s on the author’s computer. (For reference, on a similar circuit with a depth of 20, the state vector simulator in QISKit is able to simulate 25 qubits in 160 seconds, and the C++ simulator in ProjectQ is able to simulate 27 qubits in 504 seconds [LaR2018].)
The resource requirements (memory and runtime) for simulating larger circuits quickly reaches the limitations of current supercomputers. Researchers at Google are very interested in using a quantum computer to simulate some circuit that a classical computer cannot feasibly do, a feat called quantum supremacy. Cirq contains built-in functions for generating so-called “supremacy circuits,” as can be seen below.
"""Display a quantum supremacy circuit on the
first three rows of Bristlecone with a CZ depth of 5.
"""
print(cirq.generate_supremacy_circuit_google_v2_bristlecone(3, 5, 0))
(In the circuit diagram, brackets at the top/bottom are placed when operations are in the same moment but it is impossible to draw them all in the same column. In the second moment, all T gates and the Controlled-Z gates, which are all encompassed by the top/bottom brackets, happen simultaneously.)
As we’ve seen above, this small circuit using only three rows (six qubits) of Bristlecone could be easily simulated by a classical computer. However, extending the circuit to all qubits on Bristlecone generates a circuit that is extremely difficult to classically simulate. Quantum supremacy is expected to be announced within the next few years.
2. Features of the Simulators
While raw performance is an important aspect of simulators, other features such as noise capabilities and special-purpose simulators (e.g., Clifford circuit simulators) are equally if not more important. In particular, noise capabilities allow users to mimic the evolution on real quantum hardware and get better estimates of the performance of their algorithms. The software packages pyQuil and QISKit contain these features.
Currently, Cirq does not contain any obvious support for noisy circuit simulation for either the `Simulator` or `XmonSimulator`. As Cirq is still in alpha testing, however, this feature is likely to be implemented in future releases and is a current work in progress.
For now, notable features of the simulators include multi-threading for the `XmonSimulator` and access to the wave function for both simulators. To get a simulator that uses multiple threads, we can pass an `XmonOptions` object specified with the number of threads into the `XmonSimulator`, as shown below.
"""Get an XmonSimulator with multiple threads (shards)."""
options = cirq.google.sim.XmonOptions(num_shards=2)
simulator = cirq.google.XmonSimulator(options)
This simulator can then be used to execute circuits in the same way as above.
Additionally, there are two types of methods that simulators support, the “run methods” and the “simulate methods.” The “run methods” (`run` and `run_sweep`) emulate quantum computer hardware and only return measurement results. (The `run` method is what we used above to test the simulator performance.) For full access to the wavefunction and for debugging purposes, the “simulate methods” (`simulate`, `simulate_sweep`, and `simulate_moment_steps`) can be used. An example of using a simulate method is shown below.
"""Use the "simulate methods" to get full access to the wavefunction."""
# make a bell state preparation circuit
circ = cirq.Circuit()
qbits = [cirq.LineQubit(x) for x in range(2)]
circ.append([cirq.H(qbits[0]), cirq.CNOT(qbits[0], qbits[1])])
# print out the circuit
print(circ)
# simulate the circuit
res = simulator.simulate(circ)
res
is stored as a SimulationTrialResult
. This output supports several useful features including generating the wavefunction, (reduced) density matrix, and even Dirac notation of the state. A few of these features are demonstrated below."""Use the output of a simulation to generate
the wavefunction, density matrix, and Dirac notation.
"""
# show the wavefunction
print("The wavefunction of the final state is:\n",
res.final_state, end="\n")
# show the Dirac notation for the state
print("\nThe Dirac notation for the final state is:\n",
res.dirac_notation())
# show the density matrix of the total state
print("\nThe density matrix of the final state is:\n",
res.density_matrix())
# show the reduced density matrix of the first qubit
print("\nThe reduced density matrix of the first qubit is:\n",
res.density_matrix([0]))
III. Features of Cirq
In the previous section, we looked at features of the quantum computer simulators of Cirq. In this section we discuss notable features of the entire Cirq platform, including methods for manipulating, parameterizing, optimizing, and compiling circuits. Since each “full-stack” software platform has the same goal (programming a quantum computer), the features of each are what set them apart. Our coverage is not comprehensive (see the documentation for all features) but consists of features we find particularly notable or useful.
A. Manipulating Circuits
As suggested by the name, the main utility of Cirq is working with and manipulating quantum circuits. By “manipulating quantum circuits,” we mean operations of the following form:
- Creating quantum circuits.
- Performing arithmetic with circuits.
- Inserting instructions in a circuit.
- Removing instructions from a circuit.
- Gaining information about a circuit.
In order to demonstrate these operations, we’ll first import a function for creating quantum circuits with random single qubit gates.
"""Get a function for creating circuits with random one-qubit gates."""
from cirq_code import random_circuit
# create a random circuit with 5 qubits and 5 moments
circ, qbits = random_circuit(5, 5)
print(circ)
Circuits are stored as iterables of moments, where a moment is a set of operations that happens at the same time. To see each moment comprising a circuit, we can do the following:
"""Print out each moment in the circuit."""
for moment in circ:
print(moment)
This output tells us exactly what we see in the circuit diagram above as we move from left to right in the circuit.
Arithmetic can be performed on circuits in a natural way. For example, the sum of two circuits is a circuit consisting of all moments from the first circuit then all moments from the second circuit. Circuit multiplication is repeated addition of the same circuit. While simple, these operations are very useful when designing circuits, especially variational circuits. (Note that pyQuil and QISKit also have circuit arithmetic.) In this case, one circuit can be dedicated to, say, state preparation, while the other contains the variational ansatz. The circuit to be executed then consists of the sum of the two.
In many other cases with variational algorithms, it is desirable to remove specific gates from a circuit. Cirq provides a simple built-in method to perform this task, demonstrated below.
"""Clear operations from a circuit."""
circ.clear_operations_touching(qbits[1:4], range(1, 4))
print(circ)
In the same manner, Cirq contains methods for inserting operations into a circuit, such as `batch_insert`, which inserts a sequence of (location, operation) pairs into the circuit, as demonstrated below.
"""Insert operations into a circuit at specific locations."""
circ.batch_insert([(1, cirq.CZ(qbits[2], qbits[3])),
(2, cirq.CNOT(qbits[1], qbits[3]))])
print(circ)
Here we have inserted two-qubit gates (for clarity) into the region in which we previously removed gates. Although these abilities may appear to be contrived, operations of this form are particularly useful for programming variational quantum algorithms.
In addition to manipulating quantum circuits, there are many methods built in to circuits that return useful information. Among these are `all_operations`, `all_qubits`, and `next_moment_operating_on` (also `prev_moment_operating_on`). A full set of circuit methods returning information about the circuit or modifying the circuit in place can be found in the documentation. Lastly, we remark that a circuit can be turned into a unitary matrix by using the method `to_unitary_matrix`.
B. Parameterizing Circuits
Because of Cirq’s strong support for variational quantum algorithms (in which parameters/angles of an algorithm are iteratively changed to minimize an energy or cost function), the platform provides useful features for working with these types of circuits. In particular, two components are particularly notable for this task: `Symbol`s and simulating/running “sweeps” of circuits, which we now elaborate on.
Rather than having to create a new quantum circuit for every new set of variables, Cirq allows gates with parameters to have `Symbol`s, which can be resolved by a `ParamResolver` with a given set of angles. In addition to cleaning up code, this decreases overall runtime of programs by avoiding the task of creating a new quantum circuit for every given set of angles. An example of creating a circuit with `Symbol`s is shown below.
"""Create a circuit that contains a symbol."""
# get a circuit and a qubit
circ = cirq.Circuit()
qbit = cirq.LineQubit(0)
# add a gate with a definite angle
circ.append([cirq.XPowGate(exponent=np.pi / 2)(qbit)])
# add a gate with a symbol which can take any value
sym = cirq.Symbol("t")
gate = cirq.XPowGate(exponent=sym)
circ.append([gate(qbit)])
# add a measurement
circ.append(cirq.measure(qbit, key="z"))
# show the circuit diagram
print(circ)
When we print out the circuit diagram, we see that we have one gate with a definite parameter (the first gate) and another with a symbol t, which we must instantiate with a value before trying to run a circuit. One way to do this is to explicitly use a `ParamResolver` to give t a value, as follows.
"""Run a parameterized circuit by resolving
the circuit with a ParamResolver.
"""
# get a param resolver
param_resolver = cirq.ParamResolver({sym.name: np.pi / 4})
# run the resolved circuit using the param resolver
res = simulator.run(circ, param_resolver, repetitions=500)
# plot the output distribution
vals = cirq.plot_state_histogram(res)
With this method, we only have to change the value of the `ParamResolver` to change the circuit. By looping over many values, we can find the minimum of some cost function defined for a variational algorithm. A slightly simplified way to do this in Cirq is by using the “sweep” methods, e.g. `run_sweep` or `simulate_sweep`, to loop over these values automatically. A simple example of doing so using the same circuit above is as follows.
"""Sweep over a set of values to run a parameterized circuit at."""
# get a "sweep" of values
sweep = cirq.Linspace(key=sym.name, start=0, stop=np.pi, length=100)
# run the circuit at all values in the sweep
res = simulator.run_sweep(circ, sweep, repetitions=1000)
# plot the frequency of zero outputs for all values in the sweep
tvals = [x[0][1] for x in sweep.param_tuples()]
cvals = [res[i].histogram(key="z")[0]/1000 for i in range(len(res))]
plt.plot(tvals, cvals, "-o", linewidth=2)
# plot style
plt.grid(); plt.xlabel("t");
plt.ylabel("Frequency of 0 Measurement");
plt.title("Sweeping over parameters in a circuit");
C. Optimizing Circuits
Quantum compiling consists of rewriting a given algorithm in terms of gates a quantum computer can actually implement (a native gate set or simply gate set). Cirq has functionality to create a circuit for a particular `Device` (see Devices and Schedules below) and automatically compile gates into the native gate set. A similar problem is that of quantum circuit optimization, by which we mean rewriting a quantum circuit to contain as few gates as possible. For example, we would never implement two Pauli operations (X, Y, Z) in sequence because they square to the identity. Similar ideas apply to other gates. This task is critical for NISQ hardware as errors accumulate through gate application and decoherence throughout the algorithm.
Cirq provides several utilities for quantum circuit optimization, which we demonstrate below without specification to a native gate set. (The exact same functionality works for circuits tied to a `Device`.) First, we obtain a random circuit consisting of only single qubit gates for simplicity.
"""Get a random circuit."""
circ, qbits = random_circuit(4, 5)
print(circ)
The first optimization pass we will try is `EjectZ`, which rewrites the circuit by attempting to push all Z gates to the end of the circuit.
"""Push Z gates toward the end of the circuit."""
ejectZ = cirq.optimizers.EjectZ()
ejectZ.optimize_circuit(circ)
print(circ)
As we can see, all Z operations now appear at the right-most portion of the circuit. Next we will perform a similar optimization pass, this time attempting to push X, Y, and `PhasedXPowGate`s to the right.
"""Push X, Y, and PhasedXPow gates toward the end of the circuit."""
ejectPaulis = cirq.optimizers.EjectPhasedPaulis()
ejectPaulis.optimize_circuit(circ)
print(circ)
Another circuit optimization provided by Cirq is merging all single qubit gates into `PhasedXPow` and `PhasedZPow` gates, which we demonstrate below.
"""Merge single qubit gates into PhasedX and PhasedZ gates."""
cirq.merge_single_qubit_gates_into_phased_x_z(circ)
print(circ)
We see that the entire circuit is now expressed in terms of these gates. As a last touch, we can attempt to drop any gates that have a “negligible” (i.e., very small) effect on the overall circuit and drop empty `moment`s in the circuit.
"""Removes operations with a tiny effect and
drop empty moments in the circuit.
"""
# drop negligible gates
drop_neg = cirq.optimizers.DropNegligible()
drop_neg.optimize_circuit(circ)
# drop empty moments
drop_empty = cirq.optimizers.DropEmptyMoments()
drop_empty.optimize_circuit(circ)
print(circ)
Cirq also allows single qubit gates to be merged into arbitrary two by two unitary transformations. These may not in general have a specified name like H, T, etc., so the quantum circuit drawer simply shows matrix elements:
"""Merge single qubit gates."""
merge = cirq.optimizers.MergeSingleQubitGates()
merge.optimize_circuit(circ)
print(circ)
We note that optimized circuits can be run/simulated with either the `Simulator` or `XmonSimulator` and that optimizing circuits before simulating them may result in improved performance. Below we simulate the above optimized circuit and display its wavefunction in Dirac notation.
"""Run the optimized circuit and display its wavefunction in Dirac notation."""
res = simulator.simulate(circ)
print(res.dirac_notation())
The Dirac notation of the wavefunction above corresponds to the same wavefunction we would have obtained by simulating the original circuit. Here, we achieved the same effect with much fewer gates.
Of course, a given quantum computer cannot implement arbitrary gates like the simulator has done above. As mentioned, these gates need to be compiled into operations the computer can implement. Cirq provides this functionality with a `Device`, as we elaborate on below.
D. Devices and Schedules
The existence of `Device`s and `Schedule`s again reflects Cirq’s emphasis on near-term quantum computing. Briefly, a `Device` corresponds to a quantum computer with given constraints such as qubit connectivity and native gate sets. A `Schedule` corresponds to the real-time implementation of operations on a `Device`. Users have full control to specify any quantum computer architecture using a `Device` and can program a circuit for any given quantum computer. Because the documentation contains detailed information on user-defined devices and schedules, we omit this discussion here. Instead, we provide information on `Device`s by analyzing one that is already defined in Cirq. Namely, the Foxtail quantum computer.
In the following code, we create a quantum circuit with its device set as Foxtail:
"""Make a quantum circuit whose device is the
22 qubit Foxtail quantum computer.
"""
# grab the device
foxtail = cirq.google.Foxtail
# get the qubits of foxtail
qbits = sorted(list(foxtail.qubits))
# make a circuit and set foxtail to be its device
circ = cirq.Circuit(device=foxtail)
Once we grab the `Foxtail` device, we can get its qubits and then create a circuit. We can use the method `decompose_operation` of a `Device` to compile/decompose a gate operation. Below we show this for the example of the Hadamard gate.
"""Compile/decompose operations into the native gate set of Foxtail."""
# get the hadamard gate
hgate = cirq.H(qbits[0])
# compile/decompose the gate into native operations
compiled_hgate = foxtail.decompose_operation(hgate)
# print out the gate and its compilation
print(hgate, "=", *compiled_hgate)
"""Adding operations to a circuit with a device compiles them automatically."""
# cnot gate
cnot = cirq.CNOT(qbits[0], qbits[1])
# append the operations
circ.append([hgate, cnot])
# print the circuit
print(circ)
"""Attempt to optimize the circuit. Only commutes
Z across CZ for a single Bell state preparation circuit,
simplifies two sequential Bell state preparation circuits.
(Run the cell above twice to see this.)
"""
ejectZ.optimize_circuit(circ)
ejectPaulis.optimize_circuit(circ)
drop_neg.optimize_circuit(circ)
cirq.merge_single_qubit_gates_into_phased_x_z(circ)
print(circ)
Here we can see the native operations of Foxtail (which are the same for Bristlecone). When running the circuit, these are the gates the computer actually implements. Another useful feature of `Device`s is that they contain timing information for each of the native gates. (When programming a user-defined `Device`, these values can be specified to match an arbitrary computer.) To see this information, we can use the `duration_of` method to see how long each native gate takes to implement.
"""See how long each native operation takes to implement."""
# grab the times of some native gates
ytime = foxtail.duration_of(cirq.Y(qbits[0]))
ztime = foxtail.duration_of(cirq.Z(qbits[0]))
cztime = foxtail.duration_of(cirq.CZ(qbits[0], qbits[1]))
# print them out
print("Y time =", ytime.total_nanos(), "nanoseconds.")
print("Z time =", ztime.total_nanos(), "nanoseconds.")
print("CZ time =", cztime.total_nanos(), "nanoseconds.")
Here we see that the single qubit gate Y takes 20 nanoseconds to implement (on any qubit), and the two-qubit gate Controlled-Z takes over twice as long to implement at 50 nanoseconds. The Z gate takes no time to implement because it is a “virtual” gate. That is, it is implemented by shifting the clock of the software keeping track of the qubit and not by any physical operation acting on the qubit like the Y or Controlled-Z gate.
E. Other Features
To conclude this section, we briefly mention other notable features of Cirq.
In terms of library support, which we define to mean any built-in algorithms or examples demonstrating how to implement algorithms, several companies have used Cirq in collaboration with Google for various projects. For example, Zapata Computing used Cirq to implement a quantum autoencoder, QC Ware used Cirq for implementing QAOA, and Heisenberg Quantum Simulations used Cirq to simulate the Anderson model. The documentation of Cirq contains an in-depth tutorial of a variational quantum algorithm, and the examples folder on Cirq’s GitHub contains detailed Python scripts on Grover’s algorithm, the quantum Fourier Transform, the phase estimation algorithm, the Bernstein-Vazirani algorithm, and an example preparing BCS ground states for superconductors/superfluids.
Cirq also contains integration with OpenFermion, a hardware-agnostic library for simulating fermionic systems on quantum computers, through OpenFermion-Cirq. This library, also in alpha release, is focused on extending the functionality of OpenFermion “by providing routines and tools for using Cirq to compile and compose circuits for quantum simulation algorithms” (from the README).
Other notable features of the Cirq library are quantum channels and stabilizers. These features are still labeled “work in progress” in the documentation, so for fairness. we do not delve into these topics but rather note them as interesting and useful features once they are completed.
IV. Example Algorithms
In this final section, we look at example algorithms programmed in Cirq.
A. Quantum Teleportation Algorithm
For consistency with our comparison of pyQuil, QISKit, ProjectQ, and the Quantum Developer Kit [LaR2018], we include a complete program for the quantum teleportation algorithm below. This algorithm teleports an arbitrary quantum state |?> (which we take to be |1> = X|0> for example below) from one person, Alice, to another, Bob, without either one knowing what the actual state is. More complete descriptions of this protocol can be found in any standard source on quantum computing, for example the ones listed here.
"""Quantum teleportation algorithm written in Cirq."""
# get qubits and a circuit
qbits = [cirq.LineQubit(x) for x in range(3)]
circ = cirq.Circuit()
# Alice wants to teleport |1> to Bob
circ.append(cirq.ops.X(qbits[0]))
# Bell state preparation on Bob's qubits
circ.append([cirq.ops.H(qbits[1]),
cirq.ops.CNOT(qbits[1], qbits[2])])
# Bell state measurement on Alice's qubits
circ.append([cirq.ops.H(qbits[0]),
cirq.ops.CNOT(qbits[0], qbits[1]),
cirq.measure(qbits[0]),
cirq.measure(qbits[1])])
# print out the circuit
print(circ)
To the author’s knowledge, there does not exist an easy way to perform classical conditional operations, such as those required by the quantum teleportation algorithm, in Cirq. However, we can use the “simulate” methods of Cirq to get the state of the third qubit, as shown below. Note that both measurement outcomes are deterministically zero in this situation, so no conditional operations are required.
"""Verify the state of the third qubit is |1> using the simulate methods."""
# simulate the circuit with access to the wavefunction
res = simulator.simulate(circ)
# print out the density matrix, which should be
# [[0, 0],
# [0, 1]]
print(res.density_matrix([2]))
To within floating point precision, this is exactly the density matrix we expect.
B. Variational Algorithm
We now show an example of a variational quantum-classical algorithm using Cirq. The algorithm shown, called Variational Quantum State Diagonalization (VQSD), is used to diagonalize a quantum state (more generally, a positive semidefinite matrix) and was introduced in [La2018]. Without going into all details in the paper, the algorithm works by taking as input two copies of a quantum state ?. For our simple illustrative example, we will use a one qubit state ? = |+><+| which can be prepared by performing a Hadamard gate on each qubit at the start of the algorithm, |+> := H|0>. (Generally, VQSD assumes the state is prepared from some efficient process, a notable example being quantum simulation, for which VQSD can be used for entanglement spectroscopy and other condensed matter applications) Next, the algorithm implements a variational ansatz (or structure of the circuit), which we take to be U(t) = Rz(?/2) Xt Rz(?/2) where t is the only parameter. Finally, a measure of “diagonality” of the state after the ansatz circuit has been applied is computed. For our case, this amounts to a single CNOT gate and a measurement in the computational basis.
In general for variational algorithms, a (classical) optimization algorithm can be used to find the best set of parameters for the ansatz. Here, we simply sweep over the parameter t in our ansatz and plot the results to find the parameter that maximizes the measure of “diagonality” of ?’ := U?U†. The complete program is shown below.
"""Implementation of the VQSD algorithm [La2018]
on a simple one qubit state.
"""
# ====================
# get number of qubits
# ====================
n = 2
qbits = [cirq.LineQubit(x) for x in range(n)]
# =========================
# state preparation circuit
# =========================
prep = cirq.Circuit()
prep.append(cirq.H(qbits[x]) for x in range(n))
# ==============
# ansatz circuit
# ==============
ansatz = cirq.Circuit()
gate0 = cirq.Rz(rads=np.pi / 2)
gate1 = cirq.XPowGate(exponent=cirq.Symbol("t"))
ansatz.append([[gate0(qbits[x]),
gate1(qbits[x]),
gate0(qbits[x])] for x in range(2)],
strategy=cirq.InsertStrategy.EARLIEST)
# ==========================
# "diagonality" test circuit
# ==========================
diag = cirq.Circuit()
diag.append([cirq.CNOT(qbits[1], qbits[0]),
cirq.measure(qbits[0], key="z")])
# ================
# complete circuit
# ================
circ = prep + ansatz + diag
# =========================
# sweep over the parameters
# =========================
reps = 10000
simulator = cirq.google.XmonSimulator()
sweep = (cirq.Linspace(key="t", start=0, stop=1, length=100))
vals = simulator.run_sweep(circ, sweep, repetitions=reps)
# print out the circuit
print(circ)
The complete circuit for the VQSD algorithm is shown above. (Note that this can be easily simplified to contain only one qubit, but here we stick to the general formulation of the algorithm.) We now plot the “degree of diagonality” vs. the parameter t, which ranges from zero to one, in the plot below.
# plot the degree of diagonality vs. the parameter t
tvals = [x[0][1] for x in sweep.param_tuples()]
cvals = [vals[i].histogram(key="z")[0]/reps for i in range(len(vals))]
plt.plot(tvals, cvals, "-o", linewidth=2)
# plot style
plt.grid(); plt.xlabel("t");
plt.ylabel("Degree of Diagonality");
plt.title("Diagonalizing a one-qubit state with VQSD");
Here we can see that the state ?’ := U(t) ? U(t)† is the most diagonal at the value of t = 1/2. A simple classical calculation using matrix arithmetic shows that ?’ is indeed diagonal in the computational basis when t = 1/2.
V. Comparisons and Conclusions
In this final section we offer concluding remarks about Cirq. The purpose of this article was to introduce and analyze Cirq in a similar fashion to the paper [LaR2018] comparing Forest (pyQuil), QISKit, ProjectQ, and the Quantum Developer Kit (Q#). We hope at this point the reader will be able to come to his/her own conclusions about Cirq’s place in the quantum software community. (Recalling again that Cirq is still alpha software.)
In general, we find that Cirq is already a good tool for writing, manipulating, optimizing, compiling, and simulating quantum circuits. For beginners, the learning curve for Cirq is small as the language is written in Python and the syntax/organization of the code is very clean. (We do note that Cirq is written with function annotations in Python which in several cases can be rather verbose.) For experienced users, Cirq exposes details of the hardware to the programmer to maximize effective utilization of near-term processors.
We expect the platform to keep improving as quantum hardware and other simulators are made available. We find the tools for working with variational quantum algorithms, including local simulators as opposed to cloud-based simulators, to be one of the best features of Cirq. (It should be noted that Forest has emphasis on this feature as well, but currently the functionality is smoother with Cirq in the author’s opinion.) The integration with OpenFermion will likely lead to one of the best tools for quantum simulation algorithms, competing with IBM’s QISKit Aqua library. The simulators of Cirq are both good utilities (though not as high-performance as other simulators such as in QISKit Aer or ProjectQ) and will get better with the ongoing work on noise modeling. Additionally, Finally, we expect the quantum channel and stabilizer code features of Cirq to be interesting and useful once they are released.
As mentioned, probably the biggest missing feature of Cirq is the ability to simulate noisy quantum circuits, which Forest and QISKit do very well. (We expect this feature to be released in future versions.) As was seen in the quantum teleportation algorithm, the ability to implement classical operations in circuits, a feature emphasized by the Forest platform, is entirely missing in Cirq. Additionally, the documentation, tutorials, and library support of Cirq are much more sparse than Forest, QISKit, the QDK, and even ProjectQ. (Again, the alpha disclaimer is important here.) Currently, users of Cirq will not be able to connect to real quantum computers as they would with Forest, QISKit, or ProjectQ. Once Foxtail and/or Bristlecone are made available over the cloud, however, this will of course change.
The addition of Cirq to the quantum software community provides excellent tools for working with circuits for near-term quantum computers, and will soon offer connectivity to some of the world’s most advanced quantum computers. We hope that this article provides a fair introduction and overview of the platform. For readers who wish to explore Cirq further, we recommend the API Reference of the documentation.
Acknowledgements
The author thanks Doug Finke from Quantum Computing Report for many useful comments. We both thank Alan Ho from Google for reviewing the article and providing feedback.
References
[An2018] E. R. Anschuetz, J. P. Olson, A. Aspuru-Guzik, and Y. Cao, “Variational Quantum Factoring,” arXiv:1808.08927 [quant-ph], Aug. 2018.
[Du2018] Dumitrescu, E. F. et al. Cloud Quantum Computing of an Atomic Nucleus. Physical Review Letters 120, (2018).
[Fa2014] E. Farhi, J. Goldstone, and S. Gutmann, “A Quantum Approximate Optimization Algorithm,” arXiv:1411.4028 [quant-ph], Nov. 2014.
[Fi2018] M. Fingerhuth, T. Babej, and P. Wittek, “Open source software in quantum computing,” PLOS ONE, vol. 13, no. 12, p. e0208561, Dec. 2018.
[Ho2018] A. Ho and D. Bacon, “Announcing Cirq: An Open Source Framework for NISQ Algorithms,” https://ai.googleblog.com/2018/07/announcing-cirq-open-source-framework.html, Jul. 2018.
[Kh2018] S. Khatri, R. LaRose, A. Poremba, L. Cincio, A. T. Sornborger, and P. J. Coles, “Quantum-assisted quantum compiling,” arXiv:1807.00800 [quant-ph], Jul. 2018.
[La2018] R. LaRose, A. Tikku, É. O’Neel-Judy, L. Cincio, and P. J. Coles, “Variational Quantum State Diagonalization,” arXiv:1810.10506 [quant-ph], Oct. 2018.
[LaR2018] R. LaRose, “Overview and Comparison of Gate Level Quantum Software Platforms,” arXiv:1807.02500 [quant-ph], Jul. 2018.
[Ma2018] I. L. Markov, A. Fatima, S. V. Isakov, and S. Boixo, “Quantum Supremacy Is Both Closer and Farther than It Appears,” arXiv:1807.10749 [quant-ph], Jul. 2018.
[Ne2017] C. Neill et al., “A blueprint for demonstrating quantum supremacy with superconducting qubits,” Sep. 2017.
[Pe2014] A. Peruzzo et al., “A variational eigenvalue solver on a quantum processor,” Nature Communications, vol. 5, no. 1, Dec. 2014.
[Wa2018] Z. Wang, S. Hadfield, Z. Jiang, and E. G. Rieffel, “The Quantum Approximation Optimization Algorithm for MaxCut: A Fermionic View,” Physical Review A, vol. 97, no. 2, Feb. 2018.