The Quantum Circuit

from mpqp import QCircuit

MPQP is focused on gate based quantum computing. As such, the main element of a script using MPQP is the quantum circuit, or QCircuit. The QCircuit contains the data of all gates, measures, and noise models you want to apply to your qubits.

The qubits are only referred by their indices, so one could keep track of specific registers using python features, for instance

>>> circ = QCircuit(6)
>>> targets = range(3)
>>> ancillas = range(3,6)
>>> for i in range(3):
...     circ.add(CNOT(targets[i], ancillas[i]))

could be use to add CNOT gates to your circuit, using the two registers targets and ancillas.

class QCircuit(data, *, nb_qubits=None, nb_cbits=None, label=None)[source]

Bases: object

This class models a quantum circuit.

A circuit is composed of instructions and noise models applied on quantum and/or classical bits. These elements (instructions and noise models) will be called components hereafter.

Parameters
  • data (int | Sequence[Union[Instruction, NoiseModel]]) – Number of qubits or list of components to initialize the circuit with. If the number of qubits is passed, it should be a positive int.

  • nb_qubits (Optional[int]) – Optional number of qubits, in case you input the sequence of instruction and want to hardcode the number of qubits.

  • nb_cbits (Optional[int]) – Number of classical bits. It should be positive.

  • label (Optional[str]) – Name of the circuit.

Examples

>>> circuit = QCircuit(2)
>>> circuit.pretty_print()  
QCircuit : Size (Qubits, Cbits) = (2, 0), Nb instructions = 0
q_0:
q_1:
>>> circuit = QCircuit(5, nb_cbits=2, label="Circuit 1")
>>> circuit.add(Rx(1.23, 3))
>>> circuit.pretty_print()  
QCircuit Circuit 1: Size (Qubits, Cbits) = (5, 2), Nb instructions = 1
q_0: ────────────
q_1: ────────────
q_2: ────────────
     ┌──────────┐
q_3: ┤ Rx(1.23) ├
     └──────────┘
q_4: ────────────
c: 2/════════════
>>> circuit = QCircuit(3, label="NoiseExample")
>>> circuit.add([H(0), T(1), CNOT(0,1), S(2)])
>>> circuit.add(BasisMeasure(list(range(3)), shots=2345))
>>> circuit.add(Depolarizing(prob=0.50, targets=[0, 1]))
>>> circuit.pretty_print()  
QCircuit NoiseExample: Size (Qubits, Cbits) = (3, 3), Nb instructions = 5
Depolarizing noise: probability 0.5 on qubits [0, 1]
     ┌───┐     ┌─┐
q_0: ┤ H ├──■──┤M├───
     ├───┤┌─┴─┐└╥┘┌─┐
q_1: ┤ T ├┤ X ├─╫─┤M├
     ├───┤└┬─┬┘ ║ └╥┘
q_2: ┤ S ├─┤M├──╫──╫─
     └───┘ └╥┘  ║  ║
c: 3/═══════╩═══╩══╩═
            2   0  1
add(components)[source]

Adds a component or a list of component at the end of the circuit.

Parameters

components (Union[Instruction, NoiseModel, Sequence[mpqp.core.instruction.instruction.Instruction | mpqp.noise.noise_model.NoiseModel]]) – Instruction(s) or NoiseModel(s) to append to the circuit.

Examples

>>> circuit = QCircuit(2)
>>> circuit.add(X(0))
>>> circuit.add([CNOT(0, 1), BasisMeasure([0, 1], shots=100)])
>>> circuit.pretty_print()  
QCircuit : Size (Qubits, Cbits) = (2, 2), Nb instructions = 3
     ┌───┐     ┌─┐
q_0: ┤ X ├──■──┤M├───
     └───┘┌─┴─┐└╥┘┌─┐
q_1: ─────┤ X ├─╫─┤M├
          └───┘ ║ └╥┘
c: 2/═══════════╩══╩═
                0  1
