Pular para o conteúdo principal

Ansaetze e formas variacionais

No coração de todos os algoritmos variacionais está a ideia fundamental de analisar as diferenças entre estados, que estão convenientemente relacionados por meio de algum mapeamento bem comportado (por exemplo, contínuo, diferenciável) a partir de um conjunto de parâmetros ou variáveis — daí o nome.

Primeiro, vamos explorar como construir circuitos parametrizados à mão. Usaremos esses circuitos para definir uma forma variacional que representa uma coleção de estados parametrizados para nosso algoritmo variacional explorar. Em seguida, construiremos nosso ansatz aplicando essa forma variacional ao nosso estado de referência.

Também vamos explorar como equilibrar velocidade versus precisão ao explorar esse espaço de busca.

Diagrama mostrando os principais componentes da discussão sobre ansatz, incluindo ansaetze heurísticos e ansaetze específicos para o problema.

Circuitos quânticos parametrizados

Os algoritmos variacionais operam explorando e comparando uma variedade de estados quânticos ψ(θ)|\psi(\vec{\theta})\rangle, que dependem de um conjunto finito de kk parâmetros θ=(θ0,,θk1)\vec{\theta} = (\theta^0, \ldots, \theta^{k-1}). Esses estados podem ser preparados usando um circuito quântico parametrizado, onde as portas são definidas com parâmetros ajustáveis. É possível criar esse circuito parametrizado sem vincular ângulos específicos ainda:

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit rustworkx
from qiskit.circuit import QuantumCircuit, Parameter

theta = Parameter("θ")

qc = QuantumCircuit(3)
qc.rx(theta, 0)
qc.cx(0, 1)
qc.x(2)

qc.draw("mpl")

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

from math import pi

angle_list = [pi / 3, pi / 2]
circuits = [qc.assign_parameters({theta: angle}) for angle in angle_list]

for circuit in circuits:
display(circuit.draw("mpl"))

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

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

Forma variacional e ansatz

Para otimizar iterativamente de um estado de referência ρ|\rho\rangle até um estado alvo ψ(θ)|\psi(\vec\theta)\rangle, precisamos definir uma forma variacional UV(θ)U_V(\vec{\theta}) que representa uma coleção de estados parametrizados para nosso algoritmo variacional explorar:

0URUR0=ρUV(θ)UA(θ)0=UV(θ)UR0=UV(θ)ρ=ψ(θ)\begin{aligned} |0\rangle \xrightarrow{U_R} U_R|0\rangle & = |\rho\rangle \xrightarrow{U_V(\vec{\theta})} U_A(\vec{\theta})|0\rangle \\[1mm] & = U_V(\vec{\theta})U_R|0\rangle \\[1mm] & = U_V(\vec{\theta})|\rho\rangle \\[1mm] & = |\psi(\vec{\theta})\rangle \\[1mm] \end{aligned}

Note que o estado parametrizado depende tanto do estado de referência ρ|\rho\rangle, que não depende de nenhum parâmetro, quanto da forma variacional UV(θ)U_V(\vec{\theta}), que sempre depende de parâmetros. Chamamos a combinação dessas duas metades de ansatz: UA(θ):=UV(θ)URU_A(\vec\theta) := U_V(\vec\theta)U_R.

Ao construir nosso ansatz para representar uma coleção de estados parametrizados para nosso algoritmo variacional explorar, percebemos um problema importante: a dimensionalidade. Um sistema de nn qubits (ou seja, espaço de Hilbert) tem um enorme número de estados quânticos distintos no espaço de configuração. Precisaríamos de um número impraticável de parâmetros para explorá-lo completamente. Quantitativamente, sua dimensionalidade é D=22nD = 2^{2n}. Para piorar, a complexidade de tempo dos algoritmos de busca, e similares, cresce exponencialmente com essa dimensionalidade, um fenômeno frequentemente referido na literatura como a maldição da dimensionalidade.

Para contornar essa limitação, é prática comum impor restrições razoáveis à forma variacional, de modo que apenas os estados mais relevantes sejam explorados. Encontrar um ansatz truncado eficiente é uma área ativa de pesquisa, mas abordaremos dois designs comuns.

Ansaetze heurísticos e compensações

