Skip to main content
IBM Quantum Platform

Funções de custo

Nesta lição, aprenderemos a avaliar uma função de custo :

  • Primeiro, vamos aprender sobre as primitivas d IBM Quantum
  • Defina uma função de custo C(θ)C(\vec\theta). Essa é uma função específica do problema que define a meta do problema a ser minimizada (ou maximizada) pelo otimizador
  • Definição de uma estratégia de medição com as primitivas do IBM Quantum para otimizar a relação entre velocidade e precisão

 

Um diagrama que mostra os principais componentes de uma função de custo, incluindo o uso de primitivos como estimador e amostrador.

Primitivas

Todos os sistemas físicos, sejam eles clássicos ou quânticos, podem existir em diferentes estados. Por exemplo, um carro em uma estrada pode ter uma determinada massa, posição, velocidade ou aceleração que caracterizam seu estado. Da mesma forma, os sistemas quânticos também podem ter configurações ou estados diferentes, mas eles diferem dos sistemas clássicos na forma como lidamos com as medições e a evolução do estado. Isso leva a propriedades únicas, como a superposição e o emaranhamento, que são exclusivas da mecânica quântica. Assim como podemos descrever o estado de um carro usando propriedades físicas como velocidade ou aceleração, também podemos descrever o estado de um sistema quântico usando observáveis, que são objetos matemáticos.

Na mecânica quântica, os estados são representados por vetores de coluna complexos normalizados, ou kets ( ψ|\psi\rangle ), e os observáveis são operadores lineares hermitianos ( H^=H^\hat{H}=\hat{H}^{\dagger} ) que atuam nos kets. Um vetor próprio ( λ|\lambda\rangle ) de um observável é conhecido como estado próprio. A medição de um observável para um de seus estados próprios ( λ|\lambda\rangle ) nos dará o valor próprio correspondente ( λ\lambda ) como leitura.

Se você estiver se perguntando como medir um sistema quântico e o que pode medir, o Qiskit oferece dois sites Operações básicas e fundamentais ou tipo de dados. O Qiskit tem os primitivos Sampler e Estimator para servir como blocos de construção para construir facilmente cargas de trabalho algorítmicas complexas. que podem ajudar:

  • Sampler: Dado um estado quântico ψ|\psi\rangle, essa primitiva obtém a probabilidade de cada estado possível da base computacional.
  • Estimator: Dado um observável quântico H^\hat{H} e um estado ψ|\psi\rangle, essa primitiva calcula o valor esperado de H^\hat{H}.

A primitiva Sampler

A primitiva Sampler calcula a probabilidade de obter cada estado possível k|k\rangle a partir da base computacional, dado um circuito quântico que prepara o estado ψ|\psi\rangle. Ela calcula

pk=kψ2kZ2n{0,1,,2n1},p_k = |\langle k | \psi \rangle|^2 \quad \forall k \in \mathbb{Z}_2^n \equiv \{0,1,\cdots,2^n-1\},

Onde nn é o número de qubits e kk a representação inteira de qualquer cadeia binária de saída possível {0,1}n\{0,1\}^n (ou seja, a base de inteiros 22 ).

O IBM Quantum Sampler percorre o circuito várias vezes em um dispositivo quântico, realizando medições a cada passagem e reconstruindo a distribuição de probabilidade a partir das sequências de bits recuperadas. Quanto mais execuções (ou tentativas ) forem realizadas, mais precisos serão os resultados, mas isso exige mais tempo e recursos quânticos.

Entretanto, como o número de saídas possíveis cresce exponencialmente com o número de qubits nn (ou seja, 2n2^n ), o número de disparos também precisará crescer exponencialmente para capturar uma distribuição de probabilidade densa. Portanto, o Sampler só é eficiente para distribuições de probabilidade esparsas; onde o estado de destino ψ|\psi\rangle deve ser expressável como uma combinação linear dos estados da base computacional, com o número de termos crescendo no máximo polinomialmente com o número de qubits:

ψ=kPoly(n)wkk.|\psi\rangle = \sum^{\text{Poly}(n)}_k w_k |k\rangle.

O site Sampler também pode ser configurado para recuperar probabilidades de uma subseção do circuito, representando um subconjunto do total de estados possíveis.

A primitiva Estimador

A primitiva Estimator calcula o valor da expectativa de um observável H^\hat{H} para um estado quântico ψ|\psi\rangle; onde as probabilidades do observável podem ser expressas como pλ=λψ2p_\lambda = |\langle\lambda|\psi\rangle|^2, sendo λ|\lambda\rangle os estados próprios do observável H^\hat{H}. O valor da expectativa é então definido como a média de todos os resultados possíveis λ\lambda (ou seja, os valores próprios do observável) de uma medição do estado ψ|\psi\rangle, ponderada pelas probabilidades correspondentes:

H^ψ:=λpλλ=ψH^ψ\langle\hat{H}\rangle_\psi := \sum_\lambda p_\lambda \lambda = \langle \psi | \hat{H} | \psi \rangle

No entanto, nem sempre é possível calcular o valor esperado de um observável, pois muitas vezes não conhecemos sua base de eigenfunções. O método de decomposição em bases próprias ( IBM Quantum ) Estimator utiliza um processo algébrico complexo para estimar o valor esperado em um dispositivo quântico real, decompondo o observável em uma combinação de outros observáveis cujas bases próprias já conhecemos.

Em termos mais simples, o Estimator decompõe qualquer observável que ele não sabe como medir em observáveis mais simples e mensuráveis chamados Conjunto de matrizes comumente usadas na computação quântica para representar e manipular estados quânticos, consistindo na matriz de identidade e nas três matrizes de Pauli (X, Y e Z)..

Qualquer operador pode ser expresso como uma combinação de 4n4^n operadores Pauli.

P^k:=σkn1σk0kZ4n{0,1,,4n1},\hat{P}_k := \sigma_{k_{n-1}}\otimes \cdots \otimes \sigma_{k_0} \quad \forall k \in \mathbb{Z}_4^n \equiv \{0,1,\cdots,4^n-1\}, \\

tal qual

H^=k=04n1wkP^k\hat{H} = \sum^{4^n-1}_{k=0} w_k \hat{P}_k

em que nn é o número de qubits, kkn1k0k \equiv k_{n-1} \cdots k_0 para klZ4{0,1,2,3}k_l \in \mathbb{Z}_4 \equiv \{0, 1, 2, 3\} (ou seja, a base de números inteiros 44 ) e (σ0,σ1,σ2,σ3):=(I,X,Y,Z)(\sigma_0, \sigma_1, \sigma_2, \sigma_3) := (I, X, Y, Z).