>>> circuit.add(Depolarizing(0.3, [0,1], dimension=2, gates=[CNOT]))
>>> circuit.add([Depolarizing(0.02, [0])])
>>> circuit.pretty_print()  
QCircuit : Size (Qubits, Cbits) = (2, 2), Nb instructions = 3
Depolarizing noise: probability 0.3 for gate CNOT
Depolarizing noise: probability 0.02 on qubit 0
     ┌───┐     ┌─┐
q_0: ┤ X ├──■──┤M├───
     └───┘┌─┴─┐└╥┘┌─┐
q_1: ─────┤ X ├─╫─┤M├
          └───┘ ║ └╥┘
c: 2/═══════════╩══╩═
                0  1
append(other, qubits_offset=0)[source]

Appends the circuit at the end (right side) of this circuit, inplace.

If the size of the other is smaller than this circuit, the parameter qubits_offset can be used to indicate at which qubit the other circuit must be added.

Parameters
  • other (QCircuit) – The circuit to append at the end of this circuit.

  • qubits_offset (int) – If the circuit in parameter is smaller, this parameter precise at which qubit (vertically) the circuit will be added.

Raises

NumberQubitsError – if the circuit in parameter is larger than this circuit or if the qubits_offset is too big such that the other circuit would “stick out”.

Return type

None

Example

>>> c1 = QCircuit([CNOT(0,1),CNOT(1,2)])
>>> c2 = QCircuit([X(1),CNOT(1,2)])
>>> c1.append(c2)
>>> print(c1)  
q_0: ──■─────────────────
     ┌─┴─┐     ┌───┐
q_1: ┤ X ├──■──┤ X ├──■──
     └───┘┌─┴─┐└───┘┌─┴─┐
q_2: ─────┤ X ├─────┤ X ├
          └───┘     └───┘
count_gates(gate=None)[source]

Returns the number of gates contained in the circuit. If a specific gate is given in the gate arg, it returns the number of occurrences of this gate.

Parameters

gate (Optional[Type[Gate]]) – The gate for which we want to know its occurrence in this circuit.

Returns

The number of gates (eventually of a specific type) contained in the circuit.

Return type

int

Examples

>>> circuit = QCircuit(
...     [X(0), Y(1), Z(2), CNOT(0, 1), SWAP(0, 1), CZ(1, 2), X(2), X(1), X(0)]
... )
>>> circuit.count_gates()
9
>>> circuit.count_gates(X)
4
>>> circuit.count_gates(Ry)
0
depth()[source]

Computes the depth of the circuit.

Returns

Depth of the circuit.

Return type

int

Examples

>>> QCircuit([CNOT(0, 1), CNOT(1, 2), CNOT(0, 1), X(2)]).depth()
3
>>> QCircuit([CNOT(0, 1), CNOT(1, 2), CNOT(0, 1), Barrier(), X(2)]).depth()
4
display(output='mpl')[source]

Displays this circuit in the desired output format.

For now, this uses the qiskit circuit drawer, so all formats supported by qiskit are supported.

Parameters

output (str) – Format of the output, see docs.quantum.ibm.com/build/circuit-visualization for more information.

Examples

>>> theta = symbols("θ")
>>> circ = QCircuit([
...     P(theta, 0),
...     ExpectationMeasure([0], Observable(np.array([[0, 1], [1, 0]])), shots=1000)
... ])
>>> circ.display("text")
   ┌──────┐
q: ┤ P(θ) ├
   └──────┘
>>> print(circ.display("latex_source"))  
\documentclass[border=2px]{standalone}
\usepackage[braket, qm]{qcircuit}
\usepackage{graphicx}
\begin{document}
\scalebox{1.0}{
\Qcircuit @C=1.0em @R=0.2em @!R { \\
    \nghost{{q} :  } & \lstick{{q} :  } & \gate{\mathrm{P}\,(\mathrm{{\ensuremath{\theta}}})} & \qw & \qw\\
\\ }}
\end{document}
get_measurements()[source]