Se você não tiver nenhuma informação sobre seu problema específico que possa ajudar a restringir a dimensionalidade, você pode tentar uma família arbitrária de circuitos parametrizados com menos de 22n2^{2n} parâmetros. No entanto, há algumas compensações a considerar:

  • Velocidade: Ao reduzir o espaço de busca, o algoritmo pode rodar mais rápido.
  • Precisão: Reduzir o espaço pode arriscar excluir a solução real para o problema, levando a soluções subótimas.
  • Ruído: Circuitos mais profundos são afetados pelo ruído, portanto precisamos experimentar com a conectividade, portas e fidelidade de portas do nosso ansatz.

Há uma compensação fundamental entre qualidade (ou até mesmo solubilidade) e velocidade: quanto mais parâmetros, maior a probabilidade de encontrar um resultado preciso, mas mais tempo levará para executar o algoritmo.

Circuitos N-locais

Um dos exemplos mais utilizados de ansaetze heurísticos é o dos circuitos N-locais, por algumas razões:

  • Implementação eficiente: O ansatz N-local é tipicamente composto por portas locais simples que podem ser implementadas eficientemente em um computador quântico, usando um pequeno número de qubits físicos. Isso facilita a construção e otimização de circuitos quânticos.
  • Captura correlações importantes: O ansatz N-local pode capturar correlações importantes entre os qubits em um sistema quântico, mesmo com um pequeno número de portas. Isso se deve ao fato de que as portas locais podem atuar em qubits vizinhos e criar emaranhamento entre eles, o que pode ser importante para simular sistemas quânticos complexos.

Esses circuitos consistem em camadas de rotação e emaranhamento que se repetem alternadamente uma ou mais vezes da seguinte forma:

  • Cada camada é formada por portas de tamanho no máximo NN, onde NN deve ser menor que o número de qubits.
  • Para uma camada de rotação, as portas são empilhadas umas sobre as outras. Podemos usar operações de rotação padrão, como RX ou CRZ.
  • Para uma camada de emaranhamento, podemos usar portas como Toffoli ou CX com uma estratégia de emaranhamento.
  • Ambos os tipos de camadas podem ser parametrizados ou não, mas pelo menos um deles precisa conter parâmetros. Caso contrário, sem pelo menos um parâmetro, não haveria variações!
  • Opcionalmente, uma camada de rotação extra é adicionada ao final do circuito.

Por exemplo, vamos criar um circuito NLocal de cinco qubits com blocos de rotação formados por portas RX e CRZ, blocos de emaranhamento formados por portas Toffoli que atuam nos qubits [0,1,2][0,1,2], [0,2,3][0,2,3], [4,2,1][4,2,1] e [3,1,0][3,1,0], e 22 repetições de cada camada.

from qiskit.circuit.library import NLocal, CCXGate, CRZGate, RXGate
from qiskit.circuit import Parameter

theta = Parameter("θ")
ansatz = NLocal(
num_qubits=5,
rotation_blocks=[RXGate(theta), CRZGate(theta)],
entanglement_blocks=CCXGate(),
entanglement=[[0, 1, 2], [0, 2, 3], [4, 2, 1], [3, 1, 0]],
reps=2,
insert_barriers=True,
)
ansatz.decompose().draw("mpl")

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

No exemplo acima, a maior porta é a porta Toffoli, que atua em três qubits, tornando o circuito 33-local. O tipo mais comumente usado de circuitos NN-locais são os circuitos 22-locais com portas de rotação de um único qubit e portas de emaranhamento de 22 qubits.

Vamos criar um circuito 22-local usando a classe TwoLocal do Qiskit. A sintaxe é a mesma do NLocal, mas há algumas diferenças. Por exemplo, a maioria das portas, como RX, RZ e CNOT, pode ser passada como strings sem importar as portas ou criar uma instância de Parameter.

from qiskit.circuit.library import TwoLocal

ansatz = TwoLocal(
num_qubits=5,
rotation_blocks=["rx", "rz"],
entanglement_blocks="cx",
entanglement="linear",
reps=2,
insert_barriers=True,
)
ansatz.decompose().draw("mpl")

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

Neste caso, usamos a distribuição de emaranhamento linear, onde cada qubit é emaranhado com o próximo. Para aprender sobre outras estratégias, consulte a documentação do TwoLocal.

Efficient SU2

efficient_su2 é um circuito eficiente em hardware que consiste em camadas de operações de um único qubit abrangendo SU(2) e emaranhamentos CX. É um padrão heurístico que pode ser usado para preparar funções de onda de teste para algoritmos quânticos variacionais ou como circuito de classificação para aprendizado de máquina.

from qiskit.circuit.library import efficient_su2