Depois de realizar essa decomposição, o site Estimator deriva um novo circuito VkψV_k|\psi\rangle para cada observável P^k\hat{P}_k (do circuito original), para efetivamente diagonalizar o observável de Pauli na base computacional e medi-lo. Podemos medir facilmente os observáveis de Pauli porque sabemos VkV_k com antecedência, o que geralmente não acontece com outros observáveis.

Para cada P^k\hat{P}_{k}, o Estimator executa o circuito correspondente em um dispositivo quântico várias vezes, mede o estado de saída na base computacional e calcula a probabilidade pkjp_{kj} de obter cada saída possível jj. Em seguida, ele procura o valor próprio λkj\lambda_{kj} de PkP_k correspondente a cada saída jj, multiplica por wkw_k e soma todos os resultados para obter o valor esperado do observável H^\hat{H} para o estado dado ψ|\psi\rangle.

H^ψ=k=04n1wkj=02n1pkjλkj,\langle\hat{H}\rangle_\psi = \sum_{k=0}^{4^n-1} w_k \sum_{j=0}^{2^n-1}p_{kj} \lambda_{kj},

Como o cálculo do valor de expectativa de 4n4^n Paulis é impraticável (ou seja, cresce exponencialmente), Estimator só pode ser eficiente quando uma grande quantidade de wkw_k é zero (ou seja, decomposição de Pauli esparsa em vez de densa ). Formalmente, dizemos que, para que esse cálculo possa ser resolvido com eficiência, o número de termos diferentes de zero deve crescer no máximo polinomialmente com o número de qubits nn : H^=kPoly(n)wkP^k.\hat{H} = \sum^{\text{Poly}(n)}_k w_k \hat{P}_k.

O leitor pode notar a suposição implícita de que a probabilidade O processo de fazer várias medições de uma ou várias coisas. também precisa ser eficiente, conforme explicado para Sampler, o que significa

H^ψ=kPoly(n)wkjPoly(n)pkjλkj.\langle\hat{H}\rangle_\psi = \sum_{k}^{\text{Poly}(n)} w_k \sum_{j}^{\text{Poly}(n)}p_{kj} \lambda_{kj}.

Exemplo guiado para calcular valores esperados

Vamos supor o estado de um único qubit +:=H0=12(0+1)|+\rangle := H|0\rangle = \frac{1}{\sqrt{2}}(|0\rangle + |1\rangle), e observável

H^=(1221)=2XZ\begin{aligned} \hat{H} & = \begin{pmatrix} -1 & 2 \\ 2 & 1 \\ \end{pmatrix}\\[1mm] & = 2X - Z \end{aligned}

com o seguinte valor de expectativa teórica H^+=+H^+=2.\langle\hat{H}\rangle_+ = \langle+|\hat{H}|+\rangle = 2.

Como não sabemos como medir esse observável, não podemos calcular seu valor de expectativa diretamente e precisamos expressá-lo novamente como H^+=2X+Z+\langle\hat{H}\rangle_+ = 2\langle X \rangle_+ - \langle Z \rangle_+ . O que pode ser demonstrado como o mesmo resultado em virtude da observação de que +X+=1\langle+|X|+\rangle = 1, e +Z+=0\langle+|Z|+\rangle = 0.

Vamos ver como calcular X+\langle X \rangle_+ e Z+\langle Z \rangle_+ diretamente. Como XX e ZZ não se combinam (ou seja, não compartilham a mesma base própria), eles não podem ser medidos simultaneamente e, portanto, precisamos dos circuitos auxiliares:

from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp

# The following code will work for any other initial single-qubit state and observable
original_circuit = QuantumCircuit(1)
original_circuit.h(0)

H = SparsePauliOp(["X", "Z"], [2, -1])

aux_circuits = []
for pauli in H.paulis:
    aux_circ = original_circuit.copy()
    aux_circ.barrier()
    if str(pauli) == "X":
        aux_circ.h(0)
    elif str(pauli) == "Y":
        aux_circ.sdg(0)
        aux_circ.h(0)
    else:
        aux_circ.id(0)
    aux_circ.measure_all()
    aux_circuits.append(aux_circ)

original_circuit.draw("mpl")

Output:

Output of the previous code cell
# Auxiliary circuit for X
aux_circuits[0].draw("mpl")

Output:

Output of the previous code cell
# Auxiliary circuit for Z
aux_circuits[1].draw("mpl")

Output:

Output of the previous code cell

Agora podemos realizar o cálculo manualmente usando Sampler e verificar os resultados em Estimator:

from qiskit.primitives import StatevectorSampler, StatevectorEstimator
from qiskit.result import QuasiDistribution
import numpy as np


## SAMPLER
shots = 10000
sampler = StatevectorSampler()
job = sampler.run(aux_circuits, shots=shots)

# Run the sampler job and step through results
expvals = []
for index, pauli in enumerate(H.paulis):
    data_pub = job.result()[index].data
    bitstrings = data_pub.meas.get_bitstrings()
    counts = data_pub.meas.get_counts()
    quasi_dist = QuasiDistribution(
        {outcome: freq / shots for outcome, freq in counts.items()}
    )

    # Use the probabilities and known eigenvalues of Pauli operators to estimate
    # the expectation value.
    val = 0

    if str(pauli) == "X":
        val += -1 * quasi_dist.get(1, 0)
        val += 1 * quasi_dist.get(0, 0)

    if str(pauli) == "Y":
        val += -1 * quasi_dist.get(1, 0)
        val += 1 * quasi_dist.get(0, 0)

    if str(pauli) == "Z":
        val += 1 * quasi_dist.get(0, 0)
        val += -1 * quasi_dist.get(1, 0)

    expvals.append(val)

# Print expectation values

print("Sampler results:")
for pauli, expval in zip(H.paulis, expvals):
    print(f"  >> Expected value of {str(pauli)}: {expval:.5f}")

total_expval = np.sum(H.coeffs * expvals).real
print(f"  >> Total expected value: {total_expval:.5f}")

# Use estimator for comparison
observables = [
    *H.paulis,
    H,
]  # Note: run for individual Paulis as well as full observable H

estimator = StatevectorEstimator()
job = estimator.run([(original_circuit, observables)])
estimator_expvals = job.result()[0].data.evs

