French
Langues
English
Japanese
German
Korean
Portuguese, Brazilian
French
Shortcuts

Note

Cette page a été générée à partir de tutorials/circuits_advanced/2_operators_overview.ipynb.

Exécuter en mode interactif dans le IBM Quantum lab.

Opérateurs

[7]:
import numpy as np

from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit import execute, BasicAer
from qiskit.compiler import transpile
from qiskit.quantum_info.operators import Operator, Pauli
from qiskit.quantum_info import process_fidelity

from qiskit.extensions import RXGate, XGate, CXGate

La Classe Opérateurs

La classe Operator est utilisée dans Qiskit pour représenter toute matrice unitaire agissant sur un système quantique. Elle possède plusieurs méthodes qui permettent de construire des opérateurs composés en utilisant les produits tensoriels avec de plus petits opérateurs, et pour composer des opérateurs.

Créer des Opérateurs

Le moyen le plus simple pour créer un opérateur est de l’initialiser avec une matrice construite par une liste ou un tableau Numpy. Par exemple, pour créer un opérateur Pauli-XX sur deux qubits :

[8]:
XX = Operator([[0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0], [1, 0, 0, 0]])
XX
[8]:
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))

Propriétés de l’objet Operator

L’objet operator contient la matrice ainsi que les dimensions d’entrée et de sortie d’un système ou d’un sous-système.

  • data: To access the underlying Numpy array, we may use the Operator.data property.

  • dim : Renvoie les dimensions d’entrée et de sortie de l’opérateur en utilisant la propriété Operator.dim. Note : la sortie est un couple `(input_dim, output_dim)`, qui correspond aux dimensions –en ordre inverse : colonnes,lignes– de la matrice sous-jacente.

[9]:
XX.data
[9]:
array([[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]])
[10]:
input_dim, output_dim = XX.dim
input_dim, output_dim
[10]:
(4, 4)

Dimensions d’entrée et de sortie

La classe Operator contient également les dimensions du sous-système qui peut être utile pour la composition d’opérateurs. Les valeurs de ces dimensions peuvent être obtenues en utilisant les fonctions input_dims et output_dims.

Pour des opérateurs de dimension \(2^N\) par \(2^M\), les dimensions d’entrée et de sortie seront automatiquement interprétées comme représentant M-qubit et N-qubit:

[11]:
op = Operator(np.random.rand(2 ** 1, 2 ** 2))
print('Input dimensions:', op.input_dims())
print('Output dimensions:', op.output_dims())
Input dimensions: (2, 2)
Output dimensions: (2,)

Si la matrice d’entrée ne peut être divisée en sous systèmes de qubits, elle sera stockée comme un opérateur sur un seul quibt. Par exemple pour une matrice de taille \(6\times6\) :

[12]:
op = Operator(np.random.rand(6, 6))
print('Input dimensions:', op.input_dims())
print('Output dimensions:', op.output_dims())
Input dimensions: (6,)
Output dimensions: (6,)

On peut aussi spécifier manuellement les dimensions d’entrée et de sortie au moment de l’initialisation d’un nouvel opérateur :

[13]:
# Force input dimension to be (4,) rather than (2, 2)
op = Operator(np.random.rand(2 ** 1, 2 ** 2), input_dims=[4])
print('Input dimensions:', op.input_dims())
print('Output dimensions:', op.output_dims())
Input dimensions: (4,)
Output dimensions: (2,)
[14]:
# Specify system is a qubit and qutrit
op = Operator(np.random.rand(6, 6),
              input_dims=[2, 3], output_dims=[2, 3])
print('Input dimensions:', op.input_dims())
print('Output dimensions:', op.output_dims())
Input dimensions: (2, 3)
Output dimensions: (2, 3)

Les fonctions input_dims et output_dims permettent de connaître les dimensions d’entrée ou de sortie d’un sous ensemble du du système :

[15]:
print('Dimension of input system 0:', op.input_dims([0]))
print('Dimension of input system 1:', op.input_dims([1]))
Dimension of input system 0: (2,)
Dimension of input system 1: (3,)

Conversion de classes en opérateurs

Plusieurs autres types d’objets de Qiskit peuvent être convertis en objets de la classe ``Operator``en utilisant sa méthode d’initialisation. Par exemple :

  • les objets de type Pauli

  • les objets Gate et Instruction

  • ainsi que les objets QuantumCircuits

