Portuguese
Idiomas
English
Bengali
French
German
Japanese
Korean
Portuguese
Spanish
Tamil

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 StateFns 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.

StateFns 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 DictStateFns 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: 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

  • 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})
    ])
  ])
])

ListOps: SummedOp, ComposedOp, TensoredOp

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

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.

Cada ListOp tem quatro propriedades:

  • oplist - A lista de OperatorBases, 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 do oplist. Para simplificar a transmissão, essa função é definida em arrays NumPy.

  • coeff - Um coeficiente que multiplica o primitivo. Note que coeff pode ser int, float, complex ou um objeto Parameter livre (do qiskit.circuit no Terra) para ser vinculado posteriormente usando my_op.bind_parameters.

  • abelian - Indica se os Operadores em oplist são conhecidos por comutarem mutuamente (geralmente definidos após serem convertidos pelo conversor AbelianGrouper).

Observe que ListOp suporta sobrecargas de sequência típicas, então você pode 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'), 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 PauliOps.

[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 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(-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

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(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 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 \(\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 CircuitStateFns com o CircuitSampler

O CircuitSampler percorre um Operador e converte qualquer CircuitStateFns 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 SoftwareVersion
qiskit-terra0.19.0.dev0+fe3eb3f
qiskit-aer0.10.0.dev0+b78f265
qiskit-ignis0.7.0.dev0+0eb9dcc
qiskit-ibmq-provider0.17.0.dev0+15d2dfe
System information
Python version3.9.5
Python compilerClang 10.0.0
Python builddefault, May 18 2021 12:31:01
OSDarwin
CPUs4
Memory (Gb)32.0
Fri Oct 29 16:10:45 2021 BST

This code is a part of Qiskit

© Copyright IBM 2017, 2021.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.