# Print results
print("Estimator results:")
for obs, expval in zip(observables, estimator_expvals):
    if obs is not H:
        print(f"  >> Expected value of {str(obs)}: {expval:.5f}")
    else:
        print(f"  >> Total expected value: {expval:.5f}")

Output:

Sampler results:
  >> Expected value of X: 1.00000
  >> Expected value of Z: 0.00420
  >> Total expected value: 1.99580
Estimator results:
  >> Expected value of X: 1.00000
  >> Expected value of Z: 0.00000
  >> Total expected value: 2.00000

Rigor matemático (opcional)

Expressando ψ|\psi\rangle com relação à base de estados próprios de H^\hat{H}, ψ=λaλλ|\psi\rangle = \sum_\lambda a_\lambda |\lambda\rangle, segue-se:

ψH^ψ=(λaλλ)H^(λaλλ)=λλaλaλλH^λ=λλaλaλλλλ=λλaλaλλδλ,λ=λaλ2λ=λpλλ\begin{aligned} \langle \psi | \hat{H} | \psi \rangle & = \bigg(\sum_{\lambda'}a^*_{\lambda'} \langle \lambda'|\bigg) \hat{H} \bigg(\sum_{\lambda} a_\lambda | \lambda\rangle\bigg)\\[1mm] & = \sum_{\lambda}\sum_{\lambda'} a^*_{\lambda'}a_{\lambda} \langle \lambda'|\hat{H}| \lambda\rangle\\[1mm] & = \sum_{\lambda}\sum_{\lambda'} a^*_{\lambda'}a_{\lambda} \lambda \langle \lambda'| \lambda\rangle\\[1mm] & = \sum_{\lambda}\sum_{\lambda'} a^*_{\lambda'}a_{\lambda} \lambda \cdot \delta_{\lambda, \lambda'}\\[1mm] & = \sum_\lambda |a_\lambda|^2 \lambda\\[1mm] & = \sum_\lambda p_\lambda \lambda\\[1mm] \end{aligned}

Como não conhecemos os valores próprios ou os estados próprios do observável alvo H^\hat{H}, primeiro precisamos considerar sua diagonalização. Dado que H^\hat{H} é Um hermitiano é uma matriz quadrada que é igual à sua própria transposição conjugada ou um operador linear que é autoadjunto., existe uma transformação unitária VV de modo que H^=VΛV,\hat{H}=V^\dagger \Lambda V, onde Λ\Lambda é a matriz diagonal de valores próprios, portanto jΛk=0\langle j | \Lambda | k \rangle = 0 se jkj\neq k, e jΛj=λj\langle j | \Lambda | j \rangle = \lambda_j.

Isso implica que o valor esperado pode ser reescrito como:

ψH^ψ=ψVΛVψ=ψV(j=02n1jj)Λ(k=02n1kk)Vψ=j=02n1k=02n1ψVjjΛkkVψ=j=02n1ψVjjΛjjVψ=j=02n1jVψ2λj\begin{aligned} \langle\psi|\hat{H}|\psi\rangle & = \langle\psi|V^\dagger \Lambda V|\psi\rangle\\[1mm] & = \langle\psi|V^\dagger \bigg(\sum_{j=0}^{2^n-1} |j\rangle \langle j|\bigg) \Lambda \bigg(\sum_{k=0}^{2^n-1} |k\rangle \langle k|\bigg) V|\psi\rangle\\[1mm] & = \sum_{j=0}^{2^n-1} \sum_{k=0}^{2^n-1}\langle\psi|V^\dagger |j\rangle \langle j| \Lambda |k\rangle \langle k| V|\psi\rangle\\[1mm] & = \sum_{j=0}^{2^n-1}\langle\psi|V^\dagger |j\rangle \langle j| \Lambda |j\rangle \langle j| V|\psi\rangle\\[1mm] & = \sum_{j=0}^{2^n-1}|\langle j| V|\psi\rangle|^2 \lambda_j\\[1mm] \end{aligned}

Considerando que, se um sistema estiver no estado ϕ=Vψ|\phi\rangle = V |\psi\rangle, a probabilidade de medir j| j\rangle é pj=jϕ2p_j = |\langle j|\phi \rangle|^2, o valor esperado acima pode ser expresso como:

ψH^ψ=j=02n1pjλj.\langle\psi|\hat{H}|\psi\rangle = \sum_{j=0}^{2^n-1} p_j \lambda_j.

É muito importante observar que as probabilidades são obtidas do estado VψV |\psi\rangle em vez de ψ|\psi\rangle. É por isso que a matriz VV é absolutamente necessária.

Você deve estar se perguntando como obter a matriz VV e os valores próprios Λ\Lambda. Se você já tivesse os valores próprios, não haveria necessidade de usar um computador quântico, pois o objetivo dos algoritmos variacionais é encontrar esses valores próprios de H^\hat{H}.

Felizmente, há uma maneira de contornar isso: qualquer matriz 2n×2n2^n \times 2^n pode ser escrita como uma combinação linear de produtos tensoriais 4n4^n de matrizes de Pauli nn e identidades, sendo que todas elas são hermitianas e unitárias, com VV e Λ\Lambda conhecidos. É isso que o IBM Quantum Estimator faz internamente, decompondo qualquer objeto Operator em um SparsePauliOp.

Aqui estão os operadores que podem ser usados:

OperatorσVΛIσ0=(1001)V0=IΛ0=I=(1001)Xσ1=(0110)V1=H=12(1111)Λ1=σ3=(1001)Yσ2=(0ii0)V2=HS=12(1111)(100i)=12(1i1i)Λ2=σ3=(1001)Zσ3=(1001)V3=IΛ3=σ3=(1001)\begin{array}{c|c|c|c} \text{Operator} & \sigma & V & \Lambda \\[1mm] \hline I & \sigma_0 = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix} & V_0 = I & \Lambda_0 = I = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix} \\[4mm] X & \sigma_1 = \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix} & V_1 = H =\frac{1}{\sqrt{2}} \begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix} & \Lambda_1 = \sigma_3 = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix} \\[4mm] Y & \sigma_2 = \begin{pmatrix} 0 & -i \\ i & 0 \end{pmatrix} & V_2 = HS^\dagger =\frac{1}{\sqrt{2}} \begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix}\cdot \begin{pmatrix} 1 & 0 \\ 0 & -i \end{pmatrix} = \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & -i \\ 1 & i \end{pmatrix}\quad & \Lambda_2 = \sigma_3 = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix} \\[4mm] Z & \sigma_3 = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix} & V_3 = I & \Lambda_3 = \sigma_3 = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix} \end{array}