Il est à noter que dans le dernier cas l’utilisation de la classe operator permet de calculer la matrice unitaire correspondant à un circuit quantique sans avoir besoin d’utiliser un émulateur. Si le circuit contient une operation non supportée la fonction retournera une exception. Ces opérations sont : la mesure, la mise à zero, les opérations conditionnelles, ou toute autre porte qui n’est pas définie par une matrice unitaire ou qui ne peut pas être décomposée en portes définies par une matrice unitaire.

[16]:
# Create an Operator from a Pauli object

pauliXX = Pauli(label='XX')
Operator(pauliXX)
[16]:
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))
[19]:
# Create an Operator for a Gate object
Operator(CXGate())
[19]:
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))
[20]:
# Create an operator from a parameterized Gate object
Operator(RXGate(np.pi / 2))
[20]:
Operator([[0.70710678+0.j        , 0.        -0.70710678j],
          [0.        -0.70710678j, 0.70710678+0.j        ]],
         input_dims=(2,), output_dims=(2,))
[21]:
# 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)
[21]:
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))

Utilisation d’opérateurs dans les circuits

Les Operators unitaires peuvent être directement ajoutés à un QuantumCircuit en utilisant la méthode QuantumCircuit.append. Ceci a pour effet de convertir l”Operator en objet de type UnitaryGate et de l’ajouter au circuit.

Si l’opérateur n’est pas unitaire, une exception sera retournée à l’utilisateur. Ceci peut être prévenu par l’utilisation de la fonction Operator.is_unitary() qui renvoie True si l’opérateur est unitaire, et False sinon.

[22]:
# Create an operator
XX = Operator(Pauli(label='XX'))

# Add to a circuit
circ = QuantumCircuit(2, 2)
circ.append(XX, [0, 1])
circ.measure([0,1], [0,1])
circ.draw('mpl')
[22]:
../../_images/tutorials_circuits_advanced_02_operators_overview_22_0.png

Notons que dans l’exemple ci-dessus, l’opérateur est initialisé à l’aide d’un objet de type Pauli. Cependant l’objet de type Pauli peut aussi être inséré directement dans le circuit, et sera converti en une séquence de gates de Pauli sur un seul qubit :

[23]:
backend = BasicAer.get_backend('qasm_simulator')
job = execute(circ, backend, basis_gates=['u1','u2','u3','cx'])
job.result().get_counts(0)
[23]:
{'11': 1024}
[24]:
# Add to a circuit
circ2 = QuantumCircuit(2, 2)
circ2.append(Pauli(label='XX'), [0, 1])
circ2.measure([0,1], [0,1])
circ2.draw()
[24]:
     ┌───────────┐┌─┐
q_0: ┤0          ├┤M├───
     │  Pauli:XX │└╥┘┌─┐
q_1: ┤1          ├─╫─┤M├
     └───────────┘ ║ └╥┘
c: 2/══════════════╩══╩═
                   0  1 

Combinaison d’opérateurs

Operators may be combined using several methods.

Produit Tensoriel

Deux opérateurs \(A\) et \(B\) peuvent être combinés en un opérateur qui est le produit tensoriel \(A\otimes B\) en utilisant la fonction Operator.tensor(). Si \(A\) et \(B\) sont des opérateurs chacun sur un seul qubit alors A.tensor(B) = \(A\otimes B\) correspondra à l’application de la matrice \(B\) sur le sous système 0, et la matrice \(A\) sur le sous système 1.

[25]:
A = Operator(Pauli(label='X'))
B = Operator(Pauli(label='Z'))
A.tensor(B)
[25]:
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 très proche de la précédente est Operator.expand qui agit de la même manière que le produit tensoriel, mais dans l’ordre inverse. C’est à dire que pour deux opérateurs \(A\) et \(B\) l’expansion A.expand(B) = \(B\otimes A\) correspondra à l’application de la matrice \(A\) sur le sous système 0, et la matrice \(B\) sur le sous système 1.

[26]:
A = Operator(Pauli(label='X'))
B = Operator(Pauli(label='Z'))
A.expand(B)
[26]:
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

Il est également possible de faire la composition de deux opérateurs \(A\) et \(B\) pour implémenter la multiplication de matrices en utilisant la méthode Operator.compose. Ainsi, A.compose(B) retourne l’opérateur correspondant à la matrice \(B.A\) :

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