Returns all the measurements present in this circuit.

Returns

The list of all measurements present in the circuit.

Return type

list[mpqp.core.instruction.measurement.measure.Measure]

Example

>>> circuit = QCircuit([
...     BasisMeasure([0, 1], shots=1000),
...     ExpectationMeasure([1], Observable(np.identity(2)), shots=1000)
... ])
>>> circuit.get_measurements()  
[BasisMeasure([0, 1], shots=1000),
ExpectationMeasure([1], Observable(array([[1.+0.j, 0.+0.j], [0.+0.j, 1.+0.j]], dtype=complex64)), shots=1000)]
inverse()[source]

Generate the inverse (dagger) of this circuit.

Returns

The inverse circuit.

Return type

QCircuit

Examples

>>> c1 = QCircuit([H(0), CNOT(0,1)])
>>> print(c1)  
     ┌───┐
q_0: ┤ H ├──■──
     └───┘┌─┴─┐
q_1: ─────┤ X ├
          └───┘
>>> print(c1.inverse())  
          ┌───┐
q_0: ──■──┤ H ├
     ┌─┴─┐└───┘
q_1: ┤ X ├─────
     └───┘
>>> c2 = QCircuit([S(0), CZ(0,1), H(1), Ry(4.56, 1)])
>>> print(c2)  
     ┌───┐
q_0: ┤ S ├─■──────────────────
     └───┘ │ ┌───┐┌──────────┐
q_1: ──────■─┤ H ├┤ Ry(4.56) ├
             └───┘└──────────┘
>>> print(c2.inverse())  
                         ┌───┐
q_0: ──────────────────■─┤ S ├
     ┌──────────┐┌───┐ │ └───┘
q_1: ┤ Ry(4.56) ├┤ H ├─■──────
     └──────────┘└───┘

# TODO implement, test, fill second example The inverse could be computed in several ways, depending on the definition of the circuit. One can inverse each gate in the circuit, or take the global unitary of the gate and inverse it.

pretty_print()[source]

Provides a pretty print of the QCircuit.

Examples

>>> c = QCircuit([H(0), CNOT(0,1)])
>>> c.pretty_print()  
QCircuit : Size (Qubits, Cbits) = (2, 0), Nb instructions = 2
     ┌───┐
q_0: ┤ H ├──■──
     └───┘┌─┴─┐
q_1: ─────┤ X ├
          └───┘
size()[source]

Provides the size of the circuit, in terms of number of quantum and classical bits.

Returns

A couple (q, c) of integers, with q the number of qubits, and c the number of cbits of this circuit.

Return type

tuple[int, int]

Examples

>>> c1 = QCircuit([CNOT(0,1),CNOT(1,2)])
>>> c1.size()
(3, 0)
>>> c2 = QCircuit(3,nb_cbits=2)
>>> c2.size()
(3, 2)
>>> c3 = QCircuit([CNOT(0,1),CNOT(1,2), BasisMeasure([0,1,2], shots=200)])
>>> c3.size()
(3, 3)
subs(values, remove_symbolic=False)[source]

Substitute the parameters of the circuit with complex values. Optionally also remove all symbolic variables such as \(\pi\) (needed for example for circuit execution).

Since we use sympy for gates’ parameters, values can in fact be anything the subs method from sympy would accept.

Parameters
  • values (dict[Expr | str, Complex]) – Mapping between the variables and the replacing values.

  • remove_symbolic (bool) – If symbolic values should be replaced by their numeric counterpart.

Returns

The circuit with the replaced parameters.

Return type

QCircuit

Examples

>>> theta, k = symbols("θ k")
>>> c = QCircuit(
...     [Rx(theta, 0), CNOT(1,0), CNOT(1,2), X(2), Rk(2,1), H(0), CRk(k, 0, 1),
...      BasisMeasure(list(range(3)), shots=1000)]
... )
>>> print(c)  
     ┌───────┐┌───┐┌───┐                              ┌─┐