Portanto, vamos reescrever H^\hat{H} com relação à Paulis e às identidades:

H^=kn1=03...k0=03wkn1...k0σkn1...σk0=k=04n1wkP^k,\hat{H} = \sum_{k_{n-1}=0}^3... \sum_{k_0=0}^3 w_{k_{n-1}...k_0} \sigma_{k_{n-1}}\otimes ... \otimes \sigma_{k_0} = \sum_{k=0}^{4^n-1} w_k \hat{P}_k,

sendo k=l=0n14lklkn1...k0k = \sum_{l=0}^{n-1} 4^l k_l \equiv k_{n-1}...k_0 para kn1,...,k0{0,1,2,3}k_{n-1},...,k_0\in \{0,1,2,3\} (ou seja, a base 44 ) e P^k:=σkn1...σk0\hat{P}_{k} := \sigma_{k_{n-1}}\otimes ... \otimes \sigma_{k_0} :

ψH^ψ=k=04n1wkj=02n1jVkψ2jΛkj=k=04n1wkj=02n1pkjλkj,\begin{aligned} \langle\psi|\hat{H}|\psi\rangle & = \sum_{k=0}^{4^n-1} w_k \sum_{j=0}^{2^n-1}|\langle j| V_k|\psi\rangle|^2 \langle j| \Lambda_k |j\rangle \\[1mm] & = \sum_{k=0}^{4^n-1} w_k \sum_{j=0}^{2^n-1}p_{kj} \lambda_{kj}, \\[1mm] \end{aligned}

onde Vk:=Vkn1...Vk0V_k := V_{k_{n-1}}\otimes ... \otimes V_{k_0} e Λk:=Λkn1...Λk0\Lambda_k := \Lambda_{k_{n-1}}\otimes ... \otimes \Lambda_{k_0}, de modo que: Pk^=VkΛkVk.\hat{P_k}=V_k^\dagger \Lambda_k V_k.


Funções de custo

Em geral, as funções de custo são usadas para descrever a meta de um problema e o desempenho de um estado de teste em relação a essa meta. Essa definição pode ser aplicada a vários exemplos em química, aprendizado de máquina, finanças, otimização e assim por diante.

Vamos considerar um exemplo simples de como encontrar o estado fundamental de um sistema. Nosso objetivo é minimizar o valor da expectativa do observável que representa a energia (Hamiltoniano H^\hat{\mathcal{H}} ):

minθψ(θ)H^ψ(θ)\min_{\vec\theta} \langle\psi(\vec\theta)|\hat{\mathcal{H}}|\psi(\vec\theta)\rangle

Podemos usar o site Estimator para avaliar o valor da expectativa e passar esse valor para um otimizador para minimizá-lo. Se a otimização for bem-sucedida, ela retornará um conjunto de valores de parâmetros ideais θ\vec\theta^*, a partir do qual poderemos construir o estado da solução proposta ψ(θ)|\psi(\vec\theta^*)\rangle e calcular o valor da expectativa observada como C(θ)C(\vec\theta^*).

Observe como só conseguiremos minimizar a função de custo para o conjunto limitado de estados que estamos considerando. Isso nos leva a duas possibilidades distintas:

  • Nosso ansatz não define o estado da solução em todo o espaço de pesquisa : Se esse for o caso, nosso otimizador nunca encontrará a solução, e precisamos experimentar outras ansatzes que possam representar nosso espaço de pesquisa com mais precisão.
  • Nosso otimizador não consegue encontrar essa solução válida : A otimização pode ser definida globalmente e definida localmente. Exploraremos o que isso significa na seção posterior.

Em suma, estaremos executando um loop de otimização clássica, mas confiando a avaliação da função de custo a um computador quântico. Nessa perspectiva, poderíamos pensar na otimização como um esforço puramente clássico em que chamamos alguns Um dispositivo hipotético ou componente de software que executa uma função específica, mas cujo funcionamento interno é desconhecido. Um usuário está ciente apenas das entradas e saídas da caixa preta e não tem conhecimento de como a caixa preta processa as entradas para produzir as saídas. cada vez que o otimizador precisa avaliar a função de custo.

def cost_func_vqe(params, circuit, hamiltonian, estimator):
    """Return estimate of energy from estimator

    Parameters:
        params (ndarray): Array of ansatz parameters
        ansatz (QuantumCircuit): Parameterized ansatz circuit
        hamiltonian (SparsePauliOp): Operator representation of Hamiltonian
        estimator (Estimator): Estimator primitive instance

    Returns:
        float: Energy estimate
    """
    pub = (circuit, hamiltonian, params)
    cost = estimator.run([pub]).result()[0].data.evs
    return cost
from qiskit.circuit.library import TwoLocal

observable = SparsePauliOp.from_list([("XX", 1), ("YY", -3)])

reference_circuit = QuantumCircuit(2)
reference_circuit.x(0)

variational_form = TwoLocal(
    2,
    rotation_blocks=["rz", "ry"],
    entanglement_blocks="cx",
    entanglement="linear",
    reps=1,
)
ansatz = reference_circuit.compose(variational_form)

theta_list = (2 * np.pi * np.random.rand(1, 8)).tolist()
ansatz.decompose().draw("mpl")

Output:

Output of the previous code cell

Primeiro, faremos isso usando um simulador: o StatevectorEstimator. Isso geralmente é aconselhável para a depuração, mas seguiremos imediatamente a execução da depuração com um cálculo no hardware quântico real. Cada vez mais, os problemas de interesse não são mais simuláveis de forma clássica sem instalações de supercomputação de última geração.

estimator = StatevectorEstimator()
cost = cost_func_vqe(theta_list, ansatz, observable, estimator)
print(cost)

Output:

[-0.58744589]

Agora vamos prosseguir com a execução em um computador quântico real. Observe as mudanças na sintaxe. As etapas relacionadas ao pass_manager serão abordadas com mais detalhes no próximo exemplo. Uma etapa de particular importância nos algoritmos variacionais é o uso de uma sessão do Serviço de Computação do IBM Quantum. Iniciar uma sessão permite executar várias iterações de um algoritmo variacional sem precisar aguardar em uma nova fila cada vez que os parâmetros são atualizados. Isso é importante se os tempos de espera forem longos e/ou forem necessárias muitas iterações. Observe que os usuários do Open Plan não podem utilizar sessões do Quantum Compute. Caso você não tenha acesso às sessões, pode reduzir o número de iterações enviadas de cada vez e salvar os parâmetros mais recentes para uso em execuções futuras. Se você enviar muitas iterações ou se os tempos de espera na fila forem muito longos, poderá receber o código de erro 1217, que se refere a longos atrasos entre os envios de tarefas.

