Aller au contenu principal

La classe Operator

Versions des packages

Le code de cette page a été développé avec les dépendances suivantes. Nous recommandons d'utiliser ces versions ou des versions plus récentes.

qiskit[all]~=2.3.0

Cette page montre comment utiliser la classe Operator. Pour une vue d'ensemble des représentations d'opérateurs dans Qiskit, incluant la classe Operator et d'autres, consulte Vue d'ensemble des classes d'opérateurs.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.circuit.library import CXGate, RXGate, XGate
from qiskit.quantum_info import Operator, Pauli, process_fidelity

Convertir des classes en Operators

Plusieurs autres classes dans Qiskit peuvent être directement converties en objet Operator via la méthode d'initialisation de l'opérateur. Par exemple :

  • les objets Pauli
  • les objets Gate et Instruction
  • les objets QuantumCircuit

Note que ce dernier point signifie que tu peux utiliser la classe Operator comme simulateur unitaire pour calculer la matrice unitaire finale d'un circuit quantique, sans avoir à appeler un backend simulateur. Si le circuit contient des opérations non supportées, une exception est levée. Les opérations non supportées sont : measure, reset, les opérations conditionnelles, ou une porte qui n'a pas de définition matricielle ni de décomposition en portes avec définitions matricielles.

# Create an Operator from a Pauli object

