Nota
Esta página foi gerada, a partir do tutorials/operators/01_operator_flow.ipynb.
Fluxo do operador¶
Introdução¶
Qiskit fornece classes representando estados, operadores e somas, produtos de tensores e composições. Estas construções algébricas nos permitem construir expressões representando os operadores.
Introduzimos expressões construindo-as a partir de operadores Pauli. Nas seções subsequentes, exploramos com mais detalhes os operadores e os estados, como estão representados e o que podemos fazer com eles. Na última seção nós construímos um estado, evoluindo-o com um operador Hamiltoniano e calculando os valores de expectativa desse observável.
Operadores Pauli, somas, composições e produtos de tensor¶
Os operadores de base mais importantes são os operadores Pauli. Os operadores Pauli estão representados assim.
[1]:
from qiskit.opflow import I, X, Y, Z
print(I, X, Y, Z)
I X Y Z
Estes operadores também podem transportar um coeficiente.
[2]:
print(1.5 * I)
print(2.5 * X)
1.5 * I
2.5 * X
Estes coeficientes permitem que os operadores sejam utilizados como termos em uma soma.
[3]:
print(X + 2.0 * Y)
1.0 * X
+ 2.0 * Y
Os produtos de tensor são denotados com um identificador, como este.
[4]:
print(X^Y^Z)
XYZ
A composição é indicada pelo símbolo @
.
[5]:
print(X @ Y @ Z)
iI
Nos dois exemplos anteriores, o produto de tensor e a composição dos operadores Pauli foram imediatamente reduzidos ao equivalente (possivelmente multi-qubit) operador Pauli. Se usar o tensor ou se compormos objetos mais complicados, o resultado são objetos que representam as operações não avaliadas, ou seja, expressões algébricas.
Por exemplo, compondo duas somas temos
[6]:
print((X + Y) @ (Y + Z))
1j * Z
+ -1j * Y
+ 1.0 * I
+ 1j * X
E usando o tensor, duas somas retornam
[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))
Cada operador Pauli é uma instância de PauliOp
, que envolve uma instância de qiskit.quantum_info.Pauli
e adiciona um coeficiente coeff
. Em geral, um PauliOp
representa um produto de tensor ponderado dos operadores Pauli.
[9]:
2.0 * X^Y^Z
[9]:
PauliOp(Pauli('XYZ'), coeff=2.0)
Para a codificação dos operadores Pauli como pares de valores booleanos, consulte a documentação 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.
Parte I: Funções e Medidas do Estado¶
Os estados quânticos são representados por subclasses da classe StateFn
. Existem quatro representações de estados quânticos: DictStateFn
é uma representação esparsa na base computacional, apoiada por um dict
. VectorStateFn
é uma representação densa na base computacional apoiada por um array numpy. CircuitStateFn
é apoiado por um circuito e representa o estado obtido executando o circuito sobre o estado de base computacional all-zero. OperatorStateFn
representa estados mistos através de uma matriz de densidade. (Como veremos mais adiante, OperatorStateFn
também é usado para representar observáveis.)
Várias instâncias StateFn
são fornecidas por conveniência. Por exemplo Zero, One, Plus, Minus
.
[11]:
from qiskit.opflow import (StateFn, Zero, One, Plus, Minus, H,
DictStateFn, VectorStateFn, CircuitStateFn, OperatorStateFn)
Zero
e One
representam os estados quânticos \(|0\rangle\) e \(|1\rangle\). Eles são representados via DictStateFn
.
[12]:
print(Zero, One)
DictStateFn({'0': 1}) DictStateFn({'1': 1})
Plus
e Minus
, representando estados \((|0\rangle + |1\rangle)/\sqrt{2}\) e \((|0\rangle - |1\rangle)/\sqrt{2}\) são representados via circuitos. H
é um sinônimo de Plus
.
[13]:
print(Plus, Minus)
CircuitStateFn(
┌───┐
q: ┤ H ├
└───┘
) CircuitStateFn(
┌───┐┌───┐
q: ┤ X ├┤ H ├
└───┘└───┘
)
Indexar em estados quânticos é feito com o método eval
. Estes exemplos retornam os coeficientes dos estados base 0
e 1
. (Abaixo, veremos que o método eval
é usado também para outras computações.)
[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)
O vetor duplo de um estado quântico, que é o bra correspondente a um ket é obtido através do método adjoint
. O StateFn
transporta uma medição is_measurement
que é False
se o objeto é um ket e True
se é um bra.
Aqui, nós construímos \(\langle 1 |\).
[15]:
One.adjoint()
[15]:
DictStateFn({'1': 1}, coeff=1.0, is_measurement=True)
Por conveniência, pode-se obter o duplo vetor com um til, da seguinte forma
[16]:
~One
[16]:
DictStateFn({'1': 1}, coeff=1.0, is_measurement=True)
Operações algébricas e predicados¶
Muitas operações algébricas e predicados entre StateFn
s são suportados, incluindo:
+
- adição-
- subtração, negação (multiplicação escalar por -1)*
- multiplicação escalar/
- divisão escalar@
- composição^
- produto tensorial ou potência tensorial (tensor com si mesmo n vezes)**
- potência de composição (composição com si mesmo n vezes)==
- igualdade~
- adjunto, alternando entre uma função de estado e medição
Esteja bastante ciente de que esses operadores obedecem às regras do Python para precedência de operador, o que pode não ser aquilo que você espera matematicamente. Por exemplo, I^X + X^I
será realmente analisado como I ^ (X + X) ^ I == 2 * (I^X^I)
porque o Python avalia +
antes de ^
. Nestes casos, você pode usar os métodos (.tensor()
, etc) ou parênteses.
StateFn
s carregam um coeficiente. Isso nos permite multiplicar estados por um escalar e, assim, construir somas.
Aqui, construímos \((2 + 3i)|0\rangle\).
[17]:
(2.0 + 3.0j) * Zero
[17]:
DictStateFn({'0': 1}, coeff=(2+3j), is_measurement=False)
Aqui, vemos que adicionando dois DictStateFn
s retorna um objeto do mesmo tipo. Construímos \(|0\rangle + |1\rangle\).
[18]:
print(Zero + One)
DictStateFn({'0': 1.0, '1': 1.0})
Observe que você deve normalizar os estados manualmente. Por exemplo, para construir \((|0\rangle + |1\rangle)/\sqrt{2}\), nós escrevemos
[19]:
import math
v_zero_one = (Zero + One) / math.sqrt(2)
print(v_zero_one)
DictStateFn({'0': 1.0, '1': 1.0}) * 0.7071067811865475
Em outros casos, o resultado é uma representação simbólica de uma soma. Aqui, por exemplo, está uma representação de \(|+\rangle + |-\rangle\).
[20]:
print(Plus + Minus)
SummedOp([
CircuitStateFn(
┌───┐
q: ┤ H ├
└───┘
),
CircuitStateFn(
┌───┐┌───┐
q: ┤ X ├┤ H ├
└───┘└───┘
)
])
O operador composição é usado para realizar um produto interno, que por padrão é mantido em uma forma não calculada. Aqui está uma representação de \(\langle 1 | 1 \rangle\).
[21]:
print(~One @ One)
ComposedOp([
DictMeasurement({'1': 1}),
DictStateFn({'1': 1})
])
Note que a flag is_measurement
faz com que o estado (bra) ~One
seja impresso como DictMeasurement
.
Expressões simbólicas podem ser calculadas com o método eval
.
[22]:
(~One @ One).eval()
[22]:
1.0
[23]:
(~v_zero_one @ v_zero_one).eval()
[23]:
0.9999999999999998
Aqui está \(\langle - | 1 \rangle = \langle (\langle 0| - \langle 1|)/\sqrt{2} | 1\rangle\).
[24]:
(~Minus @ One).eval()
[24]:
(-0.7071067811865475-8.7e-17j)
O operador composição @
é equivalente a chamar o método compose
.
[25]:
print((~One).compose(One))
ComposedOp([
DictMeasurement({'1': 1}),
DictStateFn({'1': 1})
])
Produtos internos também podem ser calculados usando o método eval
diretamente, sem construir um ComposedOp
.
[26]:
(~One).eval(One)
[26]:
1.0
Produtos de tensor simbólicos são construídos da seguinte forma. Aqui está \(|0\rangle \otimes |+\rangle\).
[27]:
print(Zero^Plus)
TensoredOp([
DictStateFn({'0': 1}),
CircuitStateFn(
┌───┐
q: ┤ H ├
└───┘
)
])
Isto pode ser representado como um simples (não composto) CircuitStateFn
.
[28]:
print((Zero^Plus).to_circuit_op())
CircuitStateFn(
┌───┐
q_0: ┤ H ├
└───┘
q_1: ─────
)
Potências de tensor são construídos usando o acento circunflexo ^
da seguinte forma. Aqui está o \(600 (|11111\rangle + |00000\rangle)\) e \(|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})
O método to_matrix_op
converte para 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}
Construir um StateFn é fácil. A classe StateFn
também serve como uma fábrica e pode receber qualquer primitivo aplicável em seu construtor e retornar a subclasse StateFn correta. Atualmente, os seguintes primitivos podem ser passados para o construtor, listados junto com a subclasse StateFn
que eles produzem:
str (igual a alguma bitstring de base) -> DictStateFn
dict -> DictStateFn
Objeto Result do Qiskit -> DictStateFn
lista -> VectorStateFn
np.ndarray -> VectorStateFn
Statevector -> VectorStateFn
QuantumCircuit -> CircuitStateFn
Instrução -> 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 ├
└──────────────────────────────────────────────────────────┘
)
Parte II: PrimitiveOp
s¶
Os operadores básicos são subclasses de PrimitiveOp
. Assim como StateFn
, PrimitiveOp
é também uma fábrica para criar o tipo correto de PrimitiveOp
para um dado primitivo. Atualmente, os seguintes primitivos podem ser passados para o construtor, listados junto com a subclasse PrimitiveOp
que eles produzem:
Terra’s Pauli -> PauliOp
Instrução -> CircuitOp
QuantumCircuit -> CircuitOp
Lista 2d -> 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
Elementos da Matriz¶
O método eval
retorna uma coluna de um operador. Por exemplo, o operador Pauli \(X\) é representado por um PauliOp
. Pedir uma coluna retorna uma instância da representação esparsa, uma DictStateFn
.
[33]:
X
[33]:
PauliOp(Pauli('X'), coeff=1.0)
[34]:
print(X.eval('0'))
DictStateFn({'1': (1+0j)})
Daqui decorre que indexação em um operador, que está obtendo um elemento matrix é realizada com duas chamadas para o método eval
.
Temos \(X = \left(\begin{matrix} 0 & 1 \\ 1 & 0 \end{matrix} \right)\). E o elemento da matriz \(\left\{X \right\}_{0,1}\) é
[35]:
X.eval('0').eval('1')
[35]:
(1+0j)
Aqui está um exemplo usando o operador de dois qubit CX
, o X
controlado, que é representado por um circuito.
[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)
Aplicando um operador a um vetor de estado¶
Aplicar um operador a um vetor de estado pode ser feito com o método compose
(equivalentemente o operador @
). Aqui está uma representação de \(X | 1 \rangle = |0\rangle\).
[39]:
print(X @ One)
ComposedOp([
X,
DictStateFn({'1': 1})
])
Uma representação mais simples, a representação ` ` DictStateFn ` ` ` de \(|0\rangle\), é obtida com ` ` eval ` `.
[40]:
(X @ One).eval()
[40]:
DictStateFn({'0': (1+0j)}, coeff=(1+0j), is_measurement=False)
A etapa intermediária ComposedOp
pode ser evitada com o uso eval
diretamente.
[41]:
X.eval(One)
[41]:
DictStateFn({'0': (1+0j)}, coeff=(1+0j), is_measurement=False)
Composições e produtos de tensor de operadores são feitos com @
e ^
. Aqui estão alguns exemplos.
[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 ├
└───┘└───┘
)
])
Part III: ListOp
and subclasses¶
ListOp
¶
ListOp
é um contêiner para vetorização efetiva de operações sobre uma lista de operadores e estados.
[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})
])
])
Por exemplo, a composição acima é distribuída através das listas (ListOp
) usando o método de simplificação 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).
Estes OperatorBase
s compostos são como construímos uma computação rica e cada vez mais complexa a partir dos blocos de construção PrimitiveOp
e StateFn
.
Cada ListOp
tem quatro propriedades:
oplist
- A lista deOperatorBase
s, que podem representar termos, fatores, etc.combo_fn
- A função leva uma lista de números complexos para um valor de saída que define como combinar as saídas dos itens dooplist
. Para simplificar a transmissão, essa função é definida em arrays NumPy.coeff
- Um coeficiente que multiplica o primitivo. Note quecoeff
pode ser int, float, complex ou um objetoParameter
livre (doqiskit.circuit
no Terra) para ser vinculado posteriormente usandomy_op.bind_parameters
.abelian
- Indica se os Operadores emoplist
são conhecidos por comutarem mutuamente (geralmente definidos após serem convertidos pelo conversorAbelianGrouper
).
Observe que ListOp
suporta sobrecargas de sequência típicas, então você pode usar indexação como my_op[4]
para acessar o OperatorBase
s em oplist
.
OperatorStateFn
¶
Nós mencionamos acima que OperatorStateFn
representa um operador de densidade. Mas, se a medição is_measurement
é True
, então OperatorStateFn
representa um observável. O valor esperado deste observável pode então ser construído através de ComposedOp
. Ou, diretamente, usando eval
. Lembre que a medição is_measurement
(propriedade) é definida através do método adjoint
.
Aqui construímos o observável correspondente ao operador Pauli \(Z\). Observe que, ao imprimir, ele é chamado de 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)
Aqui, calculamos \(\langle 0 | Z | 0 \rangle\), \(\langle 1 | Z | 1 \rangle\), e \(\langle + | Z | + \rangle\), onde \(|+\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
Parte IV: Conversores¶
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
Evoluções, exp_i()
, e o 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.
Soma ponderada dos operadores Pauli¶
Um Hamiltoniano expresso como uma combinação linear de operadores Pauli multi-qubit podem ser construídos assim.
[49]:
two_qubit_H2 = (-1.0523732 * I^I) + \
(0.39793742 * I^Z) + \
(-0.3979374 * Z^I) + \
(-0.0112801 * Z^Z) + \
(0.18093119 * X^X)
Observe que two_qubit_H2
é representado como um SummedOp
cujos termos são PauliOp
s.
[50]:
print(two_qubit_H2)
-1.0523732 * II
+ 0.39793742 * IZ
- 0.3979374 * ZI
- 0.0112801 * ZZ
+ 0.18093119 * XX
Em seguida, multiplicamos o Hamiltoniano por um Parameter
. Este Parameter
é armazenado na propriedade coeff
do SummedOp
. Chamando exp_i()
no resultado, envolve-o em EvolvedOp
, representando uma exponenciação.
[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)
Nós construímos h2_measurement
, que representa two_qubit_H2
como um observável.
[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)
Nós construímos o estado de Bell \(|\Phi_+\rangle\) através de \(\text{CX} (H\otimes I) |00\rangle\).
[53]:
bell = CX @ (I ^ H) @ Zero
print(bell)
CircuitStateFn(
┌───┐
q_0: ┤ H ├──■──
└───┘┌─┴─┐
q_1: ─────┤ X ├
└───┘
)
Aqui está a expressão \(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 ├
└───┘
)
])
Normalmente, queremos aproximar \(e^{-iHt}\) usando portões two-qubit. Conseguimos isso com o método convert
do PauliTrotterEvolution
, que analisa expressões aplicando trotterização para todos EvolvedOp
s encontrados. Embora utilizemos PauliTrotterEvolution
aqui, existem outras possibilidades, como MatrixEvolution
, que executa a exponenciação de forma exata.
[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
contém um Parameter
. O método bind_parameters
percorre a expressão ligando valores para nomes de parâmetro como especificado através da dict
. Neste caso, há apenas um parâmetro.
[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 ├ « └───┘ └───┘
Expectativas¶
Expectation
s são conversores que permitem o cálculo dos valores probabilísticos esperados de observáveis. Eles percorrem uma árvore do Operador, substituindo OperatorStateFn
s (observáveis) por instruções equivalentes que são mais fáceis de calcular em hardware quântico ou clássico. Por exemplo, se quisermos medir o valor esperado de um Operador o
expresso como uma soma de Paulis em relação a alguma função de estado, mas só podemos acessar medições diagonais em hardware quântico, nós podemos criar um ~StateFn(o)
e usar um PauliExpectation
para convertê-lo em medições diagonais e pré-rotações de circuito para anexar ao estado.
Outra Expectation
interessante é a AerPauliExpectation
, que converte o observável em um CircuitStateFn
contendo um instantâneo da instrução especial que Aer
pode executar nativamente com alto desempenho.
[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 ├
└───┘
])
])
Por padrão o group_paulis=True
, que irá utilizar o AbelianGrouper
para converter o SummedOp
em grupos de Paulis mutuamente comutativos qubit-wise. Isto reduz a sobrecarga na execução do circuito, pois cada grupo pode compartilhar a execução do mesmo circuito.
[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
])
])
Observe que os conversores agem recursivamente, ou seja, eles percorrem uma expressão aplicando sua ação apenas quando possível. Assim, podemos apenas converter a nossa expressão de evolução completa e a expressão de medição. Nós poderíamos ter composto de forma equivalente h2_measurement
com nossa evolução CircuitStateFn
. Procedemos aplicando a conversão na expressão toda.
[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 ├
« └───┘ └───┘
)
])
])
Agora vamos vincular vários valores de parâmetros em uma ListOp
, seguida de eval
para avaliar a expressão inteira. Poderíamos ter usado eval
anteriormente se nós vinculássemos mais cedo, mas não seria eficiente. Aqui, eval
irá converter nosso CircuitStateFn
s para VectorStateFn
s internamente através de simulação.
[61]:
evo_time_points = list(range(8))
h2_trotter_expectations = diagonalized_meas_op.bind_parameters({evo_time: evo_time_points})
Aqui estão os valores esperados \(\langle \Phi_+| e^{iHt} H e^{-iHt} |\Phi_+\rangle\) correspondentes aos diferentes valores do parâmetro.
[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])
Executando CircuitStateFn
s com o CircuitSampler
¶
O CircuitSampler
percorre um Operador e converte qualquer CircuitStateFn
s em aproximações da função de estado resultante por um DictStateFn
ou VectorStateFn
usando um backend quântico. Observe que para aproximar o valor do CircuitStateFn
, ele deve 1) enviar a função de estado através de um canal despolarizante, que destruirá todas as informações de fase e 2) substituir as frequências amostradas por raízes quadradas da frequência, em vez da probabilidade bruta de amostragem (que seria o equivalente a amostrar o quadrado da função de estado, de acordo com a regra 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]
Note novamente que os circuitos são substituídos por dicts com raízes quadradas das probabilidades de amostragem do circuito. Dê uma olhada em uma sub-expressão antes e depois da conversão:
[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.