# Estimated usage: < 1 min. Benchmarked at 7 seconds on an Eagle processor
# Load necessary packages:

from qiskit_ibm_runtime import (
    QiskitRuntimeService,
    Session,
    EstimatorOptions,
    EstimatorV2 as Estimator,
)
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

# Select the least busy backend:

service = QiskitRuntimeService()
backend = service.least_busy(
    operational=True, min_num_qubits=ansatz.num_qubits, simulator=False
)
# Or get a specific backend:
# backend = service.backend("ibm_brisbane")

# Use a pass manager to transpile the circuit and observable for the specific backend being used:

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_ansatz = pm.run(ansatz)
isa_observable = observable.apply_layout(layout=isa_ansatz.layout)


# Set estimator options
estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)

# Open a Quantum Compute session:

with Session(backend=backend) as session:
    estimator = Estimator(mode=session, options=estimator_options)
    cost = cost_func_vqe(theta_list, isa_ansatz, isa_observable, estimator)

session.close()
print(cost)

Observe que os valores obtidos com os dois cálculos acima são muito semelhantes. As técnicas para melhorar os resultados serão discutidas mais adiante.

Exemplo de mapeamento para sistemas não físicos

O problema do corte máximo (max-cut) é um problema de otimização combinatória que consiste em dividir os vértices de um grafo em dois conjuntos disjuntos, de modo a maximizar o número de arestas entre os dois conjuntos. De forma mais formal, dado um grafo não direcionado G=(V,E)G=(V,E), em que VV é o conjunto de vértices e EE é o conjunto de arestas, o problema do corte máximo consiste em dividir os vértices em dois subconjuntos disjuntos, SS e TT, de modo a maximizar o número de arestas cuja uma extremidade pertença a SS e a outra a TT.

Podemos aplicar o método max-cut para resolver diversos problemas, incluindo: agrupamento, projeto de redes, transições de fase e assim por diante. Vamos começar criando um gráfico do problema:

import rustworkx as rx
from rustworkx.visualization import mpl_draw

n = 4
G = rx.PyGraph()
G.add_nodes_from(range(n))
# The edge syntax is (start, end, weight)
edges = [(0, 1, 1.0), (0, 2, 1.0), (0, 3, 1.0), (1, 2, 1.0), (2, 3, 1.0)]
G.add_edges_from(edges)

mpl_draw(
    G, pos=rx.shell_layout(G), with_labels=True, edge_labels=str, node_color="#1192E8"
)

Output:

Output of the previous code cell

Esse problema pode ser expresso como um problema de otimização binária. Para cada nó 0i<n0 \leq i < n, em que nn é o número de nós do gráfico (nesse caso, n=4n=4 ), consideraremos a variável binária xix_i. Essa variável terá o valor 11 se o nó ii for um dos grupos que rotularemos como 11 e 00 se estiver no outro grupo, que rotularemos como 00. Também denotaremos como wijw_{ij} (elemento (i,j)(i,j) da matriz de adjacência ww ) o peso da borda que vai do nó ii ao nó jj. Como o gráfico não é direcionado, wij=wjiw_{ij}=w_{ji}. Então, podemos formular nosso problema como a maximização da seguinte função de custo:

C(x)=i,j=0nwijxi(1xj)=i,j=0nwijxii,j=0nwijxixj=i,j=0nwijxii=0nj=0i2wijxixj\begin{aligned} C(\vec{x}) & =\sum_{i,j=0}^n w_{ij} x_i(1-x_j)\\[1mm] & = \sum_{i,j=0}^n w_{ij} x_i - \sum_{i,j=0}^n w_{ij} x_ix_j\\[1mm] & = \sum_{i,j=0}^n w_{ij} x_i - \sum_{i=0}^n \sum_{j=0}^i 2w_{ij} x_ix_j \end{aligned}

Para resolver esse problema com um computador quântico, vamos expressar a função de custo como o valor esperado de um observável. Entretanto, os observáveis que o Qiskit admite nativamente consistem em operadores Pauli, que têm autovalores 11 e 1-1 em vez de 00 e 11. É por isso que faremos a seguinte mudança de variável:

Onde x=(x0,x1,,xn1)\vec{x}=(x_0,x_1,\cdots ,x_{n-1}). Podemos usar a matriz de adjacência ww para acessar confortavelmente os pesos de todas as bordas. Isso será usado para obter nossa função de custo:

zi=12xixi=1zi2z_i = 1-2x_i \rightarrow x_i = \frac{1-z_i}{2}

Isso implica que:

xi=0zi=1xi=1zi=1.\begin{array}{lcl} x_i=0 & \rightarrow & z_i=1 \\ x_i=1 & \rightarrow & z_i=-1.\end{array}

Portanto, a nova função de custo que queremos maximizar é:

C(z)=i,j=0nwij(1zi2)(11zj2)=i,j=0nwij4i,j=0nwij4zizj=i=0nj=0iwij2i=0nj=0iwij2zizj\begin{aligned} C(\vec{z}) & = \sum_{i,j=0}^n w_{ij} \bigg(\frac{1-z_i}{2}\bigg)\bigg(1-\frac{1-z_j}{2}\bigg)\\[1mm] & = \sum_{i,j=0}^n \frac{w_{ij}}{4} - \sum_{i,j=0}^n \frac{w_{ij}}{4} z_iz_j\\[1mm] & = \sum_{i=0}^n \sum_{j=0}^i \frac{w_{ij}}{2} - \sum_{i=0}^n \sum_{j=0}^i \frac{w_{ij}}{2} z_iz_j \end{aligned}

Além disso, a tendência natural de um computador quântico é encontrar mínimos (geralmente a energia mais baixa) em vez de máximos, portanto, em vez de maximizar C(z)C(\vec{z}), vamos minimizar:

C(z)=i=0nj=0iwij2zizji=0nj=0iwij2-C(\vec{z}) = \sum_{i=0}^n \sum_{j=0}^i \frac{w_{ij}}{2} z_iz_j - \sum_{i=0}^n \sum_{j=0}^i \frac{w_{ij}}{2}

Agora que temos uma função de custo a ser minimizada, cujas variáveis podem ter os valores 1-1 e 11, podemos fazer a seguinte analogia com o Pauli ZZ :

