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 ofcomponent
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 parameterqubits_offset
can be used to indicate at which qubit theother
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 theother
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
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, withq
the number of qubits, andc
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, thevalues
can in fact be anything thesubs
method fromsympy
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
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
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
- 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
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
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.