ansatz = efficient_su2(4, su2_gates=["rx", "y"], entanglement="linear", reps=1)
ansatz.decompose().draw("mpl")

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

Ansaetze específicos para o problema

Enquanto os ansaetze heurísticos e eficientes em hardware nos ajudam a resolver um problema de forma ingênua, podemos usar conhecimento específico do problema para restringir o espaço de busca de circuitos a um tipo específico. Isso nos ajudará a ganhar velocidade sem perder precisão no processo de busca.

Otimização

Em um problema de max-cut, queremos particionar os nós de um grafo de forma a maximizar o número de arestas entre nós em grupos diferentes. A partição max-cut desejada para o grafo abaixo é clara: o nó 0 à esquerda deve ser separado dos demais nós à direita por um corte.

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"
)

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

Para usar o algoritmo QAOA em um problema de max-cut, precisamos de um Hamiltoniano de Pauli que codifique o custo de forma que o valor esperado mínimo do operador corresponda ao número máximo de arestas entre os nós de dois grupos diferentes.

Para este exemplo simples, o operador é uma combinação linear de termos com operadores Z nos nós conectados por uma aresta (lembre-se que o 0-ésimo qubit fica mais à direita): ZZII+IZZI+ZIIZ+IZIZ+IIZZZZII + IZZI + ZIIZ + IZIZ + IIZZ. Uma vez construído o operador, o ansatz para o algoritmo QAOA pode ser facilmente construído usando o circuito QAOAAnsatz da biblioteca de circuitos do Qiskit.

# Pre-defined ansatz circuit, operator class and visualization tools
from qiskit.circuit.library import QAOAAnsatz
from qiskit.quantum_info import SparsePauliOp

# Problem to Hamiltonian operator
hamiltonian = SparsePauliOp.from_list(
[("ZZII", 1), ("IZZI", 1), ("ZIIZ", 1), ("IZIZ", 1), ("IIZZ", 1)]
)
# QAOA ansatz circuit
ansatz = QAOAAnsatz(hamiltonian, reps=2)
# Draw
ansatz.decompose(reps=3).draw("mpl")

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

A imagem anterior ilustra o ansatz em portas básicas para maior clareza. No entanto, ele pode ser expresso em múltiplos níveis de decomposição alterando o argumento reps ou desenhando o circuito sem o método de decomposição. Por exemplo, a representação a seguir mostra diretamente a estrutura QAOA com o valor padrão de reps, que é reps=1.

ansatz.decompose(reps=2).draw("mpl")

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

Aprendizado de máquina quântico

No aprendizado de máquina, uma aplicação comum é a classificação de dados em duas ou mais categorias. Isso envolve codificar um ponto de dados em um mapa de características que mapeia vetores de características clássicos para o espaço de Hilbert quântico. Construir mapas de características quânticos baseados em circuitos quânticos parametrizados que são difíceis de simular classicamente é um passo importante para obter uma vantagem potencial sobre as abordagens de aprendizado de máquina clássico e é uma área ativa de pesquisa atual.

O zz_feature_map pode ser usado para criar um circuito parametrizado. Podemos passar nossos pontos de dados para o mapa de características (xx) e uma forma variacional separada para passar pesos como parâmetros (θ\theta).

from qiskit.circuit.library import zz_feature_map, TwoLocal

data = [0.1, 0.2]

zz_feature_map_reference = zz_feature_map(feature_dimension=2, reps=2)
zz_feature_map_reference = zz_feature_map_reference.assign_parameters(data)

variation_form = TwoLocal(2, ["ry", "rz"], "cz", reps=2)
vqc_ansatz = zz_feature_map_reference.compose(variation_form)
vqc_ansatz.decompose().draw("mpl")

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

Resumo

Com esta lição, você aprendeu como definir seu espaço de busca com uma forma variacional:

  • Preparar estados com um circuito quântico parametrizado, onde as portas são definidas com parâmetros ajustáveis
  • Como construir ansaetze que equilibram velocidade versus precisão
  • Ansaetze heurísticos
  • Ansaetze específicos para o problema

Nossa carga de trabalho variacional de alto nível tem a seguinte aparência:

Diagrama de circuito mostrando dois unitários: um preparando o estado de referência e outro preparando o ansatz.

Para cada parâmetro variacional θ\vec\theta, um estado quântico diferente será produzido. Para encontrar os parâmetros ótimos, precisamos definir uma função de custo específica para o problema, a fim de atualizar iterativamente os parâmetros do nosso ansatz.