Circuit Components
True-Q™ uses the following high-level classes to define circuits, their components, and their results:
Operation
: a quantum operationCycle
: a clock cycle of a quantum circuitCircuit
: a list of cycles and other metadataResults
: stores the results when a quantum circuit is run on hardware
Operations
The Operation
object is an abstract base class for
general quantum operations. True-Q™ uses four different types of primitive
quantum operations:
Gate
: a generic unitary operation applied during a quantum circuit.NativeGate
: a unitary operation that is native to a given hardware platform.Prep
: an input to a quantum circuit.Meas
: generates the output of a quantum circuit.
Gates
The Gate
class is a thin wrapper over a NumPy matrix. The primary
difference is that the equality for True-Q™ gates ignores an overall phase (that is, a
complex number with unit modulus), which has no physical meaning in quantum information.
Any user-specified gate should be a unitary matrix. Although this is not checked upon
construction for performance reasons, it can be checked as shown below. Common gates for
qubits are aliased for convenience. All instances of Gate
are
immutable.
import trueq as tq
# view the list of aliased gates
print(tq.Gate.ALIASES)
# construct a Pauli X gate and visualize it using plot_mat()
gate = tq.Gate.x
tq.plot_mat(gate.mat)
# construct a gate from a matrix
phased_gate = tq.Gate([[0, -1j], [-1j, 0]])
tq.plot_mat(phased_gate.mat)
# check that equality holds up to an overall phase
print("Equality holds up to a phase:", phased_gate == gate)
# check that equality holds up to an overall phase
scaled_gate = tq.Gate([[0, 2], [2, 0]])
print("scaled_gate is unitary:", scaled_gate.is_unitary)
print("Equality holds for arbitary constants:", phased_gate == scaled_gate)
{'id': Gate.id, 'x': Gate.x, 'y': Gate.y, 'z': Gate.z, 'cx': Gate.cx, 'cy': Gate.cy, 'cz': Gate.cz, 'swap': Gate.swap, 'iswap': Gate.iswap, 'h': Gate.h, 's': Gate.s, 'sx': Gate.sx, 'sy': Gate.sy, 't': Gate.t, 'ch': Gate.ch, 'i': Gate.id, 'sz': Gate.s, 'f': Gate.h, 'cnot': Gate.cx, 'cliff0': Gate.id, 'cliff1': Gate.x, 'cliff2': Gate.y, 'cliff3': Gate.z, 'cliff4': Gate.cliff4, 'cliff5': Gate.sx, 'cliff6': Gate.cliff6, 'cliff7': Gate.sy, 'cliff8': Gate.cliff8, 'cliff9': Gate.s, 'cliff10': Gate.cliff10, 'cliff11': Gate.cliff11, 'cliff12': Gate.h, 'cliff13': Gate.cliff13, 'cliff14': Gate.cliff14, 'cliff15': Gate.cliff15, 'cliff16': Gate.cliff16, 'cliff17': Gate.cliff17, 'cliff18': Gate.cliff18, 'cliff19': Gate.cliff19, 'cliff20': Gate.cliff20, 'cliff21': Gate.cliff21, 'cliff22': Gate.cliff22, 'cliff23': Gate.cliff23, 'id3': Gate.id3, 'x3': Gate.x3, 'y3': Gate.y3, 'z3': Gate.z3, 'cx3': Gate.cx3, 'cz3': Gate.cz3, 'f3': Gate.f3, 'x3pow2': Gate.x3pow2, 'y3pow2': Gate.y3pow2, 'x3pow1': Gate.x3, 'y3pow1': Gate.y3, 'cnot3': Gate.cx3, 'id5': Gate.id5, 'x5': Gate.x5, 'y5': Gate.y5, 'z5': Gate.z5, 'cx5': Gate.cx5, 'cz5': Gate.cz5, 'f5': Gate.f5, 'x5pow2': Gate.x5pow2, 'x5pow3': Gate.x5pow3, 'x5pow4': Gate.x5pow4, 'y5pow2': Gate.y5pow2, 'y5pow3': Gate.y5pow3, 'y5pow4': Gate.y5pow4, 'x5pow1': Gate.x5, 'y5pow1': Gate.y5, 'cnot5': Gate.cx5, 'id7': Gate.id7, 'x7': Gate.x7, 'y7': Gate.y7, 'z7': Gate.z7, 'cx7': Gate.cx7, 'cz7': Gate.cz7, 'f7': Gate.f7, 'x7pow2': Gate.x7pow2, 'x7pow3': Gate.x7pow3, 'x7pow4': Gate.x7pow4, 'x7pow5': Gate.x7pow5, 'x7pow6': Gate.x7pow6, 'y7pow2': Gate.y7pow2, 'y7pow3': Gate.y7pow3, 'y7pow4': Gate.y7pow4, 'y7pow5': Gate.y7pow5, 'y7pow6': Gate.y7pow6, 'x7pow1': Gate.x7, 'y7pow1': Gate.y7, 'cnot7': Gate.cx7}
Equality holds up to a phase: True
scaled_gate is unitary: False
Equality holds for arbitary constants: False
We provide additional methods to determine the generators of a gate and to construct gates via generators.
import trueq as tq
# generators can be obtained for generic gates
print(tq.Gate.h.generators)
# gates can be constructed from generators
gate = tq.Gate.from_generators("XX", 90, "YZ", 180)
gate
{'X': (127.27922061357853+0j), 'Z': (127.27922061357856+1.8815425698445618e-14j)}
- Name:
-
- Gate(XX, YZ)
- Likeness:
-
- CNOT
- Generators:
-
- 'XX': 90.0
- 'YZ': 180.0
- Matrix:
-
NativeGate
NativeGate
is a subclass of Gate
which introduces
three new attributes:
name
is a string giving a name to the gate.(optional)
parameters
is a dictionary that maps parameter names to parameter values.
These attributes are entirely metadata; there are no automatic consistency checks between them and the corresponding matrix. The purpose of these attributes is to provide tools such as transpilers to third-party circuit formats with a fast method of identifying gates without introspecting matrices. This requires trust that these metadata were generated correctly and in good faith.
Instances of NativeGate
are often constructed by
GateFactory
s, which are in turn are typically constructed by
Config
objects (see Example: Configuring Native Gates for
more details).
import trueq as tq
# make a factory that produces z rotations
rz = tq.config.GateFactory.from_matrix("Rz", [[1, 0], [0, "exp(1j*pi*theta/180)"]])
# construct a native gate
rz(90)
- Name:
-
- Rz(theta)
- Aliases:
-
- Gate.s
- Gate.sz
- Gate.cliff9
- Parameters:
-
- theta = 90
- Generators:
-
- 'Z': 90.0
- Matrix:
-
Measurements
Meas
is a singleton class that records where measurements occur
in a Circuit
. This is always a single-qubit operation. The
results
attribute of a circuit must be consistent with the
total number of Meas
that are present.
Typically, all measurement objects are found in the last cycle in a circuit, however the API does not enforce this. Presently, the simulator fails if this is not the case.
Preparations
Prep
is a singleton class that records where state preparations
occur in a circuit. This is always a single-qubit operation. These objects are optional
in circuits and exist mainly:
To allow conversion to and from third-party circuit formats that use them.
To allow the simulator to add state preparation noise.
Cycles
A Cycle
is a round of operations on a quantum device, which is
essentially a wrapper for a dictionary from tuples of qubit labels to instances of
Operation
s discussed in the previous section. Any system that
does not have a specified Operation
is implicitly acted upon by
an identity Gate
.
import trueq as tq
tq.Cycle({(0, 2): tq.Gate.cz, 1: tq.Gate.x})
|
(0, 2):
Gate.cz
|
(1):
Gate.x
|
Note
The order of system labels in the keys is important. For example, the
controlled-X gate is controlled by the first qubit, so that
Cycle({(0, 1): tq.Gate.cx})
flips the state of qubit 1
based
on the state of qubit 0
, whereas Cycle({(1, 0): tq.Gate.cx})
flips the state of 0
based on the state of qubit 1
.
A Cycle
can also be marked with a marker
,
which is an identifier that’s used to label related Cycle
s for
compiler and benchmarking tools. A group of two or more contiguous
Cycle
s which share the same marker
are
treated as a subcircuits during recompilation by various compiler
Pass
es.
Generally speaking, compiler tools are free to merge and expand
Gate
s within a contiguous block of Cycle
s
with equal marker
s; for example, most benchmarking circuits
without marker
s would be compiled down to a single cycle. The
randomly_compile()
function inserts random gates at the edges of
contiguous blocks with equal non-zero markers.
import trueq as tq
tq.Cycle({0: tq.Gate.x, (1, 2): tq.Gate.cz}, marker=3)
3
|
(1, 2):
Gate.cz
|
(0):
Gate.x
|
Results
Experimental results can be stored in a Results
object. This results
object is a thin wrapper for a dictionary class whose keys are concatenated strings
of outcomes (e.g., "001"
) to either relative frequencies or to the number of
observations. A results object also contains attributes such as the timestamp from when
the results were last modified, the dimension of the system, the total number of
measurements (i.e. the length of the strings), and the total number of shots.
Outcomes that were not observed should not be entered.
For instance, the results of flipping two coins 20 times and recording the results might be stored as follows:
import trueq as tq
tq.Results({"10": 5, "00": 2, "01": "10", "11": 3})
Results({'10': 5, '00': 2, '01': '10', '11': 3})
Circuits
A Circuit
objects holds three pieces of information:
A Circuit
is intended to be constructed from a list of
Cycle
s and a Key
containing any relevant
information. Results
s can be passed into the constructor, but
would typically be added after the circuit is executed on hardware or a simulator
as in the following example. Furthermore, two adjacent cycles that are marked with
marker=1
will be treated as a subcircuit during recompilation by various compiler
Pass
es.
import trueq as tq
key = tq.Key(foo="bar", fruits=("apples", "oranges"))
circuit = tq.Circuit(
cycles=[
tq.Cycle({0: tq.Gate.x, 1: tq.Gate.y, (2, 3): tq.Gate.cnot}),
tq.Cycle({(1, 3): tq.Gate.cz, 4: tq.Gate.x}, marker=1),
tq.Cycle({0: tq.Gate.y, (2, 4): tq.Gate.cx}, marker=1),
tq.Cycle({2: tq.Gate.h, 3: tq.Gate.s, 4: tq.Gate.z}),
tq.Cycle({(0, 4): tq.Gate.cnot, 2: tq.Gate.y}, marker=2),
],
key=key,
).measure_all()
circuit.results = {"01000": 3, "01001": 2, "01010": 1, "01011": 4}
circuit
Circuit
|
|||||
|
(2, 3):
Gate.cx
|
(0):
Gate.x
|
(1):
Gate.y
|
  | |
1
|
(1, 3):
Gate.cz
|
(4):
Gate.x
|
  | ||
1
|
(2, 4):
Gate.cx
|
(0):
Gate.y
|
  | ||
|
(2):
Gate.h
|
(3):
Gate.s
|
(4):
Gate.z
|
  | |
2
|
(0, 4):
Gate.cx
|
(2):
Gate.y
|
  | ||
3
|
(0):
Meas()
|
(1):
Meas()
|
(2):
Meas()
|
(3):
Meas()
|
(4):
Meas()
|