pauliXX = Pauli("XX")
Operator(pauliXX)
Operator([[0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
[0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]],
input_dims=(2, 2), output_dims=(2, 2))
# Create an Operator for a Gate object
Operator(CXGate())
Operator([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
[0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j]],
input_dims=(2, 2), output_dims=(2, 2))
# Create an operator from a parameterized Gate object
Operator(RXGate(np.pi / 2))
Operator([[0.70710678+0.j        , 0.        -0.70710678j],
[0. -0.70710678j, 0.70710678+0.j ]],
input_dims=(2,), output_dims=(2,))
# Create an operator from a QuantumCircuit object
circ = QuantumCircuit(10)
circ.h(0)
for j in range(1, 10):
circ.cx(j - 1, j)

# Convert circuit to an operator by implicit unitary simulation
Operator(circ)
Operator([[ 0.70710678+0.j,  0.70710678+0.j,  0.        +0.j, ...,
0. +0.j, 0. +0.j, 0. +0.j],
[ 0. +0.j, 0. +0.j, 0.70710678+0.j, ...,
0. +0.j, 0. +0.j, 0. +0.j],
[ 0. +0.j, 0. +0.j, 0. +0.j, ...,
0. +0.j, 0. +0.j, 0. +0.j],
...,
[ 0. +0.j, 0. +0.j, 0. +0.j, ...,
0. +0.j, 0. +0.j, 0. +0.j],
[ 0. +0.j, 0. +0.j, 0.70710678+0.j, ...,
0. +0.j, 0. +0.j, 0. +0.j],
[ 0.70710678+0.j, -0.70710678+0.j, 0. +0.j, ...,
0. +0.j, 0. +0.j, 0. +0.j]],
input_dims=(2, 2, 2, 2, 2, 2, 2, 2, 2, 2), output_dims=(2, 2, 2, 2, 2, 2, 2, 2, 2, 2))

Utiliser des Operators dans des circuits

Les Operators unitaires peuvent être directement insérés dans un QuantumCircuit via la méthode QuantumCircuit.append. Cela convertit l'Operator en objet UnitaryGate, qui est ajouté au circuit.

Si l'opérateur n'est pas unitaire, une exception est levée. Cela peut être vérifié avec la fonction Operator.is_unitary(), qui renvoie True si l'opérateur est unitaire et False sinon.

# Create an operator
XX = Operator(Pauli("XX"))

# Add to a circuit
circ = QuantumCircuit(2, 2)
circ.append(XX, [0, 1])
circ.measure([0, 1], [0, 1])
circ.draw("mpl")

Sortie de la cellule de code précédente

Note que dans l'exemple ci-dessus, l'opérateur est initialisé à partir d'un objet Pauli. Cependant, l'objet Pauli peut aussi être directement inséré dans le circuit lui-même et sera converti en une séquence de portes Pauli à qubit unique :

# Add to a circuit
circ2 = QuantumCircuit(2, 2)
circ2.append(Pauli("XX"), [0, 1])
circ2.measure([0, 1], [0, 1])
circ2.draw()
┌────────────┐┌─┐
q_0: ┤0 ├┤M├───
│ Pauli(XX) │└╥┘┌─┐
q_1: ┤1 ├─╫─┤M├
└────────────┘ ║ └╥┘
c: 2/═══════════════╩══╩═
0 1

Combiner des Operators

Les opérateurs peuvent être combinés de plusieurs façons.

Produit tensoriel

Deux opérateurs AA et BB peuvent être combinés en un opérateur produit tensoriel ABA\otimes B via la fonction Operator.tensor. Note que si AA et BB sont tous deux des opérateurs à qubit unique, alors A.tensor(B) = ABA\otimes B aura les sous-systèmes indexés comme la matrice BB sur le sous-système 0, et la matrice AA sur le sous-système 1.

A = Operator(Pauli("X"))
B = Operator(Pauli("Z"))
A.tensor(B)
Operator([[ 0.+0.j,  0.+0.j,  1.+0.j,  0.+0.j],
[ 0.+0.j, -0.+0.j, 0.+0.j, -1.+0.j],
[ 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[ 0.+0.j, -1.+0.j, 0.+0.j, -0.+0.j]],
input_dims=(2, 2), output_dims=(2, 2))

Expansion tensorielle

Une opération étroitement liée est Operator.expand, qui fonctionne comme un produit tensoriel mais dans l'ordre inverse. Ainsi, pour deux opérateurs AA et BB, on a A.expand(B) = BAB\otimes A où les sous-systèmes sont indexés comme la matrice AA sur le sous-système 0, et la matrice BB sur le sous-système 1.

A = Operator(Pauli("X"))
B = Operator(Pauli("Z"))
A.expand(B)
Operator([[ 0.+0.j,  1.+0.j,  0.+0.j,  0.+0.j],
[ 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[ 0.+0.j, 0.+0.j, -0.+0.j, -1.+0.j],
[ 0.+0.j, 0.+0.j, -1.+0.j, -0.+0.j]],
input_dims=(2, 2), output_dims=(2, 2))

Composition

Tu peux aussi composer deux opérateurs AA et BB pour effectuer une multiplication matricielle avec la méthode Operator.compose. A.compose(B) renvoie l'opérateur de matrice B.AB.A :

A = Operator(Pauli("X"))
B = Operator(Pauli("Z"))
A.compose(B)
Operator([[ 0.+0.j,  1.+0.j],
[-1.+0.j, 0.+0.j]],
input_dims=(2,), output_dims=(2,))

Tu peux aussi composer dans l'ordre inverse en appliquant BB devant AA via l'argument front de compose : A.compose(B, front=True) = A.BA.B :

A = Operator(Pauli("X"))
B = Operator(Pauli("Z"))
A.compose(B, front=True)
Operator([[ 0.+0.j, -1.+0.j],
[ 1.+0.j, 0.+0.j]],
input_dims=(2,), output_dims=(2,))

Composition sur des sous-systèmes

Note que la composition précédente requiert que la dimension de sortie totale du premier opérateur AA soit égale à la dimension d'entrée totale de l'opérateur composé BB (et de même, la dimension de sortie de BB doit être égale à la dimension d'entrée de AA lors d'une composition avec front=True).

Tu peux aussi composer un opérateur plus petit avec une sélection de sous-systèmes d'un opérateur plus grand via l'argument qargs de compose, avec ou sans front=True. Dans ce cas, les dimensions d'entrée et de sortie des sous-systèmes composés doivent correspondre. Note que le plus petit opérateur doit toujours être l'argument de la méthode compose.

Par exemple, pour composer une porte à deux qubits avec un opérateur à trois qubits :

# Compose XZ with a 3-qubit identity operator
op = Operator(np.eye(2**3))
XZ = Operator(Pauli("XZ"))
op.compose(XZ, qargs=[0, 2])
Operator([[ 0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  1.+0.j,  0.+0.j,  0.+0.j,
0.+0.j],
[ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, -1.+0.j, 0.+0.j,
0.+0.j],
[ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j,
0.+0.j],
[ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j,
-1.+0.j],
[ 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j,
0.+0.j],
[ 0.+0.j, -1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j,
0.+0.j],
[ 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j,
0.+0.j],
[ 0.+0.j, 0.+0.j, 0.+0.j, -1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j,
0.+0.j]],
input_dims=(2, 2, 2), output_dims=(2, 2, 2))
# Compose YX in front of the previous operator
op = Operator(np.eye(2**3))
YX = Operator(Pauli("YX"))
op.compose(YX, qargs=[0, 2], front=True)
Operator([[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j, 0.+0.j],
[0.+0.j, 0.+1.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+1.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+1.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+1.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]],
input_dims=(2, 2, 2), output_dims=(2, 2, 2))

Combinaisons linéaires

Les opérateurs peuvent aussi être combinés à l'aide des opérateurs linéaires standards : addition, soustraction et multiplication scalaire par des nombres complexes.

XX = Operator(Pauli("XX"))
YY = Operator(Pauli("YY"))
ZZ = Operator(Pauli("ZZ"))

op = 0.5 * (XX + YY - 3 * ZZ)
op
Operator([[-1.5+0.j,  0. +0.j,  0. +0.j,  0. +0.j],
[ 0. +0.j, 1.5+0.j, 1. +0.j, 0. +0.j],
[ 0. +0.j, 1. +0.j, 1.5+0.j, 0. +0.j],
[ 0. +0.j, 0. +0.j, 0. +0.j, -1.5+0.j]],
input_dims=(2, 2), output_dims=(2, 2))

Un point important : alors que tensor, expand et compose préservent l'unitarité des opérateurs unitaires, les combinaisons linéaires ne le font pas ; ainsi, additionner deux opérateurs unitaires donnera en général un opérateur non unitaire :

op.is_unitary()
False

Conversion implicite en Operators

Note que pour toutes les méthodes suivantes, si le second objet n'est pas déjà un objet Operator, il est implicitement converti en un par la méthode. Cela signifie que des matrices peuvent être passées directement sans être explicitement converties en Operator au préalable. Si la conversion est impossible, une exception est levée.

# Compose with a matrix passed as a list
Operator(np.eye(2)).compose([[0, 1], [1, 0]])
Operator([[0.+0.j, 1.+0.j],
[1.+0.j, 0.+0.j]],
input_dims=(2,), output_dims=(2,))

Comparer des Operators

Les opérateurs implémentent une méthode d'égalité qui permet de vérifier si deux opérateurs sont approximativement égaux.

Operator(Pauli("X")) == Operator(XGate())
True

Note que cette vérification porte sur chaque élément matriciel des opérateurs de façon approximative ; deux unitaires qui ne diffèrent que par une phase globale ne sont pas considérés comme égaux :

Operator(XGate()) == np.exp(1j * 0.5) * Operator(XGate())
False

Fidélité de processus

Tu peux aussi comparer des opérateurs à l'aide de la fonction process_fidelity du module Quantum Information. Il s'agit d'une quantité issue de la théorie de l'information qui mesure à quel point deux canaux quantiques sont proches l'un de l'autre ; dans le cas d'opérateurs unitaires, elle ne dépend pas de la phase globale.

# Two operators which differ only by phase
op_a = Operator(XGate())
op_b = np.exp(1j * 0.5) * Operator(XGate())

# Compute process fidelity
F = process_fidelity(op_a, op_b)
print("Process fidelity =", F)
Process fidelity = 1.0

Note que la fidélité de processus n'est généralement une mesure valide de proximité que si les opérateurs en entrée sont unitaires (ou CP dans le cas de canaux quantiques), et une exception est levée si les entrées ne sont pas CP.

Prochaines étapes

Recommandations