q_0: ┤ Rx(θ) ├┤ X ├┤ H ├────────────■─────────────────┤M├───
     └───────┘└─┬─┘└───┘┌─────────┐ │P(2**(1 - k)*pi) └╥┘┌─┐
q_1: ───────────■────■──┤ P(pi/2) ├─■──────────────────╫─┤M├
                   ┌─┴─┐└──┬───┬──┘        ┌─┐         ║ └╥┘
q_2: ──────────────┤ X ├───┤ X ├───────────┤M├─────────╫──╫─
                   └───┘   └───┘           └╥┘         ║  ║
c: 3/═══════════════════════════════════════╩══════════╩══╩═
                                            2          0  1
>>> print(c.subs({theta: np.pi, k: 1}))  
     ┌───────┐┌───┐┌───┐                 ┌─┐
q_0: ┤ Rx(π) ├┤ X ├┤ H ├───────────■─────┤M├───
     └───────┘└─┬─┘└───┘┌────────┐ │P(π) └╥┘┌─┐
q_1: ───────────■────■──┤ P(π/2) ├─■──────╫─┤M├
                   ┌─┴─┐└─┬───┬──┘  ┌─┐   ║ └╥┘
q_2: ──────────────┤ X ├──┤ X ├─────┤M├───╫──╫─
                   └───┘  └───┘     └╥┘   ║  ║
c: 3/════════════════════════════════╩════╩══╩═
                                     2    0  1
tensor(other)[source]

Computes the tensor product of this circuit with the one in parameter.

In the circuit notation, the upper part of the output circuit will correspond to the first circuit, while the bottom part correspond to the one in parameter.

Parameters
  • other (QCircuit) – QCircuit being the second operand of the tensor product with this circuit.

  • other – QCircuit being the second operand of the tensor product with this circuit.

Returns

The QCircuit resulting from the tensor product of this circuit with the one in parameter.

Returns

The QCircuit resulting from the tensor product of this circuit with the one in parameter.

Return type

QCircuit

Example

>>> c1 = QCircuit([CNOT(0,1),CNOT(1,2)])
>>> c2 = QCircuit([X(1),CNOT(1,2)])
>>> print(c1.tensor(c2))  
q_0: ──■───────
     ┌─┴─┐
q_1: ┤ X ├──■──
     └───┘┌─┴─┐
q_2: ─────┤ X ├
          └───┘
q_3: ──────────
     ┌───┐
q_4: ┤ X ├──■──
     └───┘┌─┴─┐
q_5: ─────┤ X ├
          └───┘
to_other_language(language=Language.QISKIT, cirq_proc_id=None)[source]

Transforms this circuit into the corresponding circuit in the language specified in the language arg.

By default, the circuit is translated to the corresponding QuantumCircuit in Qiskit, since it is the interface we use to generate the OpenQASM code.

In the future, we will generate the OpenQASM code on our own, and this method will be used only for complex objects that are not tractable by OpenQASM (like hybrid structures).

Note

Most providers take noise into account at the job level. A notable exception is Braket, where the noise is contained in the circuit object. For this reason you find the noise included in the Braket circuits.

Parameters
  • language (Language) – Enum representing the target language.

  • cirq_proc_id (Optional[str]) – Identifier of the processor for cirq.

Returns

The corresponding circuit in the target language.

Return type

Union[QuantumCircuit, myQLM_Circuit, braket_Circuit, cirq_Circuit]

Examples

>>> circuit = QCircuit([X(0), CNOT(0, 1)])
>>> qc = circuit.to_other_language()
>>> type(qc)
<class 'qiskit.circuit.quantumcircuit.QuantumCircuit'>
>>> circuit2 = QCircuit([H(0), CZ(0,1), Depolarizing(0.6, [0])])
>>> braket_circuit = circuit2.to_other_language(Language.BRAKET)
>>> print(braket_circuit)  
T  : │         0         │         1         │
      ┌───┐ ┌───────────┐       ┌───────────┐
