Circuit Components

True-Q™ uses the following high-level classes to define circuits, their components, and their results:

  • Operation: a quantum operation

  • Cycle: a clock cycle of a quantum circuit

  • Circuit: a list of cycles and other metadata

  • Results: 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:

  1. Gate: a generic unitary operation applied during a quantum circuit.

  2. NativeGate: a unitary operation that is native to a given hardware platform.

  3. Prep: an input to a quantum circuit.

  4. 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
../../_images/circuits_0_1.png ../../_images/circuits_0_2.png

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)}
True-Q formatting will not be loaded without trusting this notebook or rerunning the affected cells. Notebooks can be marked as trusted by clicking "File -> Trust Notebook".
Name:
  • Gate(XX, YZ)
Likeness:
  • CNOT
Generators:
  • 'XX': 90.0
  • 'YZ': 180.0
Matrix:
  • 0.71j -0.71 -0.71j 0.71 0.71 -0.71j -0.71 0.71j

NativeGate

NativeGate is a subclass of Gate which introduces three new attributes:

  1. name is a string giving a name to the gate.

  2. (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 GateFactorys, 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)
True-Q formatting will not be loaded without trusting this notebook or rerunning the affected cells. Notebooks can be marked as trusted by clicking "File -> Trust Notebook".
Name:
  • Rz(theta)
Aliases:
  • Gate.s
  • Gate.sz
  • Gate.cliff9
Parameters:
  • theta = 90
Generators:
  • 'Z': 90.0
Matrix:
  • 0.71 -0.71j 0.71 0.71j

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:

  1. To allow conversion to and from third-party circuit formats that use them.

  2. 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 Operations 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})
True-Q formatting will not be loaded without trusting this notebook or rerunning the affected cells. Notebooks can be marked as trusted by clicking "File -> Trust Notebook".
 
Marker 0
Compilation tools may only recompile cycles with equal markers.
(0, 2): Gate.cz
Name:
  • Gate.cz
Aliases:
  • Gate.cz
Likeness:
  • CNOT
Generators:
  • 'ZZ': -90.0
  • 'ZI': 90.0
  • 'IZ': 90.0
Matrix:
  • 1.00 1.00 1.00 -1.00
(1): Gate.x
Name:
  • Gate.x
Aliases:
  • Gate.x
  • Gate.cliff1
Generators:
  • 'X': 180.0
Matrix:
  • 1.00 1.00

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 Cycles for compiler and benchmarking tools. A group of two or more contiguous Cycles which share the same marker are treated as a subcircuits during recompilation by various compiler Passes.

Generally speaking, compiler tools are free to merge and expand Gates within a contiguous block of Cycles with equal markers; for example, most benchmarking circuits without markers 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)
True-Q formatting will not be loaded without trusting this notebook or rerunning the affected cells. Notebooks can be marked as trusted by clicking "File -> Trust Notebook".
3
Marker 3
Compilation tools may only recompile cycles with equal markers.
(1, 2): Gate.cz
Name:
  • Gate.cz
Aliases:
  • Gate.cz
Likeness:
  • CNOT
Generators:
  • 'ZZ': -90.0
  • 'ZI': 90.0
  • 'IZ': 90.0
Matrix:
  • 1.00 1.00 1.00 -1.00
(0): Gate.x
Name:
  • Gate.x
Aliases:
  • Gate.x
  • Gate.cliff1
Generators:
  • 'X': 180.0
Matrix:
  • 1.00 1.00

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:

  1. cycles: a list of trueq.Cycles that define the quantum circuit.

  2. key: a Key containing any metadata to be used to select and process individual circuits.

  3. results: a Results object that is used to store the outcomes obtained when the circuit is executed on quantum hardware.

A Circuit is intended to be constructed from a list of Cycles and a Key containing any relevant information. Resultss 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 Passes.

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
True-Q formatting will not be loaded without trusting this notebook or rerunning the affected cells. Notebooks can be marked as trusted by clicking "File -> Trust Notebook".
Circuit
Key:
  • foo: bar
  • fruits: ('apples', 'oranges')
 