ziZi=In1...Zi...I0z_i \equiv Z_i = \overbrace{I}^{n-1}\otimes ... \otimes \overbrace{Z}^{i} \otimes ... \otimes \overbrace{I}^{0}

Em outras palavras, a variável ziz_i será equivalente a uma porta ZZ atuando no qubit ii. Além disso:

Zixn1x0=zixn1x0xn1x0Zixn1x0=ziZ_i|x_{n-1}\cdots x_0\rangle = z_i|x_{n-1}\cdots x_0\rangle \rightarrow \langle x_{n-1}\cdots x_0 |Z_i|x_{n-1}\cdots x_0\rangle = z_i

Então, o observável que vamos considerar é:

H^=i=0nj=0iwij2ZiZj\hat{H} = \sum_{i=0}^n \sum_{j=0}^i \frac{w_{ij}}{2} Z_iZ_j

ao qual teremos que adicionar o termo independente posteriormente:

offset=i=0nj=0iwij2\texttt{offset} = - \sum_{i=0}^n \sum_{j=0}^i \frac{w_{ij}}{2}

O operador é uma combinação linear de termos com operadores Z em nós conectados por uma borda (lembre-se de que o 0º qubit é o mais à direita): IIZZ+IZIZ+IZZI+ZIIZ+ZZIIIIZZ + IZIZ + IZZI + ZIIZ + ZZII. Depois que o operador é construído, o ansatz para o algoritmo QAOA pode ser facilmente construído usando o circuito QAOAAnsatz da biblioteca de circuitos Qiskit.

from qiskit.circuit.library import QAOAAnsatz
from qiskit.quantum_info import SparsePauliOp

hamiltonian = SparsePauliOp.from_list(
    [("IIZZ", 1), ("IZIZ", 1), ("IZZI", 1), ("ZIIZ", 1), ("ZZII", 1)]
)


ansatz = QAOAAnsatz(hamiltonian, reps=2)
# Draw
ansatz.decompose(reps=3).draw("mpl")

Output:

Output of the previous code cell
# Sum the weights, and divide by 2

offset = -sum(edge[2] for edge in edges) / 2
print(f"""Offset: {offset}""")

Output:

Offset: -2.5

Como o Estimador de IBM Quantum a aceita diretamente um hamiltoniano e um ansatz parametrizado, retornando a energia necessária, a função de custo para uma instância de QAOA é bastante simples:

def cost_func(params, ansatz, hamiltonian, estimator):
    """Return estimate of energy from estimator

    Parameters:
        params (ndarray): Array of ansatz parameters
        ansatz (QuantumCircuit): Parameterized ansatz circuit
        hamiltonian (SparsePauliOp): Operator representation of Hamiltonian
        estimator (Estimator): Estimator primitive instance

    Returns:
        float: Energy estimate
    """
    pub = (ansatz, hamiltonian, params)
    cost = estimator.run([pub]).result()[0].data.evs
    #    cost = estimator.run(ansatz, hamiltonian, parameter_values=params).result().values[0]
    return cost
import numpy as np

x0 = 2 * np.pi * np.random.rand(ansatz.num_parameters)

estimator = StatevectorEstimator()
cost = cost_func_vqe(x0, ansatz, hamiltonian, estimator)
print(cost)

Output:

1.473098768180865
# Estimated usage: < 1 min, benchmarked at 6 seconds on ibm_osaka, 5-23-24
# Load some necessary packages:

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Session, EstimatorV2 as Estimator

# Select the least busy backend:

backend = service.least_busy(
    operational=True, min_num_qubits=ansatz.num_qubits, simulator=False
)

# Or get a specific backend:
# backend = service.backend("ibm_brisbane")

# Use a pass manager to transpile the circuit and observable for the specific backend being used:

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_ansatz = pm.run(ansatz)
isa_hamiltonian = hamiltonian.apply_layout(layout=isa_ansatz.layout)

# Set estimator options
estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)

# Open a Quantum Compute session:

with Session(backend=backend) as session:
    estimator = Estimator(mode=session, options=estimator_options)
    cost = cost_func_vqe(x0, isa_ansatz, isa_hamiltonian, estimator)

# Close session after done
session.close()
print(cost)

Output:

1.1120776913677988

Revisitaremos esse exemplo em Aplicativos para explorar como aproveitar um otimizador para iterar pelo espaço de pesquisa. De modo geral, isso inclui:

  • Aproveitamento de um otimizador para encontrar os parâmetros ideais
  • Vinculação de parâmetros ideais ao ansatz para encontrar os valores próprios
  • Traduzindo os valores próprios para a definição do nosso problema

Estratégia de medição: velocidade versus precisão

Conforme mencionado, estamos usando um computador quântico ruidoso como um oráculo de caixa preta, onde o ruído pode tornar os valores recuperados não determinísticos, levando a flutuações aleatórias que, por sua vez, prejudicarão - ou até mesmo impedirão completamente - a convergência de determinados otimizadores para uma solução proposta. Esse é um problema geral que precisamos resolver à medida que exploramos cada vez mais a utilidade quântica e progredimos em direção à vantagem quântica:

Um gráfico que mostra como o custo da simulação varia de acordo com a complexidade do circuito. Usando um computador clássico, ele cresce exponencialmente. Com a atenuação de erros quânticos, deve haver um cruzamento no qual isso se torna vantajoso. A correção de erros quânticos permite o crescimento linear do custo da simulação e certamente trará vantagens.

Podemos utilizar as opções de supressão e mitigação de erros de uma primitiv IBM Quantum e para lidar com o ruído e maximizar a utilidade dos computadores quânticos atuais.

Supressão de erros

A supressão de erros refere-se às técnicas utilizadas para otimizar e transformar um circuito durante a compilação, com o objetivo de minimizar os erros. Essa é uma técnica básica de tratamento de erros que geralmente implica em algum tipo de pré-processamento clássico Custos extras introduzidos por novas técnicas, em relação a uma implementação básica. para o tempo de execução total. A sobrecarga envolve a transpilagem de circuitos para execução em hardware quântico por meio de:

  • Expressar o circuito usando as portas nativas disponíveis em um sistema quântico
  • Mapeamento dos qubits virtuais para qubits físicos
  • Adição de SWAPs com base nos requisitos de conectividade
  • Otimização das portas 1Q e 2Q
  • Adição de desacoplamento dinâmico a qubits ociosos para evitar os efeitos da decoerência.

