Circuits¶
Conceptual Overview¶
The primary representation of quantum programs in Cirq is the Circuit
class. A Circuit
is a collection of Moments
. A Moment
is a collection of Operations
that all act during the same abstract time slice. An Operation
is a some effect that operates on a specific subset of Qubits, the most common type of Operation
is a GateOperation
.
Let’s unpack this.
At the base of this construction is the notion of a qubit. In Cirq, qubits and other quantum objects are identified by instances of subclasses of the Qid base class. Different subclasses of Qid can be used for different purposes. For example, the qubits that Google’s Xmon devices use are often arranged on the vertices of a square grid. For this, the class GridQubit subclasses Qid. For example, we can create a 3 by 3 grid of qubits using
[ ]:
import cirq
qubits = [cirq.GridQubit(x, y) for x in range(3) for y in range(3)]
print(qubits[0])
(0, 0)
The next level up is the notion of a Gate
. A Gate
represents a physical process that occurs on a Qubit
. The important property of a Gate
is that it can be applied to one or more qubits. This can be done via the Gate.on
method itself or via ()
, and doing this turns the Gate
into a GateOperation
.
[ ]:
# This is an Pauli X gate. It is an object instance.
x_gate = cirq.X
# Applying it to the qubit at location (0, 0) (defined above)
# turns it into an operation.
x_op = x_gate(qubits[0])
print(x_op)
X((0, 0))
A Moment
is simply a collection of operations, each of which operates on a different set of qubits, and which conceptually represents these operations as occurring during this abstract time slice. The Moment
structure itself is not required to be related to the actual scheduling of the operations on a quantum computer, or via a simulator, though it can be. For example, here is a Moment
in which Pauli X
and a CZ
gate operate on three qubits:
[ ]:
cz = cirq.CZ(qubits[0], qubits[1])
x = cirq.X(qubits[2])
moment = cirq.Moment([x, cz])
print(moment)
X((0, 2)) and CZ((0, 0), (0, 1))
The above is not the only way one can construct moments, nor even the typical method, but illustrates that a Moment
is just a collection of operations on disjoint sets of qubits.
Finally, at the top level a Circuit
is an ordered series of Moment
objects. The first Moment
in this series contains the first `Operations that will be applied. Here, for example, is a simple circuit made up of two moments:
[ ]:
cz01 = cirq.CZ(qubits[0], qubits[1])
x2 = cirq.X(qubits[2])
cz12 = cirq.CZ(qubits[1], qubits[2])
moment0 = cirq.Moment([cz01, x2])
moment1 = cirq.Moment([cz12])
circuit = cirq.Circuit((moment0, moment1))
print(circuit)
(0, 0): ───@───────
│
(0, 1): ───@───@───
│
(0, 2): ───X───@───
Note that the above is one of the many ways to construct a Circuit
, which illustrates the concept that a Circuit
is an iterable of Moment
objects.
Constructing circuits¶
Constructing Circuits as a series of Moment
objects, with each Moment
being handcrafted, is tedious. Instead, we provide a variety of different ways to create a Circuit
.
One of the most useful ways to construct a Circuit
is by appending onto the Circuit
with the Circuit.append
method.
[ ]:
from cirq.ops import CZ, H
q0, q1, q2 = [cirq.GridQubit(i, 0) for i in range(3)]
circuit = cirq.Circuit()
circuit.append([CZ(q0, q1), H(q2)])
print(circuit)
(0, 0): ───@───
│
(1, 0): ───@───
(2, 0): ───H───
This appended a new moment to the qubit, which we can continue to do:
[ ]:
circuit.append([H(q0), CZ(q1, q2)])
print(circuit)
(0, 0): ───@───H───
│
(1, 0): ───@───@───
│
(2, 0): ───H───@───
In these two examples, we appended full moments, what happens when we append all of these at once?
[ ]:
circuit = cirq.Circuit()
circuit.append([CZ(q0, q1), H(q2), H(q0), CZ(q1, q2)])
print(circuit)
(0, 0): ───@───H───
│
(1, 0): ───@───@───
│
(2, 0): ───H───@───
We see that here we have again created two Moment
objects. How did Circuit
know how to do this? Circuit
’s Circuit.append
method (and its cousin, Circuit.insert
) both take an argument called the InsertStrategy
. By default, InsertStrategy
is InsertStrategy.NEW_THEN_INLINE
.
InsertStrategies¶
InsertStrategy
defines how Operations
are placed in a Circuit
when requested to be inserted at a given location. Here, a location is identified by the index of the Moment
(in the Circuit
) where the insertion is requested to be placed at (in the case of Circuit.append
, this means inserting at the Moment
, at an index one greater than the maximum moment index in the Circuit
).
There are four such strategies: InsertStrategy.EARLIEST
, InsertStrategy.NEW
, InsertStrategy.INLINE
and InsertStrategy.NEW_THEN_INLINE
.
InsertStrategy.EARLIEST
is defined as:
Scans backward from the insert location until a moment with operations touching qubits affected by the operation to insert is found. The operation is added to the moment just after that location.
For example, if we first create an Operation
in a single moment, and then use InsertStrategy.EARLIEST
, Operation
can slide back to this first Moment
if there is space:
[ ]:
from cirq.circuits import InsertStrategy
circuit = cirq.Circuit()
circuit.append([CZ(q0, q1)])
circuit.append([H(q0), H(q2)], strategy=InsertStrategy.EARLIEST)
print(circuit)
(0, 0): ───@───H───
│
(1, 0): ───@───────
(2, 0): ───H───────
After creating the first moment with a CZ
gate, the second append uses the InsertStrategy.EARLIEST
strategy. The H
on q0
cannot slide back, while the H
on q2
can and so ends up in the first Moment
.
Contrast this with the InsertStrategy.NEW
InsertStrategy
:
Every operation that is inserted is created in a new moment.
[ ]:
circuit = cirq.Circuit()
circuit.append([H(q0), H(q1), H(q2)], strategy=InsertStrategy.NEW)
print(circuit)
(0, 0): ───H───────────
(1, 0): ───────H───────
(2, 0): ───────────H───
Here every operator processed by the append ends up in a new moment. InsertStrategy.NEW
is most useful when you are inserting a single operation and do not want it to interfere with other Moments
.
Another strategy is InsertStrategy.INLINE
:
Attempts to add the operation to insert into the moment just before the desired insert location. But, if there’s already an existing operation affecting any of the qubits touched by the operation to insert, a new moment is created instead.
[ ]:
circuit = cirq.Circuit()
circuit.append([CZ(q1, q2)])
circuit.append([CZ(q1, q2)])
circuit.append([H(q0), H(q1), H(q2)], strategy=InsertStrategy.INLINE)
print(circuit)
(0, 0): ───────H───────
(1, 0): ───@───@───H───
│ │
(2, 0): ───@───@───H───
After two initial CZ
between the second and third qubit, we try to insert three H
Operations
. We see that the H
on the first qubit is inserted into the previous Moment
, but the H
on the second and third qubits cannot be inserted into the previous Moment
, so a new Moment
is created.
Finally, we turn to the default strategy:
Creates a new moment at the desired insert location for the first operation, but then switches to inserting operations according to ``InsertStrategy.INLINE``.
[ ]:
circuit = cirq.Circuit()
circuit.append([H(q0)])
circuit.append([CZ(q1,q2), H(q0)], strategy=InsertStrategy.NEW_THEN_INLINE)
print(circuit)
(0, 0): ───H───H───
(1, 0): ───────@───
│
(2, 0): ───────@───
The first append creates a single moment with an H
on the first qubit. Then, the append with the InsertStrategy.NEW_THEN_INLINE
strategy begins by inserting the CZ
in a new Moment
(the InsertStrategy.NEW
in InsertStrategy.NEW_THEN_INLINE
). Subsequent appending is done InsertStrategy.INLINE
, so the next H
on the first qubit is appending in the just created Moment
.
Patterns for Arguments to Append and Insert¶
In the above examples, we used a series of Circuit.append
calls with a list of different Operations
added to the circuit. However, the argument where we have supplied a list can also take more than just list values. For instance:
[ ]:
def my_layer():
yield CZ(q0, q1)
yield [H(q) for q in (q0, q1, q2)]
yield [CZ(q1, q2)]
yield [H(q0), [CZ(q1, q2)]]
circuit = cirq.Circuit()
circuit.append(my_layer())
for x in my_layer():
print(x)
CZ((0, 0), (1, 0))
[cirq.H.on(cirq.GridQubit(0, 0)), cirq.H.on(cirq.GridQubit(1, 0)), cirq.H.on(cirq.GridQubit(2, 0))]
[cirq.CZ.on(cirq.GridQubit(1, 0), cirq.GridQubit(2, 0))]
[cirq.H.on(cirq.GridQubit(0, 0)), [cirq.CZ.on(cirq.GridQubit(1, 0), cirq.GridQubit(2, 0))]]
[ ]:
print(circuit)
(0, 0): ───@───H───H───────
│
(1, 0): ───@───H───@───@───
│ │
(2, 0): ───H───────@───@───
Recall that Python functions with a yield
are generators. Generators are functions that act as iterators. In the above example, we see that we can iterate over my_layer()
. In this case, each of the yield
returns produces what was yielded, and here these are:
Operations
,lists of
Operations
,or lists of
Operations
mixed with lists ofOperations
.
When we pass an iterator to the append
method, Circuit
is able to flatten all of these and pass them as one giant list to Circuit.append
(this also works for Circuit.insert
).
The above idea uses the concept of OP_TREE
. An OP_TREE
is not a class, but a contract. The basic idea is that, if the input can be iteratively flattened into a list of operations, then the input is an OP_TREE
.
A very nice pattern emerges from this structure: define generators for subcircuits, which can vary by size or Operation
parameters.
Another useful method to construct a Circuit
fully formed from an OP_TREE
is to pass the OP_TREE
into Circuit
when initializing it:
[ ]:
circuit = cirq.Circuit(H(q0), H(q1))
print(circuit)
(0, 0): ───H───
(1, 0): ───H───
Slicing and Iterating over Circuits¶
Circuits can be iterated over and sliced. When they are iterated, each item in the iteration is a moment:
[ ]:
circuit = cirq.Circuit(H(q0), CZ(q0, q1))
for moment in circuit:
print(moment)
H((0, 0))
CZ((0, 0), (1, 0))
Slicing a Circuit
, on the other hand, produces a new Circuit
with only the moments corresponding to the slice:
[ ]:
circuit = cirq.Circuit(H(q0), CZ(q0, q1), H(q1), CZ(q0, q1))
print(circuit[1:3])
(0, 0): ───@───────
│
(1, 0): ───@───H───
Especially useful is dropping the last moment (which are often just measurements): circuit[:1]
, or reversing a circuit: circuit[::1]
.
Importing from OpenQASM¶
The QASM importer is in an experimental state and currently only supports a subset of the full OpenQASM spec. Amongst others, classical control, arbitrary gate definitions, and even some of the gates that don’t have a onetoone representation in Cirq, are not yet supported. The functionality should be sufficient to import interesting quantum circuits. Error handling is very simple  on any lexical or syntactical error, a QasmException
is raised.
Importing cirq.Circuit from QASM format¶
Requirements: ply
pip install ply==3.4
The following call will create a circuit defined by the input QASM string:
[ ]:
from cirq.contrib.qasm_import import circuit_from_qasm
circuit = circuit_from_qasm("""
OPENQASM 2.0;
include "qelib1.inc";
qreg q[3];
creg meas[3];
h q;
measure q > meas;
""")
print(circuit)
q_0: ───H───M('meas_0')───
q_1: ───H───M('meas_1')───
q_2: ───H───M('meas_2')───
Supported Control statements¶
Statement 
Cirq support 
Description 
Example 


supported 
Denotes a file in Open QASM format 
OPENQASM 2.0; 

supported (see mapping qubits) 
Declare a named register of qubits 


supported (see mapping classical register to measurement keys) 
Declare a named register of bits 


supported ONLY to include the standard “qelib1.inc” lib for compatibility 
Open and parse another source file 


NOT supported 
Declare a unitary gate 


NOT supported 
Declare an opaque gate 


supported 
Comment a line of text 


supported 
Apply builtin single qubit gate(s) 


supported 
Apply builtin CNOT gate(s) 

measure qubit/qreg 
supported 
Make measurements in Z basis 


NOT supported 
Prepare qubit(s) in 0> 


supported for ONLY the supported subset of standard gates defined in “qelib1.inc” 
Apply a userdefined unitary gate 


NOT supported 
Conditionally apply quantum operation 


NOT supported 
Prevent transformations across this source line 

Gate conversion rules¶
Note: The standard Quantum Experience standard gates are defined in (qelib.inc) based on the U
and CX
builtin instructions, and we could generate them dynamically. Instead, we chose to map them to native Cirq gates explicitly, which results in a more userfriendly circuit.
QE gates 
Cirq translation 
Notes 














NOT supported 



one singlequbit Identity gate is created for each qubit if applied on a register 

NOT supported 
this is the “WAIT gate” for length γ in QE 























































NOT supported 


NOT supported 


NOT supported 


NOT supported 
Mapping quantum registers to qubits¶
For a quantum register qreg qfoo[n];
the QASM importer will create cirq.NamedQubit
s named qfoo_0
..qfoo_<n1>
.
Mapping classical registers to measurement keys¶
For a classical register creg cbar[n];
the QASM importer will create measurement keys named cbar_0
..cbar_<n1>
.