Loading [MathJax]/jax/output/HTML-CSS/jax.js
Portuguese, Brazilian
Idiomas
English
Japanese
German
Korean
Portuguese, Brazilian
French
Shortcuts

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 e |1. Eles são representados via DictStateFn.

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

Plus e Minus, representando estados (|0+|1)/2 e (|0|1)/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 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 StateFns 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.

StateFns carregam um coeficiente. Isso nos permite multiplicar estados por uma grandeza escalar e então construir somas.

Aqui, construímos (2+3i)|0.

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

Aqui, vemos que adicionando dois DictStateFns retorna um objeto do mesmo tipo. Construímos |0+|1.

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

Observe que você deve normalizar os estados manualmente. Por exemplo, para construir (|0+|1)/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 |++|.

[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 1|1.

[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á |1=(0|1|)/2|1.

[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|+.

[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+|00000) e |103.

[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: PrimitiveOps

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=(0110). E o elemento da matriz {X}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=|0.

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

Uma representação mais simples, a representação ` ` DictStateFn ` ` ` de |0, é 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})
    ])
  ])
])

ListOps: 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 OperatorBases 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 CircuitOps. 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 OperatorBases 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 OperatorBases 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 OperatorBases 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 0|Z|0, 1|Z|1, e +|Z|+, onde |+=(|0+|1)/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 eiH. 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, eiHt, 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 PauliOps.

[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 |Φ+ através de CX(HI)|00.

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

Aqui está a expressão HeiHt|Φ+.

[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 eiHt usando portões two-qubit. Conseguimos isso com o método convert do PauliTrotterEvolution, que analisa expressões aplicando trotterização para todos EvolvedOps 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

Expectations são conversores que permitem o cálculo dos valores probabilísticos esperados de observáveis. Eles percorrem uma árvore do Operador, substituindo OperatorStateFns (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 CircuitStateFns para VectorStateFns 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 Φ+|eiHtHeiHt|Φ+ 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 CircuitStateFns 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 SoftwareVersion
QiskitNone
Terra0.17.0.dev0+8c40b02
Aer0.5.2
Ignis0.4.0.dev0+15b7177
Aqua0.9.0.dev0+2734384
IBM Q Provider0.7.2
System information
Python3.8.3 (default, May 17 2020, 18:15:42) [GCC 10.1.0]
OSLinux
CPUs12
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.