1. Circuits in GraphiQ¶
1.1 Overview¶
Circuits are a sequence of operations on quantum and classical registers, that define the generation of a quantum state. In GraphiQ, circuits are represented as directed, acyclic graphs (DAGs), where the nodes represent operations/gates (e.g., Hadamard, CNOT, etc.), and the edges represent the registers on which they act. Circuit objects can be exported in the openQASM format for plotting and inter-operability with other quantum tools.
The photonic quantum circuits have 3 types of registers: 1. Emitter quantum registers: solid state qubits which generate photons. 2. Photonic quantum registers: photon registers which encode a target quantum state. 3. Classical registers: store classical information, e.g., measurement results and classical control flow.
1.2 Operations¶
The GraphiQ library includes standard quantum circuit gate sets, including 1. Operation type: this is the class of the Operation object, and tells us what type of gate the Operation applies (e.g. CNOT, Hadamard) 2. Operation registers: The operation registers define which qubits/classical bits are affect or are affected by the operation. This must specify both the type of the register (classical, photonic quantum, emitter quantum) and its index.
import graphiq.circuit.ops as ops
# single qubit examples
## Hadamard on emitter 0
h = ops.Hadamard(register=0, reg_type="e")
## Pauli X gate on photon 1
paulix = ops.SigmaX(register=1, reg_type="p")
# controlled two-qubit gate example
# CNOT controlled by emitter 0, targeting photon 1
cnot = ops.CNOT(control=0, control_type="e", target=1, target_type="p")
## classically controlled gate & reset examples
# Classical CNOT, where emitter 2 is measured, saved to classical register 0 and conditionally applied to photon 1
ccnot = ops.ClassicalCNOT(
control=2, control_type="e", target=1, target_type="p", c_register=0
)
# Emitter 0 is measured, saved to classical reg 0, conditional gate on photon 0
ccnot_reset = ops.MeasurementCNOTandReset(
control=0, control_type="e", target=0, target_type="p", c_register=0
)
It may be useful to define a single operation object which wraps and applies multiple gates sequentially.
A noise model can be appended to each operation for simulating different environmental noise processes.
1.3 Creating circuits¶
Circuits in GraphiQ are represented as directed, acyclic graphs (DAGs), where the nodes represent operations/gates (e.g., Hadamard, CNOT, etc.), and the edges represent the registers on which they act. In GraphiQ, circuits are represented using a CircuitDAG object.
import graphiq.circuit.ops as ops
from graphiq.circuit.circuit_dag import CircuitDAG
# Create a circuit with 2 quantum registers and 1 classical registers
circuit = CircuitDAG(n_emitter=1, n_photon=1, n_classical=1)
circuit.add(ops.Hadamard(register=0, reg_type="e"))
circuit.add(ops.CNOT(control=0, control_type="e", target=0, target_type="p"))
circuit.draw_dag() # DAG visualization--this shows the internal circuit representation
circuit.draw_circuit(); # this shows the circuit diagram
1.3.1 Adding to the circuit dynamically¶
Circuit size is not fixed, and can be updated dynamically. This is useful in developing circuit-design algorithms.
def print_circuit_size(c):
print(f"Emitter num: {c.n_emitters}")
print(f"Photon num: {c.n_photons}")
print(f"Classical bit num: {c.n_classical}")
c.draw_circuit()
circuit = CircuitDAG()
print(f"Circuit has {circuit.n_emitters}")
circuit.add(ops.CNOT(control=0, control_type="e", target=0, target_type="p"))
print(f"Circuit has {circuit.n_emitters}")
circuit.add_emitter_register()
print(f"Circuit has {circuit.n_emitters}")
1.3.2 Advanced circuit building (CircuitDAG specific)¶
In certain solver applications, it is useful to be able to swap, insert, and remove operations from the circuit.
circuit.draw_dag()
"""
Say we want to add a second CNOT across e0, p3 (after the first one)
The DAG edge we add on is between nodes 5 and 7 (on "e0"), 5 and 6 (on "p3")
Note that this is not obvious, but the solvers typically track this information
"""
print("Insert at: insert operations on specific DAG edges (e.g. 3 and 4)")
circuit.insert_at(
ops.CNOT(control=0, control_type="e", target=3, target_type="p"),
[(5, 6, "p3"), (5, 7, "e0")],
)
circuit.draw_circuit();
Our solvers can also swap components, but there is not explicit circuit class function for this.
1.3.3 Get a sequence of operations¶
In our software, the circuit is responsible for providing the compiler with a sequence of operations to compile. This sequence is built from the nodes of the graph provided in "topological" order:
1.3.1 Saving and visualization¶
OpenQASM is a standard intermediate representation for defining a quantum circuit. Circuit objects in GraphiQ can be saved to and loaded from openQASM text files.
Note: openQASM does not distinguish between photonic qubits and emitter qubits (this distinction is particular to photonic quantum computing applications). In our openQASM scripts, we distinguish photonic and emitter qubits by the naming convention of our registers.
1.3.2 Circuit resources¶
Some prebuilt circuits are available in the benchmarks folder.