Marker 0
Compilation tools may only recompile cycles with equal markers.
(2, 3): Gate.cx
Name:
  • Gate.cx
Aliases:
  • Gate.cx
  • Gate.cnot
Likeness:
  • CNOT
Generators:
  • 'ZX': -90.0
  • 'IX': 90.0
  • 'ZI': 90.0
Matrix:
  • 1.00 1.00 1.00 1.00
(0): Gate.x
Name:
  • Gate.x
Aliases:
  • Gate.x
  • Gate.cliff1
Generators:
  • 'X': 180.0
Matrix:
  • 1.00 1.00
(1): Gate.y
Name:
  • Gate.y
Aliases:
  • Gate.y
  • Gate.cliff2
Generators:
  • 'Y': 180.0
Matrix:
  • -1.00j 1.00j
 
1
Marker 1
Compilation tools may only recompile cycles with equal markers.
(1, 3): Gate.cz
Name:
  • Gate.cz
Aliases:
  • Gate.cz
Likeness:
  • CNOT
Generators:
  • 'ZZ': -90.0
  • 'ZI': 90.0
  • 'IZ': 90.0
Matrix:
  • 1.00 1.00 1.00 -1.00
(4): Gate.x
Name:
  • Gate.x
Aliases:
  • Gate.x
  • Gate.cliff1
Generators:
  • 'X': 180.0
Matrix:
  • 1.00 1.00
 
1
Marker 1
Compilation tools may only recompile cycles with equal markers.
(2, 4): Gate.cx
Name:
  • Gate.cx
Aliases:
  • Gate.cx
  • Gate.cnot
Likeness:
  • CNOT
Generators:
  • 'ZX': -90.0
  • 'IX': 90.0
  • 'ZI': 90.0
Matrix:
  • 1.00 1.00 1.00 1.00
(0): Gate.y
Name:
  • Gate.y
Aliases:
  • Gate.y
  • Gate.cliff2
Generators:
  • 'Y': 180.0
Matrix:
  • -1.00j 1.00j
 
 
Marker 0
Compilation tools may only recompile cycles with equal markers.
(2): Gate.h
Name:
  • Gate.h
Aliases:
  • Gate.h
  • Gate.f
  • Gate.cliff12
Generators:
  • 'X': 127.279
  • 'Z': 127.279
Matrix:
  • 0.71 0.71 0.71 -0.71
(3): Gate.s
Name:
  • Gate.s
Aliases:
  • Gate.s
  • Gate.sz
  • Gate.cliff9
Generators:
  • 'Z': 90.0
Matrix:
  • 1.00 1.00j
(4): Gate.z
Name:
  • Gate.z
Aliases:
  • Gate.z
  • Gate.cliff3
Generators:
  • 'Z': 180.0
Matrix:
  • 1.00 -1.00
 
2
Marker 2
Compilation tools may only recompile cycles with equal markers.
(0, 4): Gate.cx
Name:
  • Gate.cx
Aliases:
  • Gate.cx
  • Gate.cnot
Likeness:
  • CNOT
Generators:
  • 'ZX': -90.0
  • 'IX': 90.0
  • 'ZI': 90.0
Matrix:
  • 1.00 1.00 1.00 1.00
(2): Gate.y
Name:
  • Gate.y
Aliases:
  • Gate.y
  • Gate.cliff2
Generators:
  • 'Y': 180.0
Matrix:
  • -1.00j 1.00j
 
3
Marker 3
Compilation tools may only recompile cycles with equal markers.
(0): Meas()
Name:
  • Meas()
(1): Meas()
Name:
  • Meas()
(2): Meas()
Name:
  • Meas()
(3): Meas()
Name:
  • Meas()
(4): Meas()
Name:
  • Meas()