Pular para o conteúdo principal

Implementação com Qiskit

Nesta seção, vamos explorar algumas implementações em Qiskit dos conceitos apresentados nesta aula. Se você quiser executar essas implementações por conta própria — o que é altamente recomendado —, consulte a página Instalar o Qiskit na Documentação do IBM Quantum para obter detalhes sobre como configurar o Qiskit.

É importante entender que o Qiskit está em desenvolvimento contínuo e tem como foco principal maximizar o desempenho dos computadores quânticos que opera, os quais também continuam evoluindo. Por isso, o Qiskit está sujeito a mudanças que podem ocasionalmente tornar código obsoleto (deprecação). Tendo isso em mente, sempre executaremos os seguintes comandos antes de apresentar exemplos de código Qiskit neste curso, para que fique claro qual versão do Qiskit foi utilizada. A partir do Qiskit v1.0, esta é uma forma simples de verificar qual versão do Qiskit está instalada atualmente.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
from qiskit import __version__

print(__version__)
2.1.1

Se você estiver executando isso em um ambiente Python baseado em nuvem, pode ser necessário instalar alguns dos seguintes pacotes:

#!pip install qiskit
#!pip install jupyter
#!pip install sympy
#!pip install matplotlib
#!pip install pylatexenc

Vetores e matrizes em Python

O Qiskit utiliza a linguagem de programação Python, portanto, antes de falar sobre o Qiskit especificamente, pode ser útil revisar brevemente os cálculos com matrizes e vetores em Python.

Em Python, cálculos com matrizes e vetores podem ser realizados usando a classe array da biblioteca NumPy, que oferece funcionalidades para diversas computações numéricas e científicas. O código a seguir carrega essa biblioteca, define dois vetores coluna, ket0 e ket1, correspondentes aos vetores de estado quântico 0\vert 0\rangle e 1,\vert 1\rangle, e então imprime a média deles.

import numpy as np

ket0 = np.array([[1], [0]])
ket1 = np.array([[0], [1]])

print(ket0 / 2 + ket1 / 2)
[[0.5]
[0.5]]

Também podemos usar array para criar matrizes que representem operações.

M1 = np.array([[1, 1], [0, 0]])
M2 = np.array([[1, 0], [0, 1]])
M = M1 / 2 + M2 / 2
print(M)
[[1.  0.5]
[0. 0.5]]

Note que todo o código dentro de uma determinada aula neste curso deve ser executado sequencialmente. Portanto, não é necessário importar o NumPy novamente aqui, pois ele já foi importado anteriormente.

A multiplicação de matrizes, incluindo a multiplicação matriz-vetor como caso especial, pode ser realizada usando a função matmul do NumPy.

print(np.matmul(M1, ket1))
print(np.matmul(M1, M2))
print(np.matmul(M, M))
[[1]
[0]]
[[1 1]
[0 0]]
[[1. 0.75]
[0. 0.25]]

Essa formatação de saída deixa a desejar visualmente. Uma solução, para situações que exigem algo mais apresentável, é usar a função array_to_latex do Qiskit, do módulo qiskit.visualization. Note que, no código a seguir, estamos usando a função genérica display do Python. Em contraste, o comportamento específico de print pode variar dependendo do que está sendo impresso, como ocorre com arrays definidos pelo NumPy.

from qiskit.visualization import array_to_latex

display(array_to_latex(np.matmul(M1, ket1)))
display(array_to_latex(np.matmul(M1, M2)))
display(array_to_latex(np.matmul(M, M)))
[10] \begin{bmatrix} 1 \\ 0 \\ \end{bmatrix} [1100] \begin{bmatrix} 1 & 1 \\ 0 & 0 \\ \end{bmatrix} [134014] \begin{bmatrix} 1 & \frac{3}{4} \\ 0 & \frac{1}{4} \\ \end{bmatrix}

Estados, medições e operações

O Qiskit inclui diversas classes que permitem criar e manipular estados, medições e operações — portanto, não é necessário programar tudo do zero para simular estados quânticos, medições e operações em Python. Alguns exemplos para ajudá-lo a começar estão incluídos abaixo.

Definir e exibir vetores de estado

