Compilador Quântico Aproximado
qiskit.synthesis.unitary.aqc
Implementação do Approximate Quantum Compiler, conforme descrito no artigo [1].
Interface
A interface pública principal desse módulo é acessada passando unitary_synthesis_method='aqc' para transpile(). Isso trocará o método de síntese para usar AQCSynthesisPlugin. As classes individuais são:
AQC([otimizador, semente] ) | Uma implementação genérica do Approximate Quantum Compiler. |
ApproximateCircuit(num_qubits[, nome] ) | Uma classe básica que representa um circuito aproximado. |
ApproximatingObjective() | Uma classe base para a definição de um problema de otimização. |
CNOTUnitCircuit(num_qubits, cnots[, tol, name] ) | Uma classe que representa um circuito aproximado baseado em blocos de unidades CNOT. |
CNOTUnitObjective(num_qubits, cnots) | Uma classe base para uma definição de problema baseada na unidade CNOT. |
DefaultCNOTUnitObjective(num_qubits, cnots) | Uma implementação ingênua da função objetiva baseada em unidades CNOT. |
FastCNOTUnitObjective(num_qubits, cnots) | Implementação da função objetiva e da calculadora de gradiente, que é semelhante ao site DefaultCNOTUnitObjective , mas várias vezes mais rápida. |
Detalhes matemáticos
Estamos interessados em compilar um circuito quântico, que formalizamos como a descoberta da melhor representação do circuito em termos de uma sequência de portas ordenadas de uma matriz unitária de destino , com algumas restrições adicionais de hardware. Em particular, analisamos as representações que podem ser restringidas em termos de conectividade de hardware, bem como a profundidade da porta, e escolhemos uma base de porta em termos de CNOT e portas de rotação. Lembramos que a combinação de CNOT e portas de rotação é universal em e, portanto, não limita a compilação.
Para definir adequadamente o que entendemos por “melhor representação do circuito”, definimos a métrica como a norma de Frobenius entre a matriz unitária do circuito compilado e a matriz unitária alvo , ou seja, . Essa escolha é motivada por considerações de programação matemática e está relacionada a outras formulações que aparecem na literatura. Vamos analisar o problema com mais detalhes.
Seja o número de qubits e . Dada uma estrutura CNOT e um vetor de ângulos de rotação , o circuito paramétrico forma uma matriz . Se nos for dado um circuito-alvo que forma uma matriz , gostaríamos de calcular
em que o produto interno é o produto interno de Frobenius. Observe que para todas as unidades e , de modo que o objetivo tem alcance em .
Nossa estratégia é maximizar
usando seu gradiente. Agora discutiremos os detalhes específicos por meio de um exemplo.
Embora o intervalo de seja um subconjunto de por construção, o circuito de destino pode formar uma matriz unitária geral. Entretanto, para qualquer ,
Portanto, devemos normalizar o circuito de destino por sua fase global e, em seguida, compilar aproximadamente o circuito normalizado. Podemos adicionar a fase global novamente depois.
No algoritmo, deixe denotar a matriz de destino não normalizada e a matriz de destino normalizada. Agora que temos , fornecemos a função gradiente ao otimizador do método de Nesterov e calculamos .
Para adicionar a fase global novamente, podemos formar o circuito de controle como
Observe que, embora tenhamos otimizado usando o método de Nesterov no artigo, isso foi feito por causa de suas garantias de convergência, não por sua velocidade na prática. É muito mais rápido usar o L-BFGS, que é usado como otimizador padrão nessa implementação.
Um uso básico do algoritmo AQC deve consistir nas seguintes etapas:
# Define a target circuit as a unitary matrix
unitary = ...
# Define a number of qubits for the algorithm, at least 3 qubits
num_qubits = round(math.log2(unitary.shape[0]))
# Choose a layout of the CNOT structure for the approximate circuit, e.g. ``spin`` for
# a linear layout.
layout = options.get("layout") or "spin"
# Choose a connectivity type, e.g. ``full`` for full connectivity between qubits.
connectivity = options.get("connectivity") or "full"
# Define a targeted depth of the approximate circuit in the number of CNOT units.
depth = int(options.get("depth") or 0)
# Generate a network made of CNOT units
cnots = make_cnot_network(
num_qubits=num_qubits,
network_layout=layout,
connectivity_type=connectivity,
depth=depth
)
# Create an optimizer to be used by AQC
optimizer = partial(scipy.optimize.minimize, method="L-BFGS-B")
# Create an instance
aqc = AQC(optimizer)
# Create a template circuit that will approximate our target circuit
approximate_circuit = CNOTUnitCircuit(num_qubits=num_qubits, cnots=cnots)
# Create an objective that defines our optimization problem
approximating_objective = DefaultCNOTUnitObjective(num_qubits=num_qubits, cnots=cnots)
# Run optimization process to compile the unitary
aqc.compile_unitary(
target_matrix=unitary,
approximate_circuit=approximate_circuit,
approximating_objective=approximating_objective
)Agora, approximate_circuit é um circuito que aproxima o unitário de destino até certo ponto e pode ser usado no lugar da matriz original.
Isso usa uma função auxiliar, make_cnot_network.
make_cnot_network
qiskit.synthesis.unitary.aqc.make_cnot_network(num_qubits, network_layout='spin', connectivity_type='full', depth=0)
Gera uma rede que consiste em blocos de construção, cada um contendo um portão CNOT e possivelmente alguns de um único qubit. Essa rede modela um operador quântico em questão. Observe que cada bloco de construção tem 2 entradas e saídas correspondentes a um par de qubits. O que realmente retornamos aqui é uma cadeia de índices de pares de qubits compartilhados por cada bloco de construção em uma linha.
Parâmetros
- num_qubits (int) – número de qubits.
- network_layout (str) – tipo de geometria de rede,
{"sequ", "spin", "cart", "cyclic_spin", "cyclic_line"}. - connectivity_type (str) – tipo de conectividade entre qubits,
{"full", "line", "star"}. - depth (int) – profundidade da rede CNOT, ou seja, o número de camadas, em que cada camada consiste em um único bloco CNOT; o valor padrão será selecionado, se
L <= 0.
Retorna
Uma matriz de tamanho (2, N) que define as camadas em cnot-network, onde N
é igual a L ou definido por um tipo concreto de rede.
Aumentos
ValueError - se for passado um tipo não suportado de layout de rede CNOT, número de qubits ou combinação de parâmetros.
Tipo de retorno
É possível tirar proveito da versão acelerada da função objetiva. Ele implementa o mesmo algoritmo matemático que o padrão DefaultCNOTUnitObjective , mas é executado várias vezes mais rápido. A instanciação da classe de função objetiva acelerada é semelhante ao caso padrão:
# Crie um objetivo que defina nosso problema de otimização approximating_objective = FastCNOTUnitObjective(num_qubits=num_qubits, cnots=cnots)
O restante do código no exemplo acima não é alterado.
Referências
[1] : Liam Madden, Andrea Simonetto, Best Approximate Quantum Compiling Problems (Problemas de compilação quântica com melhor aproximação).