French
Languages
English
Bengali
French
German
Japanese
Korean
Portuguese
Spanish
Tamil

Note

Cette page a été générée à partir de tutorials/operators/01_operator_flow.ipynb.

Flux d’opérateur

Introduction

Qiskit fournit des classes représentant les états, les opérateurs, les combinaisons linéaires, les produits tensoriels et leurs compositions. Ces constructions algébriques nous permettent de construire des expressions représentant des opérateurs.

Nous introduisons des expressions en les construisant à partir des opérateurs de Pauli. Dans les sections suivantes, nous explorons plus en détail les opérateurs et les états, comment ils sont représentés et ce que nous pouvons faire avec eux. Dans la dernière section, nous construisons un état, le faisons évoluer avec un Hamiltonien, et calculons les valeurs d’espérance d’un observable.

Opérateurs de Pauli, sommes, compositions et produits tensoriels

Les opérateurs de base les plus importants sont les opérateurs Pauli. Les opérateurs Pauli sont représentés comme ceci.

[1]:
from qiskit.opflow import I, X, Y, Z
print(I, X, Y, Z)
I X Y Z

Ces opérateurs peuvent également porter un coefficient.

[2]:
print(1.5 * I)
print(2.5 * X)
1.5 * I
2.5 * X

Ces coefficients permettent aux opérateurs d’être utilisés comme termes dans une somme.

[3]:
print(X + 2.0 * Y)
1.0 * X
+ 2.0 * Y

Les produits tensoriels sont signalés par un caret, comme celui-ci.

[4]:
print(X^Y^Z)
XYZ

La composition est indiquée par le symbole @.

[5]:
print(X @ Y @ Z)
iI

Dans les deux exemples précédents, le produit tensoriel et la composition des opérateurs de Pauli ont été immédiatement réduits à l’opérateur de Pauli équivalent (éventuellement multiqubit). Si nous composons des objets plus complexes, le résultat sera un objet représentant le s opérations à évaluer. Il s’agit d’expressions algébriques.

Par exemple, composer deux sommes donne

[6]:
print((X + Y) @ (Y + Z))
1j * Z
+ -1j * Y
+ 1.0 * I
+ 1j * X

Et le produit tensoriel de deux sommes donne

[7]:
print((X + Y) ^ (Y + Z))
1.0 * XY
+ 1.0 * XZ
+ 1.0 * YY
+ 1.0 * YZ

Let’s take a closer look at the types introduced above. First the Pauli operators.

[8]:
(I, X)
[8]:
(PauliOp(Pauli('I'), coeff=1.0), PauliOp(Pauli('X'), coeff=1.0))

Chaque opérateur de Pauli est une instance de PauliOp, qui enveloppe une instance de qiskit.quantum_info.Pauli, et ajoute un coefficient coeff. D’une manière générale, un `` PauliOp`` représente un produit tensoriel pondéré d’opérateurs de Pauli.

[9]:
2.0 * X^Y^Z
[9]:
PauliOp(Pauli('XYZ'), coeff=2.0)

Pour le codage des opérateurs Pauli en tant que paires de valeurs booléennes, consultez la documentation de qiskit.quantum_info.Pauli.

All of the objects representing operators, whether as « primitive »s such as PauliOp, or algebraic expressions carry a coefficient

[10]:
print(1.1 * ((1.2 * X)^(Y + (1.3 * Z))))
1.2 * (
  1.1 * XY
  + 1.4300000000000002 * XZ
)

In the following we take a broader and deeper look at Qiskit’s operators, states, and the building blocks of quantum algorithms.

Partie I: Fonctions et mesures

Les états quantiques sont représentés par des sous-classes de la classe StateFn. Il y a quatre représentations d’états quantiques: DictStateFn est une représentation éparse dans la base de calcul, s’appuyant sur un dict. VectorStateFn est une représentation dense sur la base de calcul, et utilise un tableau numpy. CircuitStateFn est représenté par un circuit et désigne l’état obtenu en exécutant le circuit sur l’état ouù touls les qubits sont initialement à zéro dans la base de caclul. OperatorStateFn représente des états mixtes à l’aide d’une matrice de densité. (Comme nous le verrons plus loin, OperatorStateFn est également utilisé pour représenter des observables.)

Plusieurs instances StateFn sont fournies pour des raisons de commodité. Par exemple, Zero, One, Plus, Minus.

[11]:
from qiskit.opflow import (StateFn, Zero, One, Plus, Minus, H,
                           DictStateFn, VectorStateFn, CircuitStateFn, OperatorStateFn)

Zéro et One représentent les états quantiques \(| 0\rangle\) et \(| 1\rangle\). Ils sont représentés par DictStateFn.

[12]:
print(Zero, One)
DictStateFn({'0': 1}) DictStateFn({'1': 1})

Plus et Minus, représentant les états \((|0\rangle + |1\rangle)/\sqrt{2}\) et \((|0\rangle-|1\rangle)/\sqrt{2}\) sont représentés par des circuits. H est un synonyme de Plus.

[13]:
print(Plus, Minus)
CircuitStateFn(
   ┌───┐
q: ┤ H ├
   └───┘
) CircuitStateFn(
   ┌───┐┌───┐
q: ┤ X ├┤ H ├
   └───┘└───┘
)

L’indexation dans les états quantiques se fait avec la méthode eval. Ces exemples renvoient les coefficients des états de base 0 et 1. (Ci-dessous, nous verrons que la méthode eval est aussi utilisée pour d’autres calculs.)

[14]:
print(Zero.eval('0'))
print(Zero.eval('1'))
print(One.eval('1'))
print(Plus.eval('0'))
print(Minus.eval('1'))
1.0
0.0
1.0
(0.7071067811865475+0j)
(-0.7071067811865475+8.7e-17j)