A classe Statevector do Qiskit oferece funcionalidades para definir e manipular vetores de estado quântico. No código a seguir, a classe Statevector é importada e alguns vetores são definidos. (Também estamos importando a função sqrt da biblioteca NumPy para calcular raízes quadradas. Essa função poderia, alternativamente, ser chamada como np.sqrt, desde que o NumPy já tenha sido importado, como foi feito acima; esta é apenas uma forma diferente de importar e usar essa função específica.)

from qiskit.quantum_info import Statevector
from numpy import sqrt

u = Statevector([1 / sqrt(2), 1 / sqrt(2)])
v = Statevector([(1 + 2.0j) / 3, -2 / 3])
w = Statevector([1 / 3, 2 / 3])

A classe Statevector inclui um método draw para exibir vetores de estado de diversas formas, incluindo text para texto simples, latex para LaTeX renderizado e latex_source para código LaTeX, o que pode ser útil para copiar e colar em documentos. (Use print em vez de display para mostrar o código LaTeX com melhores resultados.)

display(u.draw("text"))
display(u.draw("latex"))
print(u.draw("latex_source"))
[0.70710678+0.j,0.70710678+0.j]

220+221\frac{\sqrt{2}}{2} |0\rangle+\frac{\sqrt{2}}{2} |1\rangle

\frac{\sqrt{2}}{2} |0\rangle+\frac{\sqrt{2}}{2} |1\rangle

A classe Statevector também inclui o método is_valid, que verifica se um dado vetor é um vetor de estado quântico válido (ou seja, se sua norma euclidiana é igual a 1):

display(u.is_valid())
display(w.is_valid())
True
False

Simulando medições com Statevector

A seguir, veremos uma forma de simular medições de estados quânticos no Qiskit, usando o método measure da classe Statevector. Vamos usar o mesmo vetor de estado quântico v definido anteriormente.

display(v.draw("latex"))

(13+2i3)0231(\frac{1}{3} + \frac{2 i}{3}) |0\rangle- \frac{2}{3} |1\rangle

Executar o método measure simula uma medição na base padrão. Ele retorna o resultado dessa medição, além do novo vetor de estado quântico do sistema após a medição. (Aqui estamos usando a função print do Python com o prefixo f para impressão formatada com expressões incorporadas.)

outcome, state = v.measure()
print(f"Measured: {outcome}\nPost-measurement state:")
display(state.draw("latex"))
Measured: 1
Post-measurement state:

1- |1\rangle

Os resultados das medições são probabilísticos, portanto esse método pode retornar resultados diferentes quando executado múltiplas vezes. Para o exemplo específico do vetor v definido acima, o método measure define o vetor de estado quântico após a medição como sendo

(1+2i5)0\biggl(\frac{1 + 2i}{\sqrt{5}}\biggr) \vert 0\rangle

(em vez de 0\vert 0\rangle) ou

1- \vert 1\rangle

(em vez de 1\vert 1\rangle), dependendo do resultado da medição. Em ambos os casos, as alternativas a 0\vert 0\rangle e 1\vert 1\rangle são, de fato, equivalentes a esses vetores de estado; diz-se que são equivalentes a menos de uma fase global, pois um é igual ao outro multiplicado por um número complexo no círculo unitário. Essa questão é discutida em mais detalhes na aula de Circuitos quânticos e pode ser ignorada com segurança por ora.

Statevector vai gerar um erro se o método measure for aplicado a um vetor de estado quântico inválido.

Statevector também possui o método sample_counts, que permite simular qualquer número de medições no sistema, cada vez começando com uma cópia nova do estado. Por exemplo, o código a seguir mostra o resultado de medir o vetor v 10001000 vezes, o que (com alta probabilidade) resulta no resultado 00 aproximadamente 55 de cada 99 vezes (ou cerca de 556556 das 10001000 tentativas) e no resultado 11 aproximadamente 44 de cada 99 vezes (ou cerca de 444444 das 10001000 tentativas). O código a seguir também demonstra a função plot_histogram do módulo qiskit.visualization para visualizar os resultados.

from qiskit.visualization import plot_histogram

statistics = v.sample_counts(1000)
plot_histogram(statistics)

Saída da célula de código anterior