As primitivas permitem o uso de técnicas de supressão de erros, definindo a opção optimization_level e selecionando opções avançadas de transpilação. Em um curso posterior, vamos nos aprofundar em diferentes métodos de construção de circuitos para melhorar os resultados, mas, na maioria dos casos, recomendamos definir optimization_level=3.

Visualizaremos o valor do aumento da otimização no processo de transpilação analisando um exemplo de circuito com um comportamento ideal simples.

from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.quantum_info import SparsePauliOp

theta = Parameter("theta")

qc = QuantumCircuit(2)
qc.x(1)
qc.h(0)
qc.cp(theta, 0, 1)
qc.h(0)
observables = SparsePauliOp.from_list([("ZZ", 1)])

qc.draw("mpl")

Output:

Output of the previous code cell

O circuito acima pode produzir valores de expectativa senoidais do observável dado, desde que insiramos fases que abranjam um intervalo apropriado, como [0,2π][0,2\pi].

## Setup phases
import numpy as np

phases = np.linspace(0, 2 * np.pi, 50)

# phases need to be expressed as a list of lists in order to work
individual_phases = [[phase] for phase in phases]

Podemos usar um simulador para mostrar a utilidade de uma transpilação otimizada. Voltaremos a usar hardware real para demonstrar a utilidade da atenuação de erros. Usaremos QiskitRuntimeService para obter um backend real (neste caso, ibm_brisbane) e usaremos AerSimulator para simular esse backend, incluindo seu comportamento de ruído.

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_aer import AerSimulator

# get a real backend from the runtime service
service = QiskitRuntimeService()
backend = service.backend("ibm_brisbane")

# generate a simulator that mimics the real quantum system with the latest calibration results
backend_sim = AerSimulator.from_backend(backend)

Agora podemos usar um gerenciador de passagens para transpilar o circuito para a “arquitetura de conjunto de instruções” (ISA) do backend. Este é um novo requisito no Serviço de Computação do IBM Quantum : todos os circuitos enviados a um backend devem estar em conformidade com as restrições do alvo do backend, o que significa que devem ser escritos de acordo com a ISA do backend — ou seja, o conjunto de instruções que o dispositivo é capaz de compreender e executar. Essas restrições-alvo são definidas por fatores como os portões de base nativos do dispositivo, sua conectividade de qubits e — quando relevante — suas especificações de temporização de pulsos e outras instruções.

Observe que, no presente caso, faremos isso duas vezes: uma vez com optimization_level = 0 e outra vez com ele definido como 3. Sempre usaremos a primitiva Estimator para estimar os valores de expectativa do observável em diferentes valores de fase.

# Import estimator and specify that we are using the simulated backend:

from qiskit_ibm_runtime import EstimatorV2 as Estimator

estimator = Estimator(mode=backend_sim)

circuit = qc
# Use a pass manager to transpile the circuit and observable for the backend being simulated.
# Start with no optimization:

from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

pm = generate_preset_pass_manager(backend=backend_sim, optimization_level=0)
isa_circuit = pm.run(circuit)
isa_observables = observables.apply_layout(layout=isa_circuit.layout)

noisy_exp_values = []
pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs
noisy_exp_values = cost[0]

# Repeat above steps, but now with optimization = 3:

exp_values_with_opt_es = []
pm = generate_preset_pass_manager(backend=backend_sim, optimization_level=3)
isa_circuit = pm.run(circuit)
isa_observables = observables.apply_layout(layout=isa_circuit.layout)

pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs
exp_values_with_opt_es = cost[0]

Por fim, podemos plotar os resultados e ver que a precisão do cálculo foi razoavelmente boa mesmo sem otimização, mas definitivamente melhorou com o aumento da otimização para o nível 3. Observe que, em circuitos mais profundos e complicados, a diferença entre os níveis de otimização de 0 e 3 provavelmente será mais significativa. Esse é um circuito muito simples usado como um modelo de brinquedo.

import matplotlib.pyplot as plt

plt.plot(phases, noisy_exp_values, "o", label="opt=0")
plt.plot(phases, exp_values_with_opt_es, "o", label="opt=3")
plt.plot(phases, 2 * np.sin(phases / 2) ** 2 - 1, label="ideal")
plt.ylabel("Expectation")
plt.legend()
plt.show()

Output:

Output of the previous code cell

Mitigação de erros

A mitigação de erros refere-se a técnicas que permitem aos usuários reduzir os erros de circuito por meio da modelagem do ruído do dispositivo no momento da execução. Normalmente, isso resulta em uma sobrecarga de pré-processamento quântico relacionada ao treinamento do modelo e em uma sobrecarga de pós-processamento clássico para mitigar erros nos resultados brutos por meio do uso do modelo gerado.

A opção resilience_level da primitiva IBM Quantum especifica o nível de resiliência a ser implementado contra erros. Níveis mais altos geram resultados mais precisos, mas em contrapartida aumentam o tempo de processamento devido à sobrecarga da amostragem quântica. Os níveis de resiliência podem ser usados para definir o equilíbrio entre custo e precisão ao aplicar a mitigação de erros à sua consulta primitiva.

Ao implementar qualquer técnica de mitigação de erros, esperamos que o Um desvio sistemático nas quantidades medidas, geralmente causado por erros. em nossos resultados seja reduzido em relação ao viés anterior, não mitigado. Em alguns casos, o viés pode até desaparecer. No entanto, isso tem um custo. À medida que reduzirmos o viés em nossas quantidades estimadas, a variabilidade estatística aumentará (ou seja, a variância), o que pode ser considerado aumentando ainda mais o número de disparos por circuito em nosso processo de amostragem. Isso introduzirá uma sobrecarga além da necessária para reduzir o viés, portanto, não é feito por padrão. Podemos optar facilmente por esse comportamento ajustando o número de disparos por circuito em options.executions.shots, conforme mostrado no exemplo abaixo.

Um diagrama que mostra distribuições mais amplas ou mais estreitas, como no caso do viés /var iance tradeoff.

Neste curso, exploraremos esses modelos de mitigação de erros em um nível geral para ilustrar a mitigação de erros que as primitivas d IBM Quantum m podem realizar, sem a necessidade de entrar em detalhes completos de implementação.

Extinção de erro de leitura giratória (T-REx)

A extinção de erro de leitura girada (T-REx) usa uma técnica conhecida como giro de Pauli para reduzir o ruído introduzido durante o processo de medição quântica. Essa técnica não pressupõe nenhuma forma específica de ruído, o que a torna muito geral e eficaz.