Il est également possible de faire la composition en ordre inverse en appliquant \(B\) avant \(A\), en utilisant l’argument de mot clef front de la méthode compose comme ceci : ``A.compose(B, front=True)``= \(A.B\) :

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

Composition de sous-systèmes

On peut noter que dans les compositions vues précédemment, la dimension totale de sortie du premier opérateur \(A\) est égale à la dimension totale d’entrée de l’opérateur \(B\) (et de même, la dimension de sortie de \(B\) doit être égale à la dimension de l’entrée de \(A\) lorsque l’on utilise la composition avec l’option front=True).

Il est également possible d’effectuer les compositions sur un sous système d’un opérateur plus grand en utilisant l’argument de mot clef qargs (kwarq) de la méthode compose (avec ou sans l’utilisation de front=True). Dans ce cas, les dimensions correspondant aux sous systèmes faisant l’objet de la composition doivent se correspondre. Note : l’opérateur du plus petit sous système doit être passé en argument de la méthode ``compose`` appliquée au système

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

[29]:
# Compose XZ with an 3-qubit identity operator
op = Operator(np.eye(2 ** 3))
XZ = Operator(Pauli(label='XZ'))
op.compose(XZ, qargs=[0, 2])
[29]:
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))
[30]:
# Compose YX in front of the previous operator
op = Operator(np.eye(2 ** 3))
YX = Operator(Pauli(label='YX'))
op.compose(XZ, qargs=[0, 2], front=True)
[30]:
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))

Combinaisons linéaires

Les opérateurs peuvent aussi être combinés en utilisant les fonctions standards d’addition de soustraction et de multiplication scalaire par des nombres complexes.

[31]:
XX = Operator(Pauli(label='XX'))
YY = Operator(Pauli(label='YY'))
ZZ = Operator(Pauli(label='ZZ'))

op = 0.5 * (XX + YY - 3 * ZZ)
op
[31]:
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))

Il est important de noter que si les méthodes tensor, expand and compose conservent la propriété d’unitarité des opérateurs, les combinaisons linéaires d’opérateurs par contre ne le feront pas; ainsi l’addition de deux opérateurs unitaires donnera en général un opérateur non unitaire :

[32]:
op.is_unitary()
[32]:
False

Conversion implicite en opérateur

Notez que pour les méthodes ci-dessous, si le second objet utilisé n’est pas déjà de classe Operator, il sera implicitement converti en objet de classe Operator. Ceci signifie que l’on peut passer directement des objets de type matrice en argument, sans avoir à les convertir préalablement et explicitement en opérateur. Si la conversion n’est pas possible, une exception sera retournée.

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

Comparaison d’opérateurs

Une méthode de test d’égalité est implémentée pour les objets de type Operator, elle peut être utilisée pour vérifier si deux opérateurs sont approximativement égaux.

[34]:
Operator(Pauli(label='X')) == Operator(XGate())
[34]:
True

Notons que cette méthode vérifie l’égalité approximative entre les éléments correspondant des matrices représentant les opérateurs ; deux opérateurs unitaires qui diffèreraient par une phase globale ne seront pas considérés comme égaux :

[35]:
Operator(XGate()) == np.exp(1j * 0.5) * Operator(XGate())
[35]:
False

Fidélité de Process

Nous pouvons également comparer les opérateurs à l’aide de la fonction process_fidelity du module Quantum Information. Il s’agit d’une notion de la théorie de l’information quantique permettant d’indiquer à quel point deux chemins quantiques sont équivalents, et dans le cas d’opérateurs unitaires, le résultat est indépendant de la phase globale.

[36]:
# 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 : la fidélité de process est en général une mesure valide de l’équivalence si les opérateurs considérés sont unitaires (ou CP- complètement positifs- dans le cas de chemins quantiques), et une exception sera retournée si les entrées ne sont pas CP.

[37]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright

Version Information

Qiskit SoftwareVersion
QiskitNone
Terra0.14.0
Aer0.6.0
Ignis0.3.0
Aqua0.7.0
IBM Q Provider0.6.1
System information
Python3.7.7 (default, Mar 26 2020, 10:32:53) [Clang 4.0.1 (tags/RELEASE_401/final)]
OSDarwin
CPUs4
Memory (Gb)16.0
Wed Apr 29 12:36:22 2020 EDT

This code is a part of Qiskit

© Copyright IBM 2017, 2020.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.

[ ]: