Compilador cuántico aproximado
qiskit.synthesis.unitary.aqc
Implementación del compilador cuántico aproximado descrito en el artículo [1].
Interfaz
A la interfaz pública principal de este módulo se llega pasando unitary_synthesis_method='aqc' a transpile(). Esto cambiará el método de síntesis para utilizar AQCSynthesisPlugin. Las clases individuales son:
AQC([optimizador, semilla] ) | Una implementación genérica del Compilador Cuántico Aproximado. |
ApproximateCircuit(num_qubits[, nombre] ) | Una clase base que representa un circuito aproximado. |
ApproximatingObjective() | Una clase base para la definición de un problema de optimización. |
CNOTUnitCircuit(num_qubits, cnots[, tol, nombre] ) | Clase que representa un circuito aproximado basado en bloques de unidades CNOT. |
CNOTUnitObjective(num_qubits, cnots) | Una clase base para la definición de un problema basado en la unidad CNOT. |
DefaultCNOTUnitObjective(num_qubits, cnots) | Una aplicación ingenua de la función objetivo basada en unidades CNOT. |
FastCNOTUnitObjective(num_qubits, cnots) | Implementación de la función objetivo y la calculadora de gradiente, que es similar a DefaultCNOTUnitObjective pero varias veces más rápida. |
Detalle matemático
Estamos interesados en compilar un circuito cuántico, que formalizamos como encontrar la mejor representación del circuito en términos de una secuencia ordenada de puertas de una matriz unitaria objetivo , con algunas restricciones de hardware adicionales. En particular, buscamos representaciones que puedan limitarse en términos de conectividad de hardware, así como de profundidad de compuerta, y elegimos una base de compuertas en términos de compuertas CNOT y de rotación. Recordemos que la combinación de CNOT y puertas de rotación es universal en y, por tanto, no limita la compilación.
Para definir adecuadamente lo que entendemos por «mejor representación del circuito», definimos la métrica como la norma de Frobenius entre la matriz unitaria del circuito compilado y la matriz unitaria objetivo , es decir, . Esta elección se basa en consideraciones de programación matemática y está relacionada con otras formulaciones que aparecen en la bibliografía especializada. Analicemos el problema con más detalle.
Sea el número de qubits y . Dada una estructura CNOT y un vector de ángulos de rotación , el circuito paramétrico forma una matriz . Si nos dan un circuito paramétrico que forma una matriz , entonces nos gustaría calcular
donde el producto interior es el producto interior de Frobenius. Nótese que para todos los unitarios y , por lo que el objetivo tiene alcance en .
Nuestra estrategia consiste en maximizar
utilizando su gradiente. A continuación analizaremos los detalles con un ejemplo.
Mientras que el rango de es un subconjunto de por construcción, el circuito objetivo puede formar una matriz unitaria general. Sin embargo, para cualquier ,
Por lo tanto, debemos normalizar el circuito de destino por su fase global y, a continuación, compilar aproximadamente el circuito normalizado. Podemos volver a añadir la fase global después.
En el algoritmo, denota la matriz objetivo no normalizada y la matriz objetivo normalizada. Ahora que tenemos , damos la función gradiente al optimizador del método de Nesterov y calculamos .
Para volver a añadir la fase global, podemos formar el circuito de control como
Obsérvese que, aunque en el artículo optimizamos utilizando el método de Nesterov, lo hicimos por sus garantías de convergencia, no por su velocidad en la práctica. Es mucho más rápido utilizar L-BFGS, que se utiliza como optimizador por defecto en esta implementación.
Una utilización básica del algoritmo AQC debería constar de los siguientes pasos:
# 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
)Ahora approximate_circuit es un circuito que se aproxima al unitario objetivo hasta cierto grado y puede utilizarse en lugar de la matriz original.
Para ello se utiliza una función auxiliar, make_cnot_network.
make_cnot_network
qiskit.synthesis.unitary.aqc.make_cnot_network(num_qubits, network_layout='spin', connectivity_type='full', depth=0)
Genera una red formada por bloques de construcción, cada uno de los cuales contiene una puerta CNOT y posiblemente algunas de un solo qubit. Esta red modela un operador cuántico en cuestión. Obsérvese que cada bloque de construcción tiene 2 entradas y salidas correspondientes a un par de qubits. Lo que en realidad devolvemos aquí es una cadena de índices de pares de qubits compartidos por cada bloque de construcción de una fila.
Parámetros
- num_qubits (int) – número de qubits.
- network_layout (str) – tipo de geometría de la red,
{"sequ", "spin", "cart", "cyclic_spin", "cyclic_line"}. - connectivity_type (str) – tipo de conectividad inter-qubit,
{"full", "line", "star"}. - depth (int) – profundidad de la red CNOT, es decir, el número de capas, donde cada capa consiste en un único bloque CNOT; se seleccionará el valor por defecto, si
L <= 0.
Devuelve
Una matriz de tamaño (2, N) matriz que define las capas en cnot-network, donde N
es igual L, o está definido por un tipo concreto de la red.
Eleva
ValueError - si se pasa un tipo de diseño de red CNOT o un número de qubits o una combinación de parámetros no admitidos.
Tipo de retorno
Se puede aprovechar la versión acelerada de la función objetivo. Implementa el mismo algoritmo matemático que el predeterminado DefaultCNOTUnitObjective pero se ejecuta varias veces más rápido. La instanciación de la clase de función objetivo acelerada es similar al caso por defecto:
# Crear un objetivo que defina nuestro problema de optimización approximating_objective = FastCNOTUnitObjective(num_qubits=num_qubits, cnots=cnots)
El resto del código del ejemplo anterior no cambia.
Referencias
[1] : Liam Madden, Andrea Simonetto, Los mejores problemas aproximados de compilación cuántica.