Le vecteur dual d’un état quantique, c’est-à-dire le bra correspondant à un ket est obtenu via la méthode adjoint. StateFn porte un drapeau is_measurement, qui est à Flase si l’objet est un ket et True s’il s’agit d’un bra.

Ici, nous construisons \(\langle 1 |\).

[15]:
One.adjoint()
[15]:
DictStateFn({'1': 1}, coeff=1.0, is_measurement=True)

Pour des raisons de commodité, on peut obtenir le vecteur dual avec un tilde, comme celui-ci

[16]:
~One
[16]:
DictStateFn({'1': 1}, coeff=1.0, is_measurement=True)

Opérations et prédicats algébriques

De nombreuses opérations algébriques et prédicats entre les StateFns sont pris en charge, notamment :

  • + - addition

  • - - soutraction, négation (multiplication scalaire par -1)

  • *-multiplication scalaire

  • / division scalaire

  • @-composition

  • ` ` ^ ``-produit tensoriel ou tenseur (tenseur n fois avec soi-même)

  • ** - puissance de composition (composition n fois de soi-même)

  • == - égalité

  • ~ - adjoint, alternant entre une Fonction d’Etat et une Mesure

Sachez que ces opérateurs obéissent aux règles Python pour la priorité de l’opérateur, ce qui pourrait ne pas être ce que vous attendez mathématiquement. Par exemple, I^X + X^I sera en fait analysé comme I ^ (X + X) ^ I == 2 * (I^X^I) parce que Python évalue + avant ^. Dans ces cas, vous pouvez utiliser les méthodes (.tensor(), etc) ou les parenthèses.

Les StateFns portent un coefficient. Cela nous permet de multiplier les états par un scalaire, et donc de construire des sommes.

Ici, nous construisons \((2 + 3i)|0\rangle\).

[17]:
(2.0 + 3.0j) * Zero
[17]:
DictStateFn({'0': 1}, coeff=(2+3j), is_measurement=False)

Ici, nous voyons que l’ajout de deux DictStateFn s renvoie un objet du même type. Nous construisons :math:` |0rangle + |1rangle `.

[18]:
print(Zero + One)
DictStateFn({'0': 1.0, '1': 1.0})

Notez que vous devez normaliser les états à la main. Par exemple, pour construire \((|0\rangle + |1\rangle)/\sqrt{2}\), nous écrivons

[19]:
import math

v_zero_one = (Zero + One) / math.sqrt(2)
print(v_zero_one)
DictStateFn({'0': 1.0, '1': 1.0}) * 0.7071067811865475

Dans d’autres cas, le résultat est une représentation symbolique d’une somme. Par exemple, voici une représentation de \(|+\rangle + |-\rangle\).

[20]:
print(Plus + Minus)
SummedOp([
  CircuitStateFn(
     ┌───┐
  q: ┤ H ├
     └───┘
  ),
  CircuitStateFn(
     ┌───┐┌───┐
  q: ┤ X ├┤ H ├
     └───┘└───┘
  )
])

L’opérateur de composition est utilisé pour effectuer un produit intérieur qui, par défaut, est détenu sous une forme non évaluée. Voici une représentation de \(\langle 1 | 1 \rangle\).

[21]:
print(~One @ One)
ComposedOp([
  DictMeasurement({'1': 1}),
  DictStateFn({'1': 1})
])

Notez que l’indicateur is_measurement fait que l’état (bra) ~ One doit être imprimé DictMeasurement.

Les expressions symboliques peuvent être évaluées avec la méthode eval.

[22]:
(~One @ One).eval()
[22]:
1.0
[23]:
(~v_zero_one @ v_zero_one).eval()
[23]:
0.9999999999999998

Ici est \(\langle - | 1 \rangle = \langle (\langle 0| - \langle 1|)/\sqrt{2} | 1\rangle\).

[24]:
(~Minus @ One).eval()
[24]:
(-0.7071067811865475-8.7e-17j)

L’opérateur de composition @ est équivalent à l’appel de la méthode compose.

[25]:
print((~One).compose(One))
ComposedOp([
  DictMeasurement({'1': 1}),
  DictStateFn({'1': 1})
])

Les produits internes peuvent également être calculés à l’aide de la méthode eval directement, sans construire de ComposedOp.

[26]:
(~One).eval(One)
[26]:
1.0

Les produits tensoriels symboliques sont construits comme suit. Voici \(|0\rangle \otimes | + \rangle\).

[27]:
print(Zero^Plus)
TensoredOp([
  DictStateFn({'0': 1}),
  CircuitStateFn(
     ┌───┐
  q: ┤ H ├
     └───┘
  )
])

Cela peut être représenté comme un CircuitStateFn simple (et non pas composé).

[28]:
print((Zero^Plus).to_circuit_op())
CircuitStateFn(
     ┌───┐
q_0: ┤ H ├
     └───┘
q_1: ─────

)

Les puissances de Tensor sont construites à l’aide du caret ^ comme suit : \(600 (| 11111\rangle + |00000\rangle)\), et \(|10\rangle^{\otimes 3 }\).

[29]:
print(600 * ((One^5) + (Zero^5)))
print((One^Zero)^3)
DictStateFn({'11111': 1.0, '00000': 1.0}) * 600.0
DictStateFn({'101010': 1})

La méthode to_matrix_op convertit en VectorStateFn.

[30]:
print(((Plus^Minus)^2).to_matrix_op())
print(((Plus^One)^2).to_circuit_op())
print(((Plus^One)^2).to_matrix_op().sample())
VectorStateFn(Statevector([ 0.25-6.1e-17j, -0.25+6.1e-17j,  0.25-6.1e-17j,
             -0.25+6.1e-17j, -0.25+6.1e-17j,  0.25-6.1e-17j,
             -0.25+6.1e-17j,  0.25-6.1e-17j,  0.25-6.1e-17j,
             -0.25+6.1e-17j,  0.25-6.1e-17j, -0.25+6.1e-17j,
             -0.25+6.1e-17j,  0.25-6.1e-17j, -0.25+6.1e-17j,
              0.25-6.1e-17j],
            dims=(2, 2, 2, 2)))
