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 for all gates, measurements, and noise models you want to apply to your qubits.

The qubits are only referred to 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]))
>>> print(circ)  
q_0: ──■────────────

q_1: ──┼────■───────
       │    │
q_2: ──┼────┼────■──
     ┌─┴─┐  │    │
q_3: ┤ X ├──┼────┼──
     └───┘┌─┴─┐  │
q_4: ─────┤ X ├──┼──
          └───┘┌─┴─┐
q_5: ──────────┤ X ├
               └───┘

could be used 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 to quantum and/or classical bits. These elements (instructions and noise models) will be called components hereafter.

Parameters
  • data (int | Sequence[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 integer.

  • nb_qubits (Optional[int]) – Optional number of qubits, in case you input the sequence of instructions 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([Rx(1.23, 2)], nb_qubits=4, nb_cbits=2, label="Circuit 1")
>>> circuit.pretty_print()  
QCircuit Circuit 1: Size (Qubits, Cbits) = (4, 2), Nb instructions = 1
q_0: ────────────
q_1: ────────────
     ┌──────────┐
q_2: ┤ Rx(1.23) ├
     └──────────┘
q_3: ────────────
c: 2/════════════
>>> circuit = QCircuit(3, label="NoiseExample")
>>> circuit.add([H(0), T(1), CNOT(0,1), S(2)])
>>> circuit.add(BasisMeasure(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: on qubits [0, 1] with probability 0.5
     ┌───┐     ┌─┐
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 noise model(s) to append to the circuit.

Examples

>>> circuit = QCircuit(2)
>>> circuit.add(X(0))
>>> circuit.add([CNOT(0, 1), BasisMeasure(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, dimension=2, gates=[CNOT]))
>>> circuit.add([Depolarizing(0.02, [0])])
>>> circuit.pretty_print()  
QCircuit : Size (Qubits, Cbits) = (2, 2), Nb instructions = 3
Depolarizing noise: for gate CNOT with probability 0.3 and dimension 2
Depolarizing noise: on qubit 0 with probability 0.02
     ┌───┐     ┌─┐
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.

This method can be shorthanded with the += operator (while + performs the same operation without the inplace factor.)

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 determines 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

Examples

>>> 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 ├
          └───┘     └───┘
>>> c1 = QCircuit([CNOT(0,1),CNOT(1,2)])
>>> c2 = QCircuit([X(1),CNOT(1,2)])
>>> c1 += c2
>>> print(c1)  
q_0: ──■─────────────────
     ┌─┴─┐     ┌───┐
q_1: ┤ X ├──■──┤ X ├──■──
     └───┘┌─┴─┐└───┘┌─┴─┐
q_2: ─────┤ X ├─────┤ X ├
          └───┘     └───┘
>>> c1 = QCircuit([CNOT(0,1),CNOT(1,2)])
>>> c2 = QCircuit([X(1),CNOT(1,2)])
>>> print(c1 + c2)  
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 whose occurrence we want to determine in this circuit.

Returns

The number of gates (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 the 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)])
>>> 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}
inverse()[source]

Generate the inverse (dagger) of this circuit.

Returns

The inverse circuit.

Return type

QCircuit

Examples

>>> c1 = QCircuit([S(0), CZ(0,1), H(1), Ry(4.56, 1)])
>>> print(c1)  
     ┌───┐
q_0: ┤ S ├─■──────────────────
     └───┘ │ ┌───┐┌──────────┐
q_1: ──────■─┤ H ├┤ Ry(4.56) ├
             └───┘└──────────┘
>>> print(c1.inverse())  
                          ┌────┐
q_0: ───────────────────■─┤ S† ├
     ┌───────────┐┌───┐ │ └────┘
q_1: ┤ Ry(-4.56) ├┤ H ├─■───────
     └───────────┘└───┘
 >>> c2 = QCircuit([S(0), CRk(2, 0, 1), Barrier(), H(1), Ry(4.56, 1)])
>>> print(c2)  
     ┌───┐          ░
q_0: ┤ S ├─■────────░──────────────────
     └───┘ │P(π/2)  ░ ┌───┐┌──────────┐
q_1: ──────■────────░─┤ H ├┤ Ry(4.56) ├
                    ░ └───┘└──────────┘
>>> print(c2.inverse())  
                        ░           ┌────┐
q_0: ───────────────────░──■────────┤ S† ├
     ┌───────────┐┌───┐ ░  │P(-π/2) └────┘
q_1: ┤ Ry(-4.56) ├┤ H ├─░──■──────────────
     └───────────┘└───┘ ░
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 the 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(shots=200)])
>>> c3.size()
(3, 3)
subs(values, remove_symbolic=False)[source]

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

