Nota
Esta página foi gerada, a partir do tutorials/operators/01_operator_flow.ipynb.
Execute interativamente no IBM Quantum lab.
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.aqua.operators 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)
SummedOp([
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)
1j * I
In the preceding two examples, the tensor product and composition of Pauli operators were immediately reduced to the equivalent (possibly multi-qubit) Pauli operator. If we tensor or compose more complicated objects, the result is objects representing the unevaluated operations. That is, algebraic expressions.
Por exemplo, compondo duas somas temos
[6]:
print((X + Y) @ (Y + Z))
ComposedOp([
SummedOp([
X,
Y
]),
SummedOp([
Y,
Z
])
])
E usando o tensor, duas somas retornam
[7]:
print((X + Y) ^ (Y + Z))
TensoredOp([
SummedOp([
X,
Y
]),
SummedOp([
Y,
Z
])
])
Vamos dar uma olhada nos tipos introduzidos acima. Primeiro nos operadores Pauli.
[8]:
(I, X)
[8]:
(PauliOp(Pauli(z=[False], x=[False]), coeff=1.0),
PauliOp(Pauli(z=[False], x=[True]), 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(z=[True, True, False], x=[False, True, True]), coeff=2.0)
Para a codificação dos operadores Pauli como pares de valores booleanos, consulte a documentação qiskit.quantum_info.Pauli
.
Todos os objetos representando os operadores, sejam como “primitivas” tais como o PauliOp
ou expressões algébricas, carregam um coeficiente
[10]:
print(1.1 * ((1.2 * X)^(Y + (1.3 * Z))))
1.1 * TensoredOp([
1.2 * X,
SummedOp([
Y,
1.3 * Z
])
])
No que se segue, analisamos mais ampla e profundamente os operadores do Qiskit, os estados e os blocos de construção de algoritmos quânticos.
Parte I: Funções e Medidas do Estado¶
Quantum states are represented by subclasses of the class StateFn
. There are four representations of quantum states: DictStateFn
is a sparse representation in the computational basis, backed by a dict
. VectorStateFn
is a dense representation in the computational basis backed by a numpy array. CircuitStateFn
is backed by a circuit and represents the state obtained by executing the circuit on the all-zero computational-basis state. OperatorStateFn
represents mixed states
via a density matrix. (As we will see later, OperatorStateFn
is also used to represent observables.)
Várias instâncias StateFn
são fornecidas por conveniência. Por exemplo Zero, One, Plus, Minus
.
[11]:
from qiskit.aqua.operators 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_0: ┤ H ├
└───┘
) CircuitStateFn(
┌───┐┌───┐
q_0: ┤ 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.7071067811865476+0j)
(-0.7071067811865476+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 suportadas, incluindo: * +
- adição * -
-subtração, negação (multiplicação escalar por -1) * *
- multiplicação escalar * /
- divisão escalar * @
- composição * ^
- produto do tensor ou potência do tensor (tensor com n repetições de si) * **
- potência de composição (compor consigo mesma n vezes) * ==
- igualdade * ~
- adjunção, alternando entre a Função de Estado e Medição
Be aware that parentheses are often necessary to override operator precedence.
StateFn
s carregam um coeficiente. Isso nos permite multiplicar estados por uma grandeza escalar e então 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_0: ┤ H ├
└───┘
),
CircuitStateFn(
┌───┐┌───┐
q_0: ┤ X ├┤ H ├
└───┘└───┘
)
])
The composition operator is used to perform an inner product, which by default is held in an unevaluated form. Here is a representation of \(\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.7071067811865476-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_0: ┤ 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.2626953125, '0111': 0.251953125, '1111': 0.2470703125, '0101': 0.23828125}
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: ┤ RY(θ[0]) ├──■──┤ RY(θ[2]) ├──■──┤ RY(θ[4]) ├──■──┤ RY(θ[6]) ├
├──────────┤┌─┴─┐├──────────┤┌─┴─┐├──────────┤┌─┴─┐├──────────┤
q_1: ┤ RY(θ[1]) ├┤ X ├┤ RY(θ[3]) ├┤ X ├┤ RY(θ[5]) ├┤ X ├┤ RY(θ[7]) ├
└──────────┘└───┘└──────────┘└───┘└──────────┘└───┘└──────────┘
)
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
quantum_info.Operator Terra -> MatrixOp
[32]:
from qiskit.aqua.operators import X, Y, Z, I, CX, T, H, S, PrimitiveOp
Elementos da Matriz¶
The eval
method returns a column from an operator. For example, the Pauli \(X\) operator is represented by a PauliOp
. Asking for a column returns an instance of the sparse representation, a DictStateFn
.
[33]:
X
[33]:
PauliOp(Pauli(z=[False], x=[True]), 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.0, 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.0, 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: ──■──┤ I ├┤ H ├──■──┤ I ├┤ H ├
┌─┴─┐└───┘├───┤┌─┴─┐└───┘├───┤
q_1: ┤ X ├──■──┤ H ├┤ X ├──■──┤ H ├
└───┘┌─┴─┐├───┤└───┘┌─┴─┐├───┤
q_2: ──■──┤ X ├┤ H ├──■──┤ X ├┤ H ├
┌─┴─┐└───┘├───┤┌─┴─┐└───┘├───┤
q_3: ┤ X ├──■──┤ H ├┤ X ├──■──┤ H ├
├───┤┌─┴─┐├───┤├───┤┌─┴─┐├───┤
q_4: ┤ I ├┤ X ├┤ H ├┤ I ├┤ 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: ──────────
q_1: ──────────
┌───┐┌───┐
q_2: ┤ X ├┤ H ├
└───┘└───┘
)
[43]:
print(~One @ Minus)
ComposedOp([
DictMeasurement({'1': 1}),
CircuitStateFn(
┌───┐┌───┐
q_0: ┤ X ├┤ H ├
└───┘└───┘
)
])
Parte III ListOp
e subclasses¶
ListOp
¶
ListOp
é um contêiner para vetorização efetiva de operações sobre uma lista de operadores e estados.
[44]:
from qiskit.aqua.operators 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
, introduzido acima é útil para operações de vetorização. Mas, ele também serve como uma superclasse para classes compostas por listas. Se você já usou o que foi exposto acima, notará que você pode facilmente realizar operações entre OperatorBase
s que em geral podemos não saber como executar eficientemente (ou simplesmente por que não implementamos um procedimento eficiente ainda), como a adição entre CircuitOp
s. Nesses casos, você pode receber um resultado ListOp
(ou uma subclasse destes) da sua operação, representando uma execução lenta da operação. Por exemplo, se você tentar adicionar em conjunto um DictStateFn
and a CircuitStateFn
, você receberá um SummedOp
representando a soma dos dois. Esta função Estado composta ainda tem um eval
funcional (mas pode precisar realizar um cálculo não escalável por baixo dos panos, como por exemplo, converter ambos para vetores).
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
.
Todo ListOp
tem quatro propriedades: * oplist
- A lista de OperatorBase
s que pode representar termos, fatores, etc. * combo_fn
- A função que recebe uma lista de números complexos que gera um valor de saída que define como combinar esses valores dos itens oplist
. Para simplificar a transmissão, essa função é definida sobre matrizes NumPy. * coeff
- Um coeficiente de multiplicação do primitivo. Observe que coeff
pode ser int, float, complex ou um objeto Parameter
livre (qiskit.circuit
no Terra) para ser vinculado mais tarde utilizando my_op.bind_parameters
. * abelian
- informa se os Operadores na oplist
são conhecidos por comutar mutuamente (geralmente definido depois de convertido pelo conversor AbelianGrouper
).
Note que ListOp
suporta sobrecargas típicas de iteração, permitindo 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=[True], x=[False]), 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¶
Conversores são classes que manipulam operadores e estados e executam blocos de construção de algoritmos. Exemplos incluem mudar a base dos operadores e Trotterização. Conversores examinam uma expressão e executam uma determinada manipulação ou substituição, definida pelo método convert()
do conversor, dos Operadores internos. Normalmente, se um conversor encontra um OperatorBase
na recursão que é irrelevante para o seu propósito de conversão, o OperatorBase
é deixado inalterado.
[48]:
import numpy as np
from qiskit.aqua.operators import I, X, Y, Z, H, CX, Zero, ListOp, PauliExpectation, PauliTrotterEvolution, CircuitSampler, MatrixEvolution, Suzuki
from qiskit.circuit import Parameter
from qiskit import BasicAer
Evoluções, exp_i()
, e o EvolvedOp
¶
Todo PrimitiveOp
e ListOp
tem uma função .exp_i()
tal que H.exp_i()
corresponde a \(e^{-iH}\). Na prática, apenas alguns desses Operadores têm uma exponenciação computável eficientemente (como a MatrixOp e os PauliOps com apenas um único single-qubit Pauli não-identidade), por isso precisamos retornar um parâmetro provisório, ou representação simbólica, (semelhante a como SummedOp
é um resultado representativo para quando não podemos realizar adição). Este marcador é chamado EvolvedOp
e contém a OperatorBase
a ser exponenciada em sua propriedade .primitive
.
Operadores Qiskit suportam parametrização integralmente, então podemos usar um Parameter
para o nosso tempo de evolução aqui. Observe que não há nenhum argumento de “tempo de evolução” em qualquer função. O Operador flui exponencialmente seja qual for o operador informado a ele, e se escolhermos multiplicar o operador por um tempo de evolução, \(e^{-iHt}\), isso será refletido em nossos parâmetros de exponenciação.
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)
SummedOp([
-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*θ * SummedOp([
-1.0523732 * II,
0.39793742 * IZ,
-0.3979374 * ZI,
-0.0112801 * ZZ,
0.18093119 * XX
]))
EvolvedOp(SummedOp([PauliOp(Pauli(z=[False, False], x=[False, False]), coeff=-1.0523732), PauliOp(Pauli(z=[True, False], x=[False, False]), coeff=0.39793742), PauliOp(Pauli(z=[False, True], x=[False, False]), coeff=-0.3979374), PauliOp(Pauli(z=[True, True], x=[False, False]), coeff=-0.0112801), PauliOp(Pauli(z=[False, False], x=[True, True]), coeff=0.18093119)], coeff=1.0*θ, abelian=False), 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(SummedOp([
-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(SummedOp([
-1.0523732 * II,
0.39793742 * IZ,
-0.3979374 * ZI,
-0.0112801 * ZZ,
0.18093119 * XX
])),
e^(-i*1.0*θ * SummedOp([
-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(SummedOp([
-1.0523732 * II,
0.39793742 * IZ,
-0.3979374 * ZI,
-0.0112801 * ZZ,
0.18093119 * XX
])),
CircuitStateFn(
global phase: 1.0524
┌───┐ ┌───┐┌───┐┌──────────────────┐┌───┐┌───┐┌───┐»
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
é um ComposedOp
. O segundo fator é o circuito. Vamos desenhá-lo para confirmar que a ligação ocorreu.
[57]:
bound[1].to_circuit().draw()
[57]:
global phase: 1.0524 ┌───┐ ┌───┐┌───┐┌────────────────────────┐┌───┐┌───┐┌───┐» q_0: ┤ H ├──■──┤ H ├┤ X ├┤ RZ(0.0904655950000000) ├┤ X ├┤ H ├┤ X ├» └───┘┌─┴─┐├───┤└─┬─┘└────────────────────────┘└─┬─┘├───┤└─┬─┘» q_1: ─────┤ X ├┤ H ├──■──────────────────────────────■──┤ H ├──■──» └───┘└───┘ └───┘ » « ┌──────────────────────────┐┌───┐┌───────────────────────┐ » «q_0: ┤ RZ(-0.00564005000000000) ├┤ X ├┤ RZ(0.198968710000000) ├─» « └──────────────────────────┘└─┬─┘├───────────────────────┴┐» «q_1: ──────────────────────────────■──┤ RZ(-0.198968700000000) ├» « └────────────────────────┘» « ┌───────────────────────┐ ┌───┐┌──────────────────────────┐┌───┐┌───┐» «q_0: ┤ RZ(0.198968710000000) ├─┤ X ├┤ RZ(-0.00564005000000000) ├┤ X ├┤ H ├» « ├───────────────────────┴┐└─┬─┘└──────────────────────────┘└─┬─┘├───┤» «q_1: ┤ RZ(-0.198968700000000) ├──■────────────────────────────────■──┤ H ├» « └────────────────────────┘ └───┘» « ┌───┐┌────────────────────────┐┌───┐┌───┐ «q_0: ┤ X ├┤ RZ(0.0904655950000000) ├┤ X ├┤ H ├ « └─┬─┘└────────────────────────┘└─┬─┘├───┤ «q_1: ──■──────────────────────────────■──┤ 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(AbelianSummedOp([
-1.0523732 * II,
0.18093119 * ZZ
])),
┌───┐
q_0: ┤ H ├
├───┤
q_1: ┤ H ├
└───┘
]),
ComposedOp([
OperatorMeasurement(AbelianSummedOp([
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(AbelianSummedOp([
-1.0523732 * II,
0.18093119 * ZZ
])),
CircuitStateFn(
global phase: 1.0524
┌───┐ ┌───┐┌───┐┌──────────────────┐┌───┐┌───┐┌───┐»
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(AbelianSummedOp([
0.39793742 * IZ,
-0.3979374 * ZI,
-0.0112801 * ZZ
])),
CircuitStateFn(
global phase: 1.0524
┌───┐ ┌───┐┌───┐┌──────────────────┐┌───┐┌───┐┌───┐»
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-1.111e-15j, -0.88272211-1.165e-15j,
-0.88272211-1.065e-15j, -0.88272211-1.178e-15j,
-0.88272211-1.113e-15j, -0.88272211-9.250e-16j,
-0.88272211-1.054e-15j, -0.88272211-1.156e-15j])
Executando CircuitStateFn
s com o CircuitSampler
¶
O CircuitSampler
percorre um Operador e converte qualquer CircuitStateFns
em aproximações da função estado resultante de um DictStateFn
ou VectorStateFn
usando um backend quântico. Observe que para aproximar o valor do CircuitStateFn
, ele deve 1) enviar a função estado através de um canal de despolarização, o que irá destruir todas as informações da fase e 2) substituir as frequências amostradas por raízes quadradas da frequência, em vez da probabilidade bruta de amostragem (que seria equivalente a amostragem do quadrado da função estado, pela regra Born.)
[63]:
sampler = CircuitSampler(backend=BasicAer.get_backend('qasm_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(AbelianSummedOp([
-1.0523732 * II,
0.18093119 * ZZ
])),
CircuitStateFn(
global phase: 1.0524
┌───┐ ┌───┐┌───┐┌───────┐┌───┐┌───┐┌───┐┌───────┐┌───┐┌───────┐»
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(AbelianSummedOp([
-1.0523732 * II,
0.18093119 * ZZ
])),
DictStateFn({'00': 0.7207851621669248, '11': 0.6931585316505886})
])
[65]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright
Version Information
Qiskit Software | Version |
---|---|
Qiskit | None |
Terra | 0.17.0.dev0+8c40b02 |
Aer | 0.5.2 |
Ignis | 0.4.0.dev0+15b7177 |
Aqua | 0.9.0.dev0+2734384 |
IBM Q Provider | 0.7.2 |
System information | |
Python | 3.8.3 (default, May 17 2020, 18:15:42) [GCC 10.1.0] |
OS | Linux |
CPUs | 12 |
Memory (Gb) | 62.77165603637695 |
Wed Nov 04 13:35:52 2020 EST |
This code is a part of Qiskit
© Copyright IBM 2017, 2020.
This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.