Executar esse código por conta própria várias vezes, com diferentes números de amostras no lugar de 1000,1000, pode ser útil para desenvolver intuição sobre como o número de tentativas influencia quantas vezes cada resultado aparece. Com mais e mais amostras, a fração de amostras para cada possibilidade tende a se aproximar cada vez mais da probabilidade correspondente. Esse fenômeno, de forma mais geral, é conhecido como a lei dos grandes números na teoria das probabilidades.

Realizar operações com Operator e Statevector

Operações unitárias podem ser definidas no Qiskit usando a classe Operator, como no exemplo a seguir. Essa classe inclui um método draw com argumentos similares ao Statevector. Note que a opção latex produz resultados equivalentes ao array_from_latex.

from qiskit.quantum_info import Operator

Y = Operator([[0, -1.0j], [1.0j, 0]])
H = Operator([[1 / sqrt(2), 1 / sqrt(2)], [1 / sqrt(2), -1 / sqrt(2)]])
S = Operator([[1, 0], [0, 1.0j]])
T = Operator([[1, 0], [0, (1 + 1.0j) / sqrt(2)]])

display(T.draw("latex"))
[10022+2i2] \begin{bmatrix} 1 & 0 \\ 0 & \frac{\sqrt{2}}{2} + \frac{\sqrt{2} i}{2} \\ \end{bmatrix}

Podemos aplicar uma operação unitária a um vetor de estado usando o método evolve.

v = Statevector([1, 0])

v = v.evolve(H)
v = v.evolve(T)
v = v.evolve(H)
v = v.evolve(S)
v = v.evolve(Y)

display(v.draw("latex"))

(0.14644660940.3535533906i)0+(0.3535533906+0.8535533906i)1(0.1464466094 - 0.3535533906 i) |0\rangle+(-0.3535533906 + 0.8535533906 i) |1\rangle

Uma prévia dos circuitos quânticos

Os circuitos quânticos não serão formalmente introduzidos até a aula de Circuitos quânticos, que é a terceira aula deste curso, mas podemos desde já experimentar a composição de operações unitárias em qubits usando a classe QuantumCircuit do Qiskit. Em particular, podemos definir um circuito quântico (que, neste caso, será simplesmente uma sequência de operações unitárias aplicadas a um único qubit) da seguinte forma.

from qiskit import QuantumCircuit

circuit = QuantumCircuit(1)

circuit.h(0)
circuit.t(0)
circuit.h(0)
circuit.s(0)
circuit.y(0)

display(circuit.draw(output="mpl"))

Saída da célula de código anterior

Aqui estamos usando o método draw da classe QuantumCircuit com o renderizador mpl (abreviação de Matplotlib, uma biblioteca de visualização em Python). Este é o único renderizador que usaremos para circuitos quânticos neste curso, mas há outras opções, incluindo um renderizador baseado em texto e um baseado em LaTeX.

As operações são aplicadas sequencialmente, começando à esquerda e terminando à direita no diagrama. Uma forma prática de obter a matriz unitária correspondente a este Circuit é usar o método from_circuit da classe Operator.

display(Operator.from_circuit(circuit).draw("latex"))
[0.14644660940.3535533906i0.8535533906+0.3535533906i0.3535533906+0.8535533906i0.3535533906+0.1464466094i] \begin{bmatrix} 0.1464466094 - 0.3535533906 i & 0.8535533906 + 0.3535533906 i \\ -0.3535533906 + 0.8535533906 i & 0.3535533906 + 0.1464466094 i \\ \end{bmatrix}

Também podemos inicializar um vetor de estado quântico inicial e então evoluir esse estado de acordo com a sequência de operações descritas pelo Circuit.

ket0 = Statevector([1, 0])
v = ket0.evolve(circuit)
display(v.draw("latex"))

(0.14644660940.3535533906i)0+(0.3535533906+0.8535533906i)1(0.1464466094 - 0.3535533906 i) |0\rangle+(-0.3535533906 + 0.8535533906 i) |1\rangle

O código a seguir simula um experimento em que o estado obtido do Circuit acima é medido com uma medição na base padrão 4000 vezes (usando uma cópia nova do estado a cada vez).

statistics = v.sample_counts(4000)
display(plot_histogram(statistics))

Saída da célula de código anterior