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 StateFn
s 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 StateFn
s 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: PrimitiveOp
s¶
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})
])
])
])
ListOp
s: 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 OperatorBase
s 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 CircuitOp
s. 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 OperatorBase
s 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 desOperatorBase
s 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émentsoplist
. Pour la simplicité de diffusion, cette fonction est définie sur les tableaux NumPy.coeff
- Un coefficient multiplicateur de la primitive. Notez quecoeff
peut être int, float, complexe ou un objetParameter
libre (à partir deqiskit. ircuit
dans Terra) pour être lié plus tard en utilisantmy_op.bind_parameters
.abelian
- Indique si les Opérateurs deoplist
sont connus pour être mutuellement commutés (généralement définis après avoir été convertis par le convertisseurAbelianGrouper
).
Notez que ListOp
supporte les surcharges de séquences typiques, donc vous pouvez utiliser l’indexation comme my_op[4]
pour accéder aux OperatorBase
s 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 PauliOp
s.
[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 EvolvedOp
s 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 Expectation
s sont des convertisseurs qui permettent le calcul des valeurs attendues des observables. Ils traversent une arborescence de Operator, remplaçant les OperatorStateFn
s (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 CircuitStateFn
s en VectorStateFn
s à 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 CircuitStateFn
s avec le CircuitSampler
¶
Le CircuitSampler
traverse un Operator et convertit n’importe quel CircuitStateFn
s 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 Software | Version |
---|---|
qiskit-terra | 0.19.0.dev0+fe3eb3f |
qiskit-aer | 0.10.0.dev0+b78f265 |
qiskit-ignis | 0.7.0.dev0+0eb9dcc |
qiskit-ibmq-provider | 0.17.0.dev0+15d2dfe |
System information | |
Python version | 3.9.5 |
Python compiler | Clang 10.0.0 |
Python build | default, May 18 2021 12:31:01 |
OS | Darwin |
CPUs | 4 |
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.