CircuitStateFn(
     ┌───┐
q_0: ┤ X ├
     ├───┤
q_1: ┤ H ├
     ├───┤
q_2: ┤ X ├
     ├───┤
q_3: ┤ H ├
     └───┘
)
{'1101': 0.2734375, '0111': 0.2509765625, '1111': 0.24609375, '0101': 0.2294921875}

Construire un StateFn est facile. La classe StateFn sert également de fabrique, et peut prendre n’importe quelle primitive applicable dans son constructeur et retourner la sous-classe StateFn correcte. Actuellement, les primitives suivantes peuvent être transmises au constructeur, répertoriées pour la sous-classe StateFn ils produisent :

  • str (égal à une chaîne de bits de base) -> DictStateFn

  • dict -> DictStateFn

  • Objet Résultat Qiskit-> DictStateFn

  • list -> VectorStateFn

  • np.ndarray -> VectorStateFn

  • Statevector -> VectorStateFn

  • QuantumCircuit -> CircuitStateFn

  • Instruction -> CircuitStateFn

  • OperatorBase -> OperatorStateFn

[31]:
print(StateFn({'0':1}))
print(StateFn({'0':1}) == Zero)

print(StateFn([0,1,1,0]))

from qiskit.circuit.library import RealAmplitudes
print(StateFn(RealAmplitudes(2)))
DictStateFn({'0': 1})
True
VectorStateFn(Statevector([0.+0.j, 1.+0.j, 1.+0.j, 0.+0.j],
            dims=(2, 2)))
CircuitStateFn(
     ┌──────────────────────────────────────────────────────────┐
q_0: ┤0                                                         ├
     │  RealAmplitudes(θ[0],θ[1],θ[2],θ[3],θ[4],θ[5],θ[6],θ[7]) │
q_1: ┤1                                                         ├
     └──────────────────────────────────────────────────────────┘
)

Partie II: PrimitiveOps

Les opérateurs de base sont des sous-classes de PrimitiveOp. Tout comme StateFn, PrimitiveOp est aussi un constructeur pour créer le type correct de PrimitiveOp pour une primitive donnée. Actuellement, les primitives suivantes peuvent être transmises au constructeur, répertoriées à côté de la sous-classe PrimitiveOp ils produisent :

  • Terra’s Pauli -> PauliOp

  • Instruction -> CircuitOp

  • QuantumCircuit -> CircuitOp

  • 2d List -> MatrixOp

  • np.ndarray -> MatrixOp

  • spmatrix -> MatrixOp

  • Terra’s quantum_info.Operator -> MatrixOp

[32]:
from qiskit.opflow import X, Y, Z, I, CX, T, H, S, PrimitiveOp

Eléments de matrice

La méthode eval renvoie une colonne d’un opérateur. Par exemple, l’opérateur Pauli \(X\) est représenté par un PauliOp. Le fait d’obtenir une colonne renvoie une instance de la représentation éparse, une DictStateFn.

[33]:
X
[33]:
PauliOp(Pauli('X'), coeff=1.0)
[34]:
print(X.eval('0'))
DictStateFn({'1': (1+0j)})

Il s’ensuit que l’indexation dans un opérateur, qui obtient un élément de matrice, est effectuée avec deux appels à la méthode eval.

Nous avons :math:` X = left (begin{matrix} 0 & 1 \ 1 & 0 end{matrix} right) . Et l’élément de matrice :math: left{ X right} _{0,1}` est

[35]:
X.eval('0').eval('1')
[35]:
(1+0j)

Voici un exemple utilisant l’opérateur de deux qubits CX, le X contrôlé, qui est représenté par un circuit.

[36]:
print(CX)
print(CX.to_matrix().real) # The imaginary part vanishes.

q_0: ──■──
     ┌─┴─┐
q_1: ┤ X ├
     └───┘
[[1. 0. 0. 0.]
 [0. 0. 0. 1.]
 [0. 0. 1. 0.]
 [0. 1. 0. 0.]]