q0 : ─┤ H ├─┤ DEPO(0.6) ├───●───┤ DEPO(0.6) ├─
      └───┘ └───────────┘   │   └───────────┘
                          ┌─┴─┐
q1 : ─────────────────────┤ Z ├───────────────
                          └───┘
T  : │         0         │         1         │
to_qasm2()[source]

Converts this circuit to the corresponding OpenQASM 2 code.

For now, we use an intermediate conversion to a Qiskit QuantumCircuit.

Returns

A string representing the OpenQASM2 code corresponding to this circuit.

Return type

str

Example

>>> circuit = QCircuit([X(0), CNOT(0, 1), BasisMeasure([0, 1], shots=100)])
>>> print(circuit.to_qasm2())  
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
x q[0];
cx q[0],q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];
to_qasm3()[source]

Converts this circuit to the corresponding OpenQASM 3 code.

For now, we use an intermediate conversion to OpenQASM 2, and then a converter from 2 to 3.

Returns

A string representing the OpenQASM3 code corresponding to this circuit.

Return type

str

Example

>>> circuit = QCircuit([X(0), CNOT(0, 1), BasisMeasure([0, 1], shots=100)])
>>> print(circuit.to_qasm3())  
OPENQASM 3.0;
include "stdgates.inc";
qubit[2] q;
bit[2] c;
x q[0];
cx q[0],q[1];
c[0] = measure q[0];
c[1] = measure q[1];
variables()[source]

Returns all the parameters involved in this circuit.

Returns

All the parameters of the circuit.

Return type

set[Basic]

Example

>>> circ = QCircuit([
...     Rx(theta, 0), CNOT(1,0), CNOT(1,2), X(2), Rk(2,1),
...     H(0), CRk(k, 0, 1), ExpectationMeasure([1], obs)
... ])
>>> circ.variables()  
{θ, k}
without_measurements()[source]

Provides a copy of this circuit with all the measurements removed.

Returns

A copy of this circuit with all the measurements removed.

Return type

QCircuit

Example

>>> circuit = QCircuit([X(0), CNOT(0, 1), BasisMeasure([0, 1], shots=100)])
>>> print(circuit)  
     ┌───┐     ┌─┐
q_0: ┤ X ├──■──┤M├───
     └───┘┌─┴─┐└╥┘┌─┐
q_1: ─────┤ X ├─╫─┤M├
          └───┘ ║ └╥┘
c: 2/═══════════╩══╩═
                0  1
>>> print(circuit.without_measurements())  
     ┌───┐
q_0: ┤ X ├──■──
     └───┘┌─┴─┐
q_1: ─────┤ X ├
          └───┘
without_noises()[source]

Provides a copy of this circuit with all the noise models removed.

Returns

A copy of this circuit with all the noise models removed.

Return type

QCircuit

Example

>>> circuit = QCircuit(2)
>>> circuit.add([CNOT(0, 1), Depolarizing(prob=0.4, targets=[0, 1]), BasisMeasure([0, 1], shots=100)])
>>> print(circuit)  
          ┌─┐
q_0: ──■──┤M├───
     ┌─┴─┐└╥┘┌─┐
q_1: ┤ X ├─╫─┤M├
     └───┘ ║ └╥┘
c: 2/══════╩══╩═
           0  1
NoiseModel: Depolarizing(0.4, [0, 1], 1)
>>> print(circuit.without_noises())  
          ┌─┐
q_0: ──■──┤M├───
     ┌─┴─┐└╥┘┌─┐
q_1: ┤ X ├─╫─┤M├
     └───┘ ║ └╥┘
c: 2/══════╩══╩═
           0  1
instructions: list[mpqp.core.instruction.instruction.Instruction]

List of instructions of the circuit.

label

See parameter description.

nb_cbits

See parameter description.

nb_qubits: int

Number of qubits of the circuit.

noises: list[mpqp.noise.noise_model.NoiseModel]

List of noise models attached to the circuit.