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.
Circuitos quânticos parametrizados
Os algoritmos variacionais operam explorando e comparando uma variedade de estados quânticos , que dependem de um conjunto finito de parâmetros . 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")
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"))
Forma variacional e ansatz
Para otimizar iterativamente de um estado de referência até um estado alvo , precisamos definir uma forma variacional que representa uma coleção de estados parametrizados para nosso algoritmo variacional explorar:
Note que o estado parametrizado depende tanto do estado de referência , que não depende de nenhum parâmetro, quanto da forma variacional , que sempre depende de parâmetros. Chamamos a combinação dessas duas metades de ansatz: .
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 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 é . 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 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 , onde 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
RXouCRZ. - Para uma camada de emaranhamento, podemos usar portas como
ToffoliouCXcom 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 , , e , e 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")
No exemplo acima, a maior porta é a porta Toffoli, que atua em três qubits, tornando o circuito -local. O tipo mais comumente usado de circuitos -locais são os circuitos -locais com portas de rotação de um único qubit e portas de emaranhamento de qubits.
Vamos criar um circuito -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")
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")
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"
)
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): . 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")
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")
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 () e uma forma variacional separada para passar pesos como parâmetros ().
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")
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:
Para cada parâmetro variacional , 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.