Since we use sympy for the gate parameters, the 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) – Whether symbolic values should be replaced by their numeric counterparts.

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(shots=1000)]
... )
>>> print(c)  
     ┌───────┐┌───┐┌───┐                             ┌─┐
q_0: ┤ Rx(θ) ├┤ X ├┤ H ├───────────■─────────────────┤M├───
     └───────┘└─┬─┘└───┘┌────────┐ │P(2**(1 - k)*pi) └╥┘┌─┐
q_1: ───────────■────■──┤ P(π/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 that in parameter.

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

This method can be shorthanded with the @ operator.

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 that in parameter.

Returns

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

Return type

QCircuit

Examples

>>> 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 ├
          └───┘
>>> c1 = QCircuit([CNOT(0,1),CNOT(1,2)])
>>> c2 = QCircuit([X(1),CNOT(1,2)])
>>> print(c1 @ c2)  
q_0: ──■───────
     ┌─┴─┐
q_1: ┤ X ├──■──
     └───┘┌─┴─┐
q_2: ─────┤ X ├
          └───┘
q_3: ──────────
     ┌───┐
q_4: ┤ X ├──■──
     └───┘┌─┴─┐
q_5: ─────┤ X ├
          └───┘
to_matrix()[source]

Compute the unitary matrix associated to this circuit.

Returns

a unitary matrix representing this circuit

Return type

npt.NDArray[np.complex64]

Examples

>>> c = QCircuit([H(0), CNOT(0,1)])
>>> pprint(c.to_matrix())
[[0.70711, 0      , 0.70711 , 0       ],
 [0      , 0.70711, 0       , 0.70711 ],
 [0      , 0.70711, 0       , -0.70711],
 [0.70711, 0      , -0.70711, 0       ]]
to_other_language(language=Language.QISKIT, cirq_proc_id=None, translation_warning=True)[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 this 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 with OpenQASM (like hybrid structures).

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

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

  • translation_warning (bool) –

Returns

The corresponding circuit in the target language.

Return type

QuantumCircuit | myQLM_Circuit | braket_Circuit | cirq_Circuit | str

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]), BasisMeasure()])
>>> print(circuit2.to_other_language(Language.BRAKET))  
T  : │         0         │         1         │
      ┌───┐ ┌───────────┐       ┌───────────┐
q0 : ─┤ H ├─┤ DEPO(0.6) ├───●───┤ DEPO(0.6) ├─
      └───┘ └───────────┘   │   └───────────┘
                          ┌─┴─┐
q1 : ─────────────────────┤ Z ├───────────────
                          └───┘
T  : │         0         │         1         │
>>> print(circuit2.to_other_language(Language.QASM2))  
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q[0];
cz q[0],q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];
>>> print(circuit2.to_other_language(Language.QASM3, translation_warning=False))  
OPENQASM 3.0;
include "stdgates.inc";
qubit[2] q;
bit[2] c;
h q[0];
cz q[0],q[1];
c[0] = measure q[0];
c[1] = measure q[1];

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 will find the noise included in the Braket circuits.

variables()[source]

Returns all the symbolic 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(obs, [1])
... ])
>>> circ.variables()  
{θ, k}
without_breakpoints()[source]

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

Returns

A copy of this circuit with all the breakpoints removed.

Return type

QCircuit

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(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(shots=100)])
>>> print(circuit)  
          ┌─┐
q_0: ──■──┤M├───
     ┌─┴─┐└╥┘┌─┐
q_1: ┤ X ├─╫─┤M├
     └───┘ ║ └╥┘
c: 2/══════╩══╩═
           0  1
NoiseModel: Depolarizing(0.4, [0, 1])
>>> print(circuit.without_noises())  
          ┌─┐
q_0: ──■──┤M├───
     ┌─┴─┐└╥┘┌─┐
q_1: ┤ X ├─╫─┤M├
     └───┘ ║ └╥┘
c: 2/══════╩══╩═
           0  1
property breakpoints: list[Breakpoint]

Returns the breakpoints of the circuit in order.

property gates: list[Gate]

Retrieve all the gates from the instructions in the circuit.

Returns

The list of all gates present in the circuit.

Example

>>> circuit = QCircuit([H(0), Barrier(), CNOT(0, 1), BasisMeasure()])
>>> circuit.gates
[H(0), CNOT(0, 1)]
gphase: float

Stores the global phase (angle) arising from the Qiskit conversion of CustomGates to OpenQASM2. It is used to correct the global phase when the job type is STATE_VECTOR, and when this circuit contains CustomGate.

instructions: list[mpqp.core.instruction.instruction.Instruction]

List of instructions of the circuit.

label

See parameter description.

property measurements: list[Measure]

Returns all the measurements present in this circuit.

Returns

The list of all measurements present in the circuit.

Example

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

See parameter description.

property nb_qubits: int

Number of qubits of the circuit.

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

List of noise models attached to the circuit.