[37]:
CX.eval('01')  # 01 is the one in decimal. We get the first column.
[37]:
VectorStateFn(Statevector([0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
            dims=(2, 2)), coeff=1.0, is_measurement=False)
[38]:
CX.eval('01').eval('11')  # This returns element with (zero-based) index (1, 3)
[38]:
(1+0j)

Application d’un opérateur à un vecteur d’état

L’application d’un opérateur à un vecteur d’état peut être effectuée avec la méthode compose (équivalence, @ opérateur). Voici une représentation de \(X | 1 \rangle = | 0\rangle\).

[39]:
print(X @ One)
ComposedOp([
  X,
  DictStateFn({'1': 1})
])

Une représentation plus simple, la représentation DictStateFn de \(| 0\rangle \).

[40]:
(X @ One).eval()
[40]:
DictStateFn({'0': (1+0j)}, coeff=(1+0j), is_measurement=False)

L’étape intermédiaire ComposedOp peut être évitée en utilisant eval directement.

[41]:
X.eval(One)
[41]:
DictStateFn({'0': (1+0j)}, coeff=(1+0j), is_measurement=False)

La composition et les produits tensoriels des opérateurs sont effectués avec @ et ^. Voici quelques exemples.

[42]:
print(((~One^2) @ (CX.eval('01'))).eval())

print(((H^5) @ ((CX^2)^I) @ (I^(CX^2)))**2)
print((((H^5) @ ((CX^2)^I) @ (I^(CX^2)))**2) @ (Minus^5))
print(((H^I^I)@(X^I^I)@Zero))
(1+0j)
          ┌───┐          ┌───┐
q_0: ──■──┤ H ├───────■──┤ H ├─────
     ┌─┴─┐└───┘┌───┐┌─┴─┐└───┘┌───┐
q_1: ┤ X ├──■──┤ H ├┤ X ├──■──┤ H ├
     └───┘┌─┴─┐├───┤└───┘┌─┴─┐├───┤
q_2: ──■──┤ X ├┤ H ├──■──┤ X ├┤ H ├
     ┌─┴─┐└───┘├───┤┌─┴─┐└───┘├───┤
q_3: ┤ X ├──■──┤ H ├┤ X ├──■──┤ H ├
     └───┘┌─┴─┐├───┤└───┘┌─┴─┐├───┤
q_4: ─────┤ X ├┤ H ├─────┤ X ├┤ H ├
          └───┘└───┘     └───┘└───┘
CircuitStateFn(
     ┌───┐┌───┐     ┌───┐          ┌───┐
q_0: ┤ X ├┤ H ├──■──┤ H ├───────■──┤ H ├─────
     ├───┤├───┤┌─┴─┐└───┘┌───┐┌─┴─┐└───┘┌───┐
q_1: ┤ X ├┤ H ├┤ X ├──■──┤ H ├┤ X ├──■──┤ H ├
     ├───┤├───┤└───┘┌─┴─┐├───┤└───┘┌─┴─┐├───┤
q_2: ┤ X ├┤ H ├──■──┤ X ├┤ H ├──■──┤ X ├┤ H ├
     ├───┤├───┤┌─┴─┐└───┘├───┤┌─┴─┐└───┘├───┤
q_3: ┤ X ├┤ H ├┤ X ├──■──┤ H ├┤ X ├──■──┤ H ├
     ├───┤├───┤└───┘┌─┴─┐├───┤└───┘┌─┴─┐├───┤
q_4: ┤ X ├┤ H ├─────┤ X ├┤ H ├─────┤ X ├┤ H ├
     └───┘└───┘     └───┘└───┘     └───┘└───┘
)
CircuitStateFn(
     ┌─────────────┐
q_0: ┤0            ├─────
     │             │
q_1: ┤1 Pauli(XII) ├─────
     │             │┌───┐
q_2: ┤2            ├┤ H ├
     └─────────────┘└───┘
)
[43]:
print(~One @ Minus)
ComposedOp([
  DictMeasurement({'1': 1}),
  CircuitStateFn(
     ┌───┐┌───┐
  q: ┤ X ├┤ H ├
     └───┘└───┘
  )
])

Partie III : ListOp et sous-classes

ListOp

ListOp est un conteneur pour des opérations de vectorisation efficace sur une liste d’opérateurs et d’états.

[44]:
from qiskit.opflow import ListOp

print((~ListOp([One, Zero]) @ ListOp([One, Zero])))
ComposedOp([
  ListOp([
    DictMeasurement({'1': 1}),
    DictMeasurement({'0': 1})
  ]),
  ListOp([
    DictStateFn({'1': 1}),
    DictStateFn({'0': 1})
  ])
])

Par exemple, la composition ci-dessus est distribuée sur les listes (ListOp) à l’aide de la méthode de simplification reduce.

[45]:
print((~ListOp([One, Zero]) @ ListOp([One, Zero])).reduce())
ListOp([
  ListOp([
    ComposedOp([
      DictMeasurement({'1': 1}),
      DictStateFn({'1': 1})
    ]),
    ComposedOp([
      DictMeasurement({'1': 1}),
      DictStateFn({'0': 1})
    ])
  ]),
  ListOp([
    ComposedOp([
      DictMeasurement({'0': 1}),
      DictStateFn({'1': 1})
    ]),
    ComposedOp([
      DictMeasurement({'0': 1}),
      DictStateFn({'0': 1})
    ])
  ])
])

ListOps: SummedOp, ComposedOp, TensoredOp

ListOp, introduced above, is useful for vectorizing operations. But, it also serves as the superclass for list-like composite classes. If you’ve already played around with the above, you’ll notice that you can easily perform operations between OperatorBases which we may not know how to perform efficiently in general (or simply haven’t implemented an efficient procedure for yet), such as addition between CircuitOps. In those cases, you may receive a ListOp result (or subclass thereof) from your operation representing the lazy execution of the operation. For example, if you attempt to add together a DictStateFn and a CircuitStateFn, you’ll receive a SummedOp representing the sum of the two. This composite State function still has a working eval (but may need to perform a non-scalable computation under the hood, such as converting both to vectors).

Ces OperatorBases composites sont à l’image de comment nous construisons des calculs de plus en plus complexes et riches en utilisant les blocs de construction PrimitiveOp et StateFn.

Chaque ListOp a quatre propriétés :

  • oplist - La liste des OperatorBases qui peuvent représenter des termes, des facteurs, etc.

  • combo_fn - La fonction qui prend une liste de nombres complexes à une valeur de sortie qui définit comment combiner les sorties des éléments oplist. Pour la simplicité de diffusion, cette fonction est définie sur les tableaux NumPy.

  • coeff - Un coefficient multiplicateur de la primitive. Notez que coeff peut être int, float, complexe ou un objet Parameter libre (à partir de qiskit. ircuit dans Terra) pour être lié plus tard en utilisant my_op.bind_parameters.

  • abelian - Indique si les Opérateurs de oplist sont connus pour être mutuellement commutés (généralement définis après avoir été convertis par le convertisseur AbelianGrouper).

Notez que ListOp supporte les surcharges de séquences typiques, donc vous pouvez utiliser l’indexation comme my_op[4] pour accéder aux OperatorBases dans oplist.

OperatorStateFn

Nous avons mentionné ci-dessus que OperatorStateFn représente un opérateur de densité. Mais si le flag is_measurement est True, alors OperatorStateFn représente un observable. La valeur attendue de cette observable peut alors être construite via ComposedOp. Ou directement, en utilisant eval. Rappelez-vous que le flag is_measurement (property) est défini via la méthode adjoint.

Ici nous construisons l’observable correspondant à l’opérateur Pauli \(Z\). Notez que lors de l’impression, il est appelé OperatorMeasurement.

[46]:
print(StateFn(Z).adjoint())
StateFn(Z).adjoint()
OperatorMeasurement(Z)
[46]:
OperatorStateFn(PauliOp(Pauli('Z'), coeff=1.0), coeff=1.0, is_measurement=True)

Ici, nous calculons \(\langle 0 | Z | 0 \rangle\), \(\langle 1 | Z | 1 \rangle\), and \(\langle + | Z | + \rangle\), where \(|+\rangle = (|0\rangle + |1\rangle)/\sqrt{2}\).

[47]:
print(StateFn(Z).adjoint().eval(Zero))
print(StateFn(Z).adjoint().eval(One))
print(StateFn(Z).adjoint().eval(Plus))
(1+0j)
(-1+0j)
0j

Partie IV : Convertisseurs

Converters are classes that manipulate operators and states and perform building blocks of algorithms. Examples include changing the basis of operators and Trotterization. Converters traverse an expression and perform a particular manipulation or replacement, defined by the converter’s convert() method, of the Operators within. Typically, if a converter encounters an OperatorBase in the recursion which is irrelevant to its conversion purpose, that OperatorBase is left unchanged.

[48]:
import numpy as np
from qiskit.opflow import I, X, Y, Z, H, CX, Zero, ListOp, PauliExpectation, PauliTrotterEvolution, CircuitSampler, MatrixEvolution, Suzuki
from qiskit.circuit import Parameter
from qiskit import Aer

Evolutions, exp_i() et le EvolvedOp

Every PrimitiveOp and ListOp has an .exp_i() function such that H.exp_i() corresponds to \(e^{-iH}\). In practice, only a few of these Operators have an efficiently computable exponentiation (such as MatrixOp and the PauliOps with only one non-identity single-qubit Pauli), so we need to return a placeholder, or symbolic representation, (similar to how SummedOp is a placeholder when we can’t perform addition). This placeholder is called EvolvedOp, and it holds the OperatorBase to be exponentiated in its .primitive property.

Qiskit operators fully support parameterization, so we can use a Parameter for our evolution time here. Notice that there’s no « evolution time » argument in any function. The Operator flow exponentiates whatever operator we tell it to, and if we choose to multiply the operator by an evolution time, \(e^{-iHt}\), that will be reflected in our exponentiation parameters.

Somme pondérée des opérateurs Pauli

Un Hamiltonien exprimé comme une combinaison linéaire d’opérateurs Pauli multi-qubit peut être construit comme ceci.

[49]:
two_qubit_H2 =  (-1.0523732 * I^I) + \
                (0.39793742 * I^Z) + \
                (-0.3979374 * Z^I) + \
                (-0.0112801 * Z^Z) + \
                (0.18093119 * X^X)

Notez que two_qubit_H2 est représenté comme un SummedOp dont les termes sont des PauliOps.

[50]:
print(two_qubit_H2)
-1.0523732 * II
+ 0.39793742 * IZ
- 0.3979374 * ZI
- 0.0112801 * ZZ
+ 0.18093119 * XX

Ensuite, nous multiplions l’Hamiltonien par un Parameter. Ce Parameter est stocké dans la propriété coeff du SummedOp. Appeler exp_i() sur le résultat l’enveloppe dans EvolvedOp, représentant l’exponentiation.

[51]:
evo_time = Parameter('θ')
evolution_op = (evo_time*two_qubit_H2).exp_i()
print(evolution_op) # Note, EvolvedOps print as exponentiations
print(repr(evolution_op))
e^(-i*1.0*θ * (
  -1.0523732 * II
  + 0.39793742 * IZ
  - 0.3979374 * ZI
  - 0.0112801 * ZZ
  + 0.18093119 * XX
))
EvolvedOp(PauliSumOp(SparsePauliOp(['II', 'IZ', 'ZI', 'ZZ', 'XX'],
              coeffs=[-1.0523732 +0.j,  0.39793742+0.j, -0.3979374 +0.j, -0.0112801 +0.j,
  0.18093119+0.j]), coeff=1.0*θ), coeff=1.0)

Nous construisons h2_measurement, qui représente two_qubit_H2 comme observable.

[52]:
h2_measurement = StateFn(two_qubit_H2).adjoint()
print(h2_measurement)
OperatorMeasurement(-1.0523732 * II
+ 0.39793742 * IZ
- 0.3979374 * ZI
- 0.0112801 * ZZ
+ 0.18093119 * XX)

Nous construisons un état Bell \(|\Phi_+\rangle\) via \(\text{CX} (H\otimes I) |00\rangle\).

[53]:
bell = CX @ (I ^ H) @ Zero
print(bell)
CircuitStateFn(
     ┌───┐
q_0: ┤ H ├──■──
     └───┘┌─┴─┐
q_1: ─────┤ X ├
          └───┘
)

Voici l’expression \(H e^{-iHt} |\Phi_+\rangle\).

[54]:
evo_and_meas = h2_measurement @ evolution_op @ bell
print(evo_and_meas)
ComposedOp([
  OperatorMeasurement(-1.0523732 * II
  + 0.39793742 * IZ
  - 0.3979374 * ZI
  - 0.0112801 * ZZ
  + 0.18093119 * XX),
  e^(-i*1.0*θ * (
    -1.0523732 * II
    + 0.39793742 * IZ
    - 0.3979374 * ZI
    - 0.0112801 * ZZ
    + 0.18093119 * XX
  )),
  CircuitStateFn(
       ┌───┐
  q_0: ┤ H ├──■──
       └───┘┌─┴─┐
  q_1: ─────┤ X ├
            └───┘
  )
])

Généralement, nous voulons approximer \(e^{-iHt}\) en utilisant des portes à deux qubits. Nous y parvenons avec la méthode convert de PauliTrotterEvolution, qui traverse les expressions en appliquant la trotterisation à tous les EvolvedOps rencontrés. Bien que nous utilisions PauliTrotterEvolution ici, il y a d’autres possibilités, telles que MatrixEvolution, qui effectue l’exponentiation exactement.

[55]:
trotterized_op = PauliTrotterEvolution(trotter_mode=Suzuki(order=2, reps=1)).convert(evo_and_meas)
# We can also set trotter_mode='suzuki' or leave it empty to default to first order Trotterization.
print(trotterized_op)
ComposedOp([
  OperatorMeasurement(-1.0523732 * II
  + 0.39793742 * IZ
  - 0.3979374 * ZI
  - 0.0112801 * ZZ
  + 0.18093119 * XX),
  CircuitStateFn(
  global phase: 1.0523732*θ
       ┌───┐     ┌───┐┌───┐┌──────────────────┐┌───┐┌───┐┌───┐»
  q_0: ┤ H ├──■──┤ H ├┤ X ├┤ Rz(0.18093119*θ) ├┤ X ├┤ H ├┤ X ├»
       └───┘┌─┴─┐├───┤└─┬─┘└──────────────────┘└─┬─┘├───┤└─┬─┘»
  q_1: ─────┤ X ├┤ H ├──■────────────────────────■──┤ H ├──■──»
            └───┘└───┘                              └───┘     »
  «     ┌──────────────────┐┌───┐┌──────────────────┐┌──────────────────┐┌───┐»
  «q_0: ┤ Rz(-0.0112801*θ) ├┤ X ├┤ Rz(0.39793742*θ) ├┤ Rz(0.39793742*θ) ├┤ X ├»
  «     └──────────────────┘└─┬─┘├──────────────────┤├──────────────────┤└─┬─┘»
  «q_1: ──────────────────────■──┤ Rz(-0.3979374*θ) ├┤ Rz(-0.3979374*θ) ├──■──»
  «                              └──────────────────┘└──────────────────┘     »
  «     ┌──────────────────┐┌───┐┌───┐┌───┐┌──────────────────┐┌───┐┌───┐
  «q_0: ┤ Rz(-0.0112801*θ) ├┤ X ├┤ H ├┤ X ├┤ Rz(0.18093119*θ) ├┤ X ├┤ H ├
  «     └──────────────────┘└─┬─┘├───┤└─┬─┘└──────────────────┘└─┬─┘├───┤
  «q_1: ──────────────────────■──┤ H ├──■────────────────────────■──┤ H ├
  «                              └───┘                              └───┘
  )
])

trotterized_op contient un Parameter. La méthode bind_parameters traverse les valeurs de liaison de l’expression aux noms de paramètres comme spécifié via un dict. Dans ce cas, il n’y a qu’un seul paramètre.

[56]:
bound = trotterized_op.bind_parameters({evo_time: .5})

bound is a ComposedOp. The second factor is the circuit. Let’s draw it to verify that the binding has taken place.

[57]:
bound[1].to_circuit().draw()
[57]:
global phase: 0.52619
     ┌───┐     ┌───┐┌───┐┌─────────────────┐┌───┐┌───┐┌───┐┌─────────────────┐»
q_0: ┤ H ├──■──┤ H ├┤ X ├┤ Rz(0.090465595) ├┤ X ├┤ H ├┤ X ├┤ Rz(-0.00564005) ├»
     └───┘┌─┴─┐├───┤└─┬─┘└─────────────────┘└─┬─┘├───┤└─┬─┘└─────────────────┘»
q_1: ─────┤ X ├┤ H ├──■───────────────────────■──┤ H ├──■─────────────────────»
          └───┘└───┘                             └───┘                        »
«     ┌───┐┌────────────────┐┌────────────────┐┌───┐┌─────────────────┐┌───┐»
«q_0: ┤ X ├┤ Rz(0.19896871) ├┤ Rz(0.19896871) ├┤ X ├┤ Rz(-0.00564005) ├┤ X ├»
«     └─┬─┘├────────────────┤├────────────────┤└─┬─┘└─────────────────┘└─┬─┘»
«q_1: ──■──┤ Rz(-0.1989687) ├┤ Rz(-0.1989687) ├──■───────────────────────■──»
«          └────────────────┘└────────────────┘                             »
«     ┌───┐┌───┐┌─────────────────┐┌───┐┌───┐
«q_0: ┤ H ├┤ X ├┤ Rz(0.090465595) ├┤ X ├┤ H ├
«     ├───┤└─┬─┘└─────────────────┘└─┬─┘├───┤
«q_1: ┤ H ├──■───────────────────────■──┤ H ├
«     └───┘                             └───┘

Espérances

Les Expectations sont des convertisseurs qui permettent le calcul des valeurs attendues des observables. Ils traversent une arborescence de Operator, remplaçant les OperatorStateFns (observables) par des instructions équivalentes qui sont plus aptes au calcul sur le matériel quantique ou classique. Par exemple, si nous voulons mesurer la valeur attendue d’un Operator o exprimé en somme de Paulis par rapport à une fonction d’état, mais que nous ne pouvons accéder qu’aux mesures diagonales sur le matériel quantique, nous pouvons créer un observable ~StateFn(o) et utiliser une PauliExpectation pour le convertir en une mesure diagonale et des pré-rotations de circuit à ajouter à l’état.

Une autre Expectation intéressante est la AerPauliExpectation, qui convertit l’observable en un CircuitStateFn contenant une instruction intstantanée d’espérance que Aer peut exécuter nativement avec de hautes performances.

[58]:
# Note that XX was the only non-diagonal measurement in our H2 Observable
print(PauliExpectation(group_paulis=False).convert(h2_measurement))
SummedOp([
  ComposedOp([
    OperatorMeasurement(-1.0523732 * II),
    II
  ]),
  ComposedOp([
    OperatorMeasurement(0.39793742 * IZ),
    II
  ]),
  ComposedOp([
    OperatorMeasurement(-0.3979374 * ZI),
    II
  ]),
  ComposedOp([
    OperatorMeasurement(-0.0112801 * ZZ),
    II
  ]),
  ComposedOp([
    OperatorMeasurement(0.18093119 * ZZ),
         ┌───┐
    q_0: ┤ H ├
         ├───┤
    q_1: ┤ H ├
         └───┘
  ])
])

Par défaut group_paulis=True, qui utilisera le AbelianGrouper pour convertir le SummedOp en des groupes de Paulis commutant mutuellement sur les qubits. Cela réduit la surcharge d’exécution du circuit, car chaque groupe peut partager le même circuit exécution.

[59]:
print(PauliExpectation().convert(h2_measurement))
SummedOp([
  ComposedOp([
    OperatorMeasurement(0.18093119 * ZZ
    - 1.0523732 * II),
         ┌───┐
    q_0: ┤ H ├
         ├───┤
    q_1: ┤ H ├
         └───┘
  ]),
  ComposedOp([
    OperatorMeasurement(0.39793742 * IZ
    - 0.3979374 * ZI
    - 0.0112801 * ZZ),
    II
  ])
])

Notez que les convertisseurs agissent récursivement, c’est-à-dire qu’ils traversent une expression en appliquant leur action uniquement lorsque cela est possible. Nous pouvons donc simplement convertir notre évolution complète et notre expression de mesure. Nous pourrions avoir également composé le h2_measurement converti avec notre évolution CircuitStateFn. Nous procéderons en appliquant la conversion sur toute l’expression.

[60]:
diagonalized_meas_op = PauliExpectation().convert(trotterized_op)
print(diagonalized_meas_op)
SummedOp([
  ComposedOp([
    OperatorMeasurement(0.18093119 * ZZ
    - 1.0523732 * II),
    CircuitStateFn(
    global phase: 1.0523732*θ
         ┌───┐     ┌───┐┌───┐┌──────────────────┐┌───┐┌───┐┌───┐»
    q_0: ┤ H ├──■──┤ H ├┤ X ├┤ Rz(0.18093119*θ) ├┤ X ├┤ H ├┤ X ├»
         └───┘┌─┴─┐├───┤└─┬─┘└──────────────────┘└─┬─┘├───┤└─┬─┘»
    q_1: ─────┤ X ├┤ H ├──■────────────────────────■──┤ H ├──■──»
              └───┘└───┘                              └───┘     »
    «     ┌──────────────────┐┌───┐┌──────────────────┐┌──────────────────┐┌───┐»
    «q_0: ┤ Rz(-0.0112801*θ) ├┤ X ├┤ Rz(0.39793742*θ) ├┤ Rz(0.39793742*θ) ├┤ X ├»
    «     └──────────────────┘└─┬─┘├──────────────────┤├──────────────────┤└─┬─┘»
    «q_1: ──────────────────────■──┤ Rz(-0.3979374*θ) ├┤ Rz(-0.3979374*θ) ├──■──»
    «                              └──────────────────┘└──────────────────┘     »
    «     ┌──────────────────┐┌───┐┌───┐┌───┐┌──────────────────┐┌───┐┌───┐┌───┐
    «q_0: ┤ Rz(-0.0112801*θ) ├┤ X ├┤ H ├┤ X ├┤ Rz(0.18093119*θ) ├┤ X ├┤ H ├┤ H ├
    «     └──────────────────┘└─┬─┘├───┤└─┬─┘└──────────────────┘└─┬─┘├───┤├───┤
    «q_1: ──────────────────────■──┤ H ├──■────────────────────────■──┤ H ├┤ H ├
    «                              └───┘                              └───┘└───┘
    )
  ]),
  ComposedOp([
    OperatorMeasurement(0.39793742 * IZ
    - 0.3979374 * ZI
    - 0.0112801 * ZZ),
    CircuitStateFn(
    global phase: 1.0523732*θ
         ┌───┐     ┌───┐┌───┐┌──────────────────┐┌───┐┌───┐┌───┐»
    q_0: ┤ H ├──■──┤ H ├┤ X ├┤ Rz(0.18093119*θ) ├┤ X ├┤ H ├┤ X ├»
         └───┘┌─┴─┐├───┤└─┬─┘└──────────────────┘└─┬─┘├───┤└─┬─┘»
    q_1: ─────┤ X ├┤ H ├──■────────────────────────■──┤ H ├──■──»
              └───┘└───┘                              └───┘     »
    «     ┌──────────────────┐┌───┐┌──────────────────┐┌──────────────────┐┌───┐»
    «q_0: ┤ Rz(-0.0112801*θ) ├┤ X ├┤ Rz(0.39793742*θ) ├┤ Rz(0.39793742*θ) ├┤ X ├»
    «     └──────────────────┘└─┬─┘├──────────────────┤├──────────────────┤└─┬─┘»
    «q_1: ──────────────────────■──┤ Rz(-0.3979374*θ) ├┤ Rz(-0.3979374*θ) ├──■──»
    «                              └──────────────────┘└──────────────────┘     »
    «     ┌──────────────────┐┌───┐┌───┐┌───┐┌──────────────────┐┌───┐┌───┐
    «q_0: ┤ Rz(-0.0112801*θ) ├┤ X ├┤ H ├┤ X ├┤ Rz(0.18093119*θ) ├┤ X ├┤ H ├
    «     └──────────────────┘└─┬─┘├───┤└─┬─┘└──────────────────┘└─┬─┘├───┤
    «q_1: ──────────────────────■──┤ H ├──■────────────────────────■──┤ H ├
    «                              └───┘                              └───┘
    )
  ])
])

Maintenant nous associons plusieurs valeurs de paramètres dans un ListOp, suivi par eval pour évaluer l’expression entière. Nous aurions pu utiliser eval plus tôt si nous lions plus tôt, mais cela ne serait pas efficace. Ici, eval convertira nos CircuitStateFns en VectorStateFns à travers la simulation interne.

[61]:
evo_time_points = list(range(8))
h2_trotter_expectations = diagonalized_meas_op.bind_parameters({evo_time: evo_time_points})

Voici les valeurs d’espérance \(\langle \Phi_+| e^{iHt} H e^{-iHt} |\Phi_+\rangle\) correspondant aux différentes valeurs du paramètre.

[62]:
h2_trotter_expectations.eval()
[62]:
array([-0.88272211+0.0e+00j, -0.88272211+0.0e+00j, -0.88272211+0.0e+00j,
       -0.88272211+0.0e+00j, -0.88272211+0.0e+00j, -0.88272211+0.0e+00j,
       -0.88272211+5.6e-17j, -0.88272211+0.0e+00j])

Exécution des CircuitStateFns avec le CircuitSampler

Le CircuitSampler traverse un Operator et convertit n’importe quel CircuitStateFns en approximations de la fonction d’état qui en résulte par un DictStateFn ou un VectorStateFn en utilisant un backend quantique. Notez que pour approcher la valeur du CircuitStateFn, il doit 1) envoyer la fonction d’état par un canal dépolarisant, qui détruira toutes les informations de phase et 2) remplacera les fréquences échantillonnées par des racines carrées de la fréquence, plutôt que la probabilité brute d’échantillonnage (qui serait l’équivalent de l’échantillonnage du carré de la fonction d’état, selon la règle de Born).

