2.5. Sequences and Controls#

The concatenation of several gates is the most important contruction for quantum circuits. Besides this, using classical bits as well as qubits to control the execution of gates is also an important building block of quantum circuits. The following constructions are supported by geqo for building up quantum circuits:

  • Sequence

  • QuantumControl

  • ClassicalControl

2.5.1. Sequence#

With the Sequence constructions, several quantum operations can be assembled together to a single operation.

The first two arguments of Sequence are lists of quantum and classical bits in the circuit and the third argument is the list of operations in the circuits. Each entry in the latter list consists of the gate, its quantum targets, and classical targets. In this example, the gates have no parameters like rotation angles.

Qubits and classical bits can be referred to by their name or their position in the provided list of qubits and classical bits, respectively.

from geqo.gates import Hadamard
from geqo.core.quantum_circuit import Sequence
from geqo.operations import Measure
from geqo.simulators.sympy import ensembleSimulatorSymPy

seq = Sequence(
    ["q0", "q1"],
    ["c0"],
    [(Hadamard(), ["q0"], []), (Hadamard(), ["q1"], []), (Measure(1), ["q0"], ["c0"])],
)

sim = ensembleSimulatorSymPy(2, 1)
sim.apply(seq, [0, 1], [0])
sim.ensemble
{(0,): (1/2,
  Matrix([
  [1/2, 1/2, 0, 0],
  [1/2, 1/2, 0, 0],
  [  0,   0, 0, 0],
  [  0,   0, 0, 0]])),
 (1,): (1/2,
  Matrix([
  [0, 0,   0,   0],
  [0, 0,   0,   0],
  [0, 0, 1/2, 1/2],
  [0, 0, 1/2, 1/2]]))}

2.5.2. QuantumControl#

With this construction, a gate can be turned into a gate with control qubits, i.e., the gate is only applied to one or several qubits, if the control qubits have a specified state in the computational basis. The first argument of QuantumControl is a list of 0 and 1 values, which indicate whether a control qubit should lead to the execution of the gate in the basis state \(|0\rangle\) or \(|1\rangle\). The second argument is the gate, which should be turned into the controlled version.

from geqo.gates import Hadamard, PauliX
from geqo.operations.controls import QuantumControl
from geqo.simulators.sympy import ensembleSimulatorSymPy

gate = QuantumControl([0], Hadamard())

sim = ensembleSimulatorSymPy(2, 0)
sim.apply(gate, [0, 1])  # The gate is executed because the control qubit is in state 0.
print("Ensemble 1:")
print(sim.ensemble)  # Show the resulting ensemble.

sim.apply(PauliX(), [0])  # Set the state of the control qubit to 1.
sim.apply(
    gate, [0, 1]
)  # The gate is not executed because the control qubit is in state 1.
print("Ensemble 2:")
print(sim.ensemble)  # Show the resulting ensemble.
Ensemble 1:
{(): (1, Matrix([
[1/2, 1/2, 0, 0],
[1/2, 1/2, 0, 0],
[  0,   0, 0, 0],
[  0,   0, 0, 0]]))}
Ensemble 2:
{(): (1, Matrix([
[0, 0,   0,   0],
[0, 0,   0,   0],
[0, 0, 1/2, 1/2],
[0, 0, 1/2, 1/2]]))}

2.5.3. ClassicalControl#

With this construction, a gate can be turned into a gate with classical control bits, i.e., the gate is only applied to one or several qubits, if the control bits are set to specific values. The first argument of ClassicalControl is a list of 0 and 1 values, which indicate whether a classical control bit should lead to the execution of the gate if it is set to \(0\) or \(1\). The second argument is the gate, which should be turned into the controlled version.

from geqo.gates import Hadamard
from geqo.operations.controls import ClassicalControl
from geqo.simulators.sympy import ensembleSimulatorSymPy
from geqo.initialization import SetBits

gate1 = ClassicalControl([0], Hadamard())  # implement Hadamard if the classical bit = 0
gate2 = ClassicalControl([1], Hadamard())  # implement Hadamard if the classical bit = 1

bits = SetBits("a", 1)

sim = ensembleSimulatorSymPy(1, 1)
sim.setValue("a", [1])

sim.apply(
    gate1, [0], [0]
)  # Apply the gate. SetBits was not used, so the classical bit is 0.
print("Ensemble 1:")
print(sim.ensemble)  # Show the resulting ensemble.

sim.apply(bits, [], [0])  # Set the bit to value 1.
print("Ensemble 2:")
print(sim.ensemble)  # Show the resulting ensemble.

# This operation should be turned off.
sim.apply(
    gate1, [0], [0]
)  # This operation is not executed, because the classical bit is now 1.
print("Ensemble 3:")
print(sim.ensemble)  # Show the resulting ensemble.

# This operation should be applied since classical bit =1
sim.apply(gate2, [0], [0])
print("Ensemble 4:")
print(sim.ensemble)  # Show the resulting ensemble.
Ensemble 1:
{(0,): (1, Matrix([
[1/2, 1/2],
[1/2, 1/2]]))}
Ensemble 2:
{(1,): (1, Matrix([
[1/2, 1/2],
[1/2, 1/2]]))}
Ensemble 3:
{(1,): (1, Matrix([
[1/2, 1/2],
[1/2, 1/2]]))}
Ensemble 4:
{(1,): (1, Matrix([
[1, 0],
[0, 0]]))}