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
GateetInstruction - 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")
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 et peuvent être combinés en un opérateur produit tensoriel via la fonction Operator.tensor. Note que si et sont tous deux des opérateurs à qubit unique, alors A.tensor(B) = aura les sous-systèmes indexés comme la matrice sur le sous-système 0, et la matrice 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 et , on a A.expand(B) = où les sous-systèmes sont indexés comme la matrice sur le sous-système 0, et la matrice 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 et pour effectuer une multiplication matricielle avec la méthode Operator.compose. A.compose(B) renvoie l'opérateur de matrice :
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 devant via l'argument front de compose : A.compose(B, front=True) = :
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 soit égale à la dimension d'entrée totale de l'opérateur composé (et de même, la dimension de sortie de doit être égale à la dimension d'entrée de 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
- Explore la référence de l'API Operator.