Fluxo de trabalho geral:

  1. Adquirir dados para o estado zero com inversão aleatória de bits (Pauli X antes da medição)
  2. Adquirir dados para o estado desejado (ruidoso) com inversão aleatória de bits (Pauli X antes da medição)
  3. Calcule a função especial para cada conjunto de dados e divida.

 

Um diagrama que mostra os circuitos de medição e calibração do T-REX.

Podemos definir isso com options.resilience_level = 1, demonstrado no exemplo abaixo.

Extrapolacão de ruído zero

A extrapolação de ruído zero (ZNE) funciona primeiro amplificando o ruído no circuito que está preparando o estado quântico desejado, obtendo medições para vários níveis diferentes de ruído e usando essas medições para inferir o resultado sem ruído.

Fluxo de trabalho geral:

  1. Amplificar o ruído do circuito para vários fatores de ruído
  2. Executar todos os circuitos amplificados por ruído
  3. Extrapolar de volta para o limite de ruído zero

 

Um diagrama que mostra as etapas da ZNE. O ruído é amplificado artificialmente por diferentes fatores. Em seguida, os valores são extrapolados para o que deveriam ser com ruído zero.

Podemos definir isso com options.resilience_level = 2. Podemos otimizar ainda mais isso explorando uma variedade de noise_factors, noise_amplifiers, e extrapolators, mas isso está fora do escopo deste curso. Recomendamos que você experimente essas opções conforme descrito aqui.

Cada método vem com sua própria sobrecarga associada: uma compensação entre o número de cálculos quânticos necessários (tempo) e a precisão de nossos resultados:

MethodsR=1, T-RExR=2, ZNEAssumptionsNoneAbility to scale noiseQubit overhead11Sampling overhead2Nnoise-factorsBias0O(λNnoise-factors)\begin{array}{c|c|c|c} \text{Methods} & R=1 \text{, T-REx} & R=2 \text{, ZNE} \\[1mm] \hline \text{Assumptions} & \text{None} & \text{Ability to scale noise} \\[1mm] \text{Qubit overhead} & 1 & 1 \\[1mm] \text{Sampling overhead} & 2 & N_{\text{noise-factors}} \\[1mm] \text{Bias} & 0 & \mathcal{O}(\lambda^{N_{\text{noise-factors}}}) \\[1mm] \end{array}

Utilização das opções do Quantum Compute para mitigação e supressão

Veja a seguir como calcular um valor esperado ao utilizar a mitigação e a supressão de erros no Compute Service d IBM Quantum. Podemos utilizar exatamente o mesmo circuito e a mesma variável observável de antes, mas, desta vez, mantendo o nível de otimização fixo no nível 2 e ajustando a resiliência ou as técnicas de mitigação de erros que estão sendo utilizadas. Esse processo de mitigação de erros ocorre várias vezes ao longo de um ciclo de otimização.

Executamos essa parte em hardware real, pois a atenuação de erros não está disponível em simuladores.

# Estimated usage: 8 minutes, benchmarked on an Eagle processor, 5-23-24

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import (
    Session,
    EstimatorOptions,
    EstimatorV2 as Estimator,
)

# We select the least busy backend

# Select the least busy backend
# backend = service.least_busy(
#    operational=True, min_num_qubits=ansatz.num_qubits, simulator=False
# )

# Or use a specific backend
backend = service.backend("ibm_brisbane")

# Initialize some variables to save the results from different runs:

exp_values_with_em0_es = []
exp_values_with_em1_es = []
exp_values_with_em2_es = []

# Use a pass manager to optimize the circuit and observables for the backend chosen:

pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
isa_circuit = pm.run(circuit)
isa_observables = observables.apply_layout(layout=isa_circuit.layout)

# Open a session and run with no error mitigation:

estimator_options = EstimatorOptions(resilience_level=0, default_shots=10_000)

with Session(backend=backend) as session:
    estimator = Estimator(mode=session, options=estimator_options)

    pub = (isa_circuit, isa_observables, [individual_phases])
    cost = estimator.run([pub]).result()[0].data.evs

session.close()

exp_values_with_em0_es = cost[0]

# Open a session and run with resilience = 1:

estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)

with Session(backend=backend) as session:
    estimator = Estimator(mode=session, options=estimator_options)

    pub = (isa_circuit, isa_observables, [individual_phases])
    cost = estimator.run([pub]).result()[0].data.evs

session.close()

exp_values_with_em1_es = cost[0]

# Open a session and run with resilience = 2:

estimator_options = EstimatorOptions(resilience_level=2, default_shots=10_000)

with Session(backend=backend) as session:
    estimator = Estimator(mode=session, options=estimator_options)

    pub = (isa_circuit, isa_observables, [individual_phases])
    cost = estimator.run([pub]).result()[0].data.evs

session.close()

exp_values_with_em2_es = cost[0]

Como antes, podemos plotar os valores de expectativa resultantes como uma função do ângulo de fase para os três níveis de atenuação de erro usados. Com muita dificuldade, é possível ver que a atenuação de erros melhora um pouco os resultados. Novamente, esse efeito é muito mais pronunciado em circuitos mais profundos e complicados.

import matplotlib.pyplot as plt

plt.plot(phases, exp_values_with_em0_es, "o", label="unmitigated")
plt.plot(phases, exp_values_with_em1_es, "o", label="resil = 1")
plt.plot(phases, exp_values_with_em2_es, "o", label="resil = 2")
plt.plot(phases, 2 * np.sin(phases / 2) ** 2 - 1, label="ideal")
plt.ylabel("Expectation")
plt.legend()
plt.show()

Output:

Output of the previous code cell

Resumo

Com esta lição, você aprendeu a criar uma função de custo:

  • Criar uma função de custo
  • Como aproveitar os recursos básicos d IBM Quantum s para mitigar e suprimir o ruído
  • Como definir uma estratégia de medição para otimizar a velocidade em relação à precisão

Esta é a nossa carga de trabalho variacional de alto nível:

Um diagrama que mostra o circuito quântico com unidades preparando o estado de referência e o estado variacional, seguido de medições. Eles são usados para avaliar a função de custo.

Nossa função de custo é executada durante cada iteração do loop de otimização. A próxima lição explorará como o otimizador clássico usa nossa avaliação da função de custo para selecionar novos parâmetros.

import qiskit
import qiskit_ibm_runtime

print(qiskit.version.get_version_info())
print(qiskit_ibm_runtime.version.get_version_info())

Output:

1.1.0
0.23.0
Esta página foi útil?
Relate um bug, erro de digitação ou solicite conteúdo no GitHub.