[63]:
sampler = CircuitSampler(backend=Aer.get_backend('aer_simulator'))
# sampler.quantum_instance.run_config.shots = 1000
sampled_trotter_exp_op = sampler.convert(h2_trotter_expectations)
sampled_trotter_energies = sampled_trotter_exp_op.eval()
print('Sampled Trotterized energies:\n {}'.format(np.real(sampled_trotter_energies)))
Sampled Trotterized energies:
 [-0.88272211 -0.88272211 -0.88272211 -0.88272211 -0.88272211 -0.88272211
 -0.88272211 -0.88272211]

Notez à nouveau que les circuits sont remplacés par des dicts avec racines carrées des probabilités d’échantillonnage du circuit. Jetez un coup d’œil à une sous-expression avant et après la conversion :

[64]:
print('Before:\n')
print(h2_trotter_expectations.reduce()[0][0])
print('\nAfter:\n')
print(sampled_trotter_exp_op[0][0])
Before:

ComposedOp([
  OperatorMeasurement(0.18093119 * ZZ
  - 1.0523732 * II),
  CircuitStateFn(
       ┌───┐     ┌───┐┌───┐┌───────┐┌───┐┌───┐┌───┐┌───────┐┌───┐┌───────┐»
  q_0: ┤ H ├──■──┤ H ├┤ X ├┤ Rz(0) ├┤ X ├┤ H ├┤ X ├┤ Rz(0) ├┤ X ├┤ Rz(0) ├»
       └───┘┌─┴─┐├───┤└─┬─┘└───────┘└─┬─┘├───┤└─┬─┘└───────┘└─┬─┘├───────┤»
  q_1: ─────┤ X ├┤ H ├──■─────────────■──┤ H ├──■─────────────■──┤ Rz(0) ├»
            └───┘└───┘                   └───┘                   └───────┘»
  «     ┌───────┐┌───┐┌───────┐┌───┐┌───┐┌───┐┌───────┐┌───┐┌───┐┌───┐
  «q_0: ┤ Rz(0) ├┤ X ├┤ Rz(0) ├┤ X ├┤ H ├┤ X ├┤ Rz(0) ├┤ X ├┤ H ├┤ H ├
  «     ├───────┤└─┬─┘└───────┘└─┬─┘├───┤└─┬─┘└───────┘└─┬─┘├───┤├───┤
  «q_1: ┤ Rz(0) ├──■─────────────■──┤ H ├──■─────────────■──┤ H ├┤ H ├
  «     └───────┘                   └───┘                   └───┘└───┘
  )
])

After:

ComposedOp([
  OperatorMeasurement(0.18093119 * ZZ
  - 1.0523732 * II),
  DictStateFn({'00': 0.7167090588237321, '11': 0.6973723001381686})
])
[65]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright

Version Information

Qiskit SoftwareVersion
qiskit-terra0.19.0.dev0+fe3eb3f
qiskit-aer0.10.0.dev0+b78f265
qiskit-ignis0.7.0.dev0+0eb9dcc
qiskit-ibmq-provider0.17.0.dev0+15d2dfe
System information
Python version3.9.5
Python compilerClang 10.0.0
Python builddefault, May 18 2021 12:31:01
OSDarwin
CPUs4
Memory (Gb)32.0
Fri Oct 29 16:10:45 2021 BST

This code is a part of Qiskit

© Copyright IBM 2017, 2021.

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.