Trabajar con circuitos bosónicos
Un BosonicCircuit expresa un cálculo directamente sobre modos bosónicos. No lleva truncamiento, codificación ni número de qubits: estos entran más tarde, durante la transpilación, y así un mismo circuito se puede reducir con varias codificaciones y varios truncamientos sin reconstruirlo.
Expone deliberadamente una API reducida en comparación con QuantumCircuit de Qiskit, de modo que no se puedan aplicar puertas de qubits a un modo bosónico, lo que no sería una operación bien definida. Esta guía trata las operaciones que sí se exponen para ensamblar circuitos grandes a partir de otros más pequeños.
Por qué usar circuitos bosónicos
Considera el flujo de trabajo tradicional para llevar la evolución temporal de un hamiltoniano bosónico a una arquitectura basada en qubits:
-
Define el hamiltoniano bosónico, por ejemplo .
-
Elige un truncamiento y una codificación, y mapea con ellos el hamiltoniano a un operador de qubits.
-
Implementa la evolución temporal en el espacio de qubits, mediante trotterización u otro método.
El segundo paso llega pronto y toma dos decisiones que son costosas de deshacer. El truncamiento fija cuántos niveles conserva cada modo, y la codificación fija cuántos qubits cuesta eso, así que ambos quedan fijados en el operador de qubits antes de haber hecho nada más con el circuito. Para ver la misma física con otro corte u otra codificación, hay que volver a mapear el hamiltoniano y volver a construir el circuito. Además, lo que el mapeo descarta es la estructura bosónica: qué términos van juntos, y que los términos de un par de salto son adjuntos entre sí.
Un BosonicCircuit traslada esas decisiones al final:
-
Define el hamiltoniano bosónico.
-
Expresa la evolución temporal directamente sobre modos bosónicos, con puertas bosónicas.
-
Elige el truncamiento y la codificación como parte de la transpilación.
El circuito describe qué cálculo realizar, y la transpilación decide cómo llevarlo a qubits. El mismo circuito se reduce entonces con cada corte y bajo cada codificación, y la agrupación del operador sigue ahí para los pases que puedan aprovecharla. Las secciones siguientes muestran cómo se ensambla un circuito así; Transpilar circuitos bosónicos muestra cómo se reduce.
Indexación genérica de modos
El módulo operators y el circuito comparten una misma convención para los modos. Un modo es solo un índice que etiqueta un grado de libertad bosónico, y no tiene significado propio. Por tanto, puedes evolucionar cualquier operador que implemente el protocolo OperatorTrait, sea lo que sea lo que represente, y el circuito nunca necesita saber qué significa el modo 2. Lo mismo vale para el tamaño de un modo: un circuito no tiene corte de ocupación, así que nada aquí indica cuántos niveles tiene un modo.
Añadir puertas
Las puertas se añaden sobre modos. Solo se aceptan instancias de BosonicGate:
>>> from qiskit_bosons.circuit import BosonicCircuit
>>> from qiskit_bosons.circuit.library import Evolution, InitializeModes
>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>>
>>> hopping = BosonOperator.from_dict({(cre(0), ann(1)): -1.0, (cre(1), ann(0)): -1.0})
>>> hopping.groups = [0] * len(hopping) # the pair is Hermitian, its terms alone are not
>>>
>>> prepare = BosonicCircuit(2)
>>> prepare.append(InitializeModes([2, 0]), prepare.modes)
>>> step = BosonicCircuit(2)
>>> step.append(Evolution(2, hopping, time=0.1), step.modes)
Cualquier otra cosa se rechaza, y el error nombra el tipo infractor:
>>> from qiskit.circuit.library import HGate
>>> try:
... prepare.append(HGate(), [0])
... except ValueError as error:
... print(error)
Unsupported instruction type: <class '...HGate'>
Componer circuitos
compose() coloca las instrucciones de un circuito sobre otro. Se incrustan en línea, no se envuelven en una única puerta, de modo que el resultado es un circuito ordinario que puedes seguir descomponiendo y transpilando.
>>> program = prepare.compose(step)
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
De forma predeterminada se devuelve un circuito nuevo y ambos operandos quedan intactos. Pasa inplace=True para modificar el primero, y front=True para colocar el operando antes de las instrucciones existentes:
>>> step.compose(prepare, front=True)._inner.data == program._inner.data
True
Un circuito más estrecho se puede colocar sobre un subconjunto elegido de los modos:
>>> single = BosonicCircuit(1)
>>> single.append(Evolution(1, BosonOperator.from_dict({(cre(0), ann(0)): 1.0}), 0.1), single.modes)
>>> wide = BosonicCircuit(3).compose(single, [2])
>>> wide.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Solo se acepta como operando otro BosonicCircuit, porque el compose propio de Qiskit incrustaría puertas de qubits y eludiría la comprobación que impone append.
Repetir un circuito
repeat() repite un circuito de extremo a extremo. Las repeticiones se aplanan: el resultado contiene las instrucciones reps veces en secuencia, en lugar de una puerta que envuelve el circuito. Por tanto, los recuentos de operaciones escalan con reps, y decompose actúa sobre el cuerpo repetido en lugar de eliminar una repetición.
>>> len(step.repeat(0)._inner.data) # zero repetitions is the identity
0
>>> step.repeat(5).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Esta es la forma de trotterizar a nivel de circuito: evolucionar durante un tiempo corto y repetir. Consulta Sintetizar una puerta de evolución en el espacio bosónico para la alternativa de elegir una fórmula de producto en la propia puerta, y por qué ambas son cosas distintas que casualmente comparten la palabra “repeticiones”.
Separar pasos con barreras
Una barrera marca un punto de sincronización a través del cual los pases de optimización no fusionan puertas. barrier() coloca una en todos los modos, o en los modos que indiques:
>>> program = BosonicCircuit(2)
>>> program.append(InitializeModes([2, 0]), program.modes)
>>> program.barrier()
>>> program.barrier(program.modes[0], label="zero")
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
repeat(..., insert_barriers=True) coloca una entre repeticiones consecutivas, y ninguna después de la última:
>>> step.repeat(3, insert_barriers=True).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Barreras y codificación
Una codificación necesita varios qubits por modo, así que el registro de modos y el registro de qubits difieren en tamaño, y un layout relaciona registros completos. Por tanto, un modo no tiene qubits propios a los que restringir una barrera. Durante la síntesis de bosón a qubit, una barrera se mapea a todos los qubits de cada registro que tocaba, lo cual es correcto con cualquier layout:
>>> from qiskit_bosons.mappers import LevelSpec
>>> from qiskit_bosons.transpiler.presets import generate_preset_occupation_pass_manager
>>> lowered = generate_preset_occupation_pass_manager(LevelSpec(2, "unary_projected")).run(program)
>>> lowered.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Leer el resultado de un circuito bosónico
Un BosonicCircuit no tiene measure ni measure_all. Lo que significa un bit medido depende de la codificación. Con una codificación unaria, la ocupación de un modo se reparte entre varios qubits y hay que leerla de un patrón de bits, y con una compacta es un número binario, así que una medición que nombre un modo tendría que conocer la codificación, algo que un circuito bosónico deliberadamente no hace. Mide en su lugar el QuantumCircuit transpilado, y decodifica las cadenas de bits con LevelEncoding.level_to_bits() y los desplazamientos del LevelSpec con el que hiciste la reducción. Simular la dinámica de Bose-Hubbard con una codificación a elegir hace esto para un modelo completo.
>>> hasattr(BosonicCircuit, "measure_all")
False
Siguientes pasos
-
Transpilar circuitos bosónicos explica cómo un circuito bosónico se convierte en un circuito de qubits, etapa por etapa.
-
Rotar modos bosónicos trata la puerta que mezcla modos y conserva el número de bosones.
-
Sintetizar una puerta de evolución en el espacio bosónico muestra cómo trotterizar una evolución en el espacio bosónico.
-
Principios de diseño de las representaciones de operadores explica los operadores que transportan las puertas.