Circuits

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 five 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.

  5. Block: used to prevent compiler optimizations.

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 visualization.plot_mat()
gate = tq.Gate.x
tq.visualization.plot_mat(gate.mat)

# construct a gate from a matrix
phased_gate = tq.Gate([[0, -1j], [-1j, 0]])
tq.visualization.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, 't': Gate.t, 'ch': Gate.ch, 'i': Gate.id, 'cnot': Gate.cx, 'cliff0': Gate.id, 'cliff1': Gate.x, 'cliff2': Gate.y, 'cliff3': Gate.z, 'cliff4': Gate.cliff4, 'cliff5': Gate.cliff5, 'cliff6': Gate.cliff6, 'cliff7': Gate.cliff7, '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}
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)
tq.visualization.plot_mat(gate.mat)
{'X': (127.27922061357853+0j), 'Z': (127.27922061357856+0j)}
../../_images/circuits_1_1.png

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.

  3. (optional) classname is a string specifying the factory that constructed this instance.

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 Configuration for more details).

import trueq as tq

# make a factory that produces z rotations
rz = tq.config.GateFactory(name="Rz", matrix=[[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:
  • GateFactory.Rz
Aliases:
  • Gate.s
  • Gate.cliff9
Parameters:
  • theta = 90
Generators:
  • 'Z': 90.0
Matrix:
  • -0.45 -0.89j 0.89 -0.45j

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.

Block

Block is a singleton class used to trivially occupy some set of qubits in a circuit cycle; it is similar to the barrier statement in Open QASM. Its main purpose is to prevent compilation tools from merging adjacent gates, commuting gates past each other from different cycles. However, the use of immutable cycles is preferred, if possible.

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 gate 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".
imm
(0, 2): Gate.cz
Name:
  • Gate.cz
Aliases:
  • Gate.cz
Likeness:
  • CNOT
Generators:
  • 'IZ': 90.0
  • 'ZI': 90.0
  • 'ZZ': -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.

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

Circuit objects hold three pieces of information.

  1. cycles: the 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. Results 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.

import trueq as tq

circuit = tq.Circuit(cycles=[
    tq.Cycle({(0, ): tq.Gate.x, (1, ): tq.Gate.y}),
    tq.Cycle({(0, 2): tq.Gate.cz, (1, ): tq.Gate.x}),
    tq.Cycle({(3, ): tq.Gate.h, (5, ): tq.Gate.s})
], key=tq.Key(foo="bar"))
circuit.measure_all()
circuit.results = {"01000": 3, "01001": 2, "01010": 1, "01011": 4}
circuit.draw()
0 1 2 3 5 Key: foo: bar Results: Num Shots: 10 Timestamp: 2020-10-21T19:20:02.825087 Labels: (0,) Name: Gate.x Aliases: Gate.x Gate.cliff1 Generators: X: 180.00 1.00 1.00 X Labels: (1,) Name: Gate.y Aliases: Gate.y Gate.cliff2 Generators: Y: 180.00 -1.00j 1.00j Y Imm Labels: (0, 2) Name: Gate.cz Aliases: Gate.cz Locally Equivalent: CNOT Generators: IZ: 90.00 ZI: 90.00 ZZ: -90.00 1.00 1.00 1.00 -1.00 CZ CZ Labels: (1,) Name: Gate.x Aliases: Gate.x Gate.cliff1 Generators: X: 180.00 1.00 1.00 X Labels: (3,) Name: Gate.h Aliases: Gate.h Gate.cliff12 Generators: Z: 127.28 X: 127.28 0.71 0.71 0.71 -0.71 H Labels: (5,) Name: Gate.s Aliases: Gate.s Gate.cliff9 Generators: Z: 90.00 0.71 -0.71j 0.71 0.71j S Imm Labels: (0,) Name: Meas() M Labels: (1,) Name: Meas() M Labels: (2,) Name: Meas() M Labels: (3,) Name: Meas() M Labels: (5,) Name: Meas() M

Examples

../../_images/sphx_glr_recording_results_thumb1.png

Recording Results