Generar circuitos « SqDRIFT »
Con la diagonalización cuántica basada en muestras ( SQD ), debes elegir un ansatz a partir del cual tomar muestras de cadenas de bits. La variante « SqDRIFT » utiliza, en cambio, un conjunto de circuitos de evolución temporal construidos directamente a partir del hamiltoniano objetivo. Esto se consigue mediante el submuestreo de operadores de evolución temporal más pequeños a partir del hamiltoniano, basándose en sus coeficientes, lo que se conoce como el método de « qDRIFT » de Trotterización.
Esta guía de introducción explica cómo generar un conjunto de circuitos aleatorios de este tipo.
1. Configuración hamiltoniana
A efectos de esta guía, carga el hamiltoniano de la estructura electrónica de N2 desde un archivo FCIDUMP. Existen otras formas de construir el FermionOperator. No olvides consultar su documentación, así como el qiskit_fermions.operators.library.
[x] PYTHON
>>> from qiskit_fermions.operators.library import FCIDump
>>> from qiskit_fermions.operators import FermionOperator
>>>
>>> fcidump = FCIDump.from_file("docs/guides/n2.fcidump")
>>> num_modes = 2 * fcidump.norb
>>> hamil = FermionOperator.from_fcidump(fcidump)[] C
#include <qiskit_fermions.h>
QfFCIDump* fcidump = qf_fcidump_from_file("docs/guides/n2.fcidump");
QfFermionOperator* hamil = qf_ferm_op_from_fcidump(fcidump);
uint32_t num_modes = 2 * qf_fcidump_norb(fcidump);2. Términos del hamiltoniano de grupo
Aprovecha las numerosas simetrías presentes en el hamiltoniano de la estructura electrónica agrupando los términos relacionados que tienen coeficientes idénticos. Esta acción modifica la distribución de coeficientes de los operadores a partir de la cual el protocolo « qDRIFT » realiza sus muestreos, pero no afecta a sus garantías de convergencia. Es fundamental señalar que agrupar los términos relacionados por simetría da lugar a una cancelación favorable de los términos de Pauli, lo que se traduce en una profundidad de circuito global menor a la hora de hacer evolucionar un estado en el tiempo bajo su acción.
El qiskit_fermions.operators.terms.grouping módulo ofrece funciones prácticas para agrupar los términos de un operador. Esto se explica con más detalle en esta guía.
¡La aplicación de la fórmula group_terms_by_electronic_structure() supone que los términos del hamiltoniano están ordenados según la norma!
[x] PYTHON
>>> from qiskit_fermions.operators.terms.grouping import group_terms_by_electronic_structure
>>> from qiskit_fermions.operators.terms.ordering import canonical_order
>>>
>>> canon = canonical_order(hamil.normal_ordered().simplify(atol=1e-16))
>>> exit_code = group_terms_by_electronic_structure(canon, num_modes, two_body_physicist_order=False)
>>> assert exit_code is None
>>> print(canon.groups) # the groups attribute now contains some list of group indices
[0, ...][] C
QfFermionOperator* normal;
QfExitCode exit = qf_ferm_op_group_terms_by_electronic_structure(normal, num_modes, false);
QfFermionOperator* canon = qf_ferm_op_canonical_order(normal);El hamiltoniano completo de la estructura electrónica contiene ciertos términos cuya inclusión en un circuito de evolución temporal no tiene ningún impacto en las cadenas de bits percibidas y, por lo tanto, solo supone un mayor esfuerzo de muestreo. Por lo tanto, se recomienda eliminar dichos términos del hamiltoniano en este punto, antes de construir la puerta Evolution en el siguiente paso.
Los términos que se ajustan a esta descripción son aquellos que son diagonales en la base de números de ocupación, es decir, los productos de operadores numéricos ( ). Esto incluye el desplazamiento de energía constante, cuya evolución temporal solo introduce una fase global en el circuito; los operadores numéricos individuales, cuya evolución temporal equivale a rotaciones Z de un solo qubit; así como productos de orden superior, como . Ninguno de ellos afecta a las cadenas de bits muestreadas.
La función filter_diagonal_terms() elimina dichos términos de un operador sin modificarlo:
[x] PYTHON
>>> from qiskit_fermions.operators.terms.filtering import filter_diagonal_terms
>>>
>>> filter_diagonal_terms(canon)[] C
qf_ferm_op_filter_diagonal_terms(canon);Filtrar aquí, una sola vez, resulta considerablemente más barato que filtrar repetidamente. QDriftTrotterization Se ejecuta una vez por cada circuito transpilado, por lo que filtrar el hamiltoniano al principio, en lugar de hacerlo en cada llamada, evita tener que repetir el trabajo para cada circuito generado a partir de él.
3. Preparar el circuito de evolución temporal
Prepara el circuito de evolución temporal del hamiltoniano y el circuito base del que se extraerán las muestras. El contiene qiskit_fermions.circuit.library todos los componentes necesarios para ello, de acuerdo con las convenciones de Qiskit.
[x] PYTHON
>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>>
>>> time = 1.0 # you can choose a desired scaling factor here
>>> evo_gate = Evolution(num_modes, canon, time)
>>>
>>> circ = FermionicCircuit(num_modes)
>>> circ.append(evo_gate, circ.modes)[] C
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions.En este ejemplo no se inicializan los modos fermiónicos con partículas ni se mide su estado final.
4. Transpilar el circuito con QDrift Trotterization
El módulo qiskit_fermions.transpiler se integra directamente con el proceso de transpilación de Qiskit, lo que permite que el código construido FermionicCircuit anteriormente se transpilara directamente a un QuantumCircuit.
Utiliza la correspondencia entre jordan_wigner() fermiones y qubits para convertir el hamiltoniano expresado en términos de fermiones y expresarlo, en su lugar, en cadenas de Pauli. Esto se puede hacer directamente como parte del proceso de transpilación utilizando el complemento de pasada de transpilación EvolutionSynthesis . Utiliza generate_preset_jw_pass_manager() para construir FermionicStagedPassManager, lo que garantiza que la codificación de Jordan-Wigner se utilice de forma coherente en todas las instrucciones del circuito.
Es fundamental añadir la fase de transpilación QDriftTrotterization a la etapa optimization correspondiente del proceso de transpilación. Esto garantiza que el circuito no utilice la evolución temporal del hamiltoniano completo, cuya complejidad superaría las capacidades del hardware de computación cuántica disponible en la actualidad.
En su lugar, toma una submuestra de un número fijo de groups términos hamiltonianos para cada circuito, cada vez que se transpiló el circuito. De este modo, se pueden generar múltiples aleatorizaciones de circuitos, tal y como requiere el algoritmo « SqDRIFT », ejecutando repetidamente el proceso de transpilación.
En este paso también se presentan los pocos parámetros que puedes utilizar para personalizar los circuitos que se van a generar:
- El número de circuitos que hay que generar:
num_sqdrift_randomizations - La longitud de cada circuito en términos de grupos de excitación:
num_groups
[x] PYTHON
>>> from qiskit_fermions.transpiler import FermionicPassManager
>>> from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
>>> from qiskit_fermions.transpiler.passes import QDriftTrotterization
>>>
>>> num_groups = 10
>>> qdrift = QDriftTrotterization(num_groups, rng=19)
>>>
>>> pm = generate_preset_jw_pass_manager()
>>> pm.optimization = FermionicPassManager([qdrift])
>>>
>>> num_sqdrift_randomizations = 10
>>> sqdrift_circuits = [
... pm.run(circ) for _ in range(num_sqdrift_randomizations)
... ][] C
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.El ejemplo anterior corrige el generador de seed números aleatorios utilizado en el paso de QDriftTrotterization transpilación.
5. Eliminar las excitaciones insignificantes
Más allá de los términos diagonales filtrados en el paso anterior, una excitación muestreada aún puede no afectar a las cadenas de bits muestreadas. Siempre que actúa exclusivamente dentro de un conjunto de modos cuya ocupación ya está fijada (todos ocupados o todos desocupados), no puede trasladar una partícula de uno a otro, por lo que deja el estado (y, por tanto, el resultado final de la medición) sin cambios. La configuración filter_trivial=True en el QDriftTrotterization paso rechaza dichos términos a medida que se seleccionan y vuelve a extraer uno en su lugar, de modo que cada una de las num_groups ranuras del circuito resultante aporte una excitación no trivial.
Este filtro necesita saber qué modos están ocupados desde el principio. Por lo tanto, requiere una InitializeModes puerta previa a las puertas Evolution del circuito; InitializeModes.from_hartree_fock() es una forma práctica de construirla. Añade aquí una para la referencia de Hartree-Fock de N2 (siete electrones alfa y siete beta en 14 orbitales espaciales) y compara las excitaciones muestreadas con y sin filter_trivial=True:
[x] PYTHON
>>> from qiskit_fermions.circuit.library import InitializeModes
>>>
>>> init = InitializeModes.from_hartree_fock(fcidump.norb, (7, 7))
>>>
>>> hf_circ = FermionicCircuit(num_modes)
>>> hf_circ.append(init, hf_circ.modes)
>>> hf_circ.append(Evolution(num_modes, canon, time), hf_circ.modes)
>>>
>>> num_groups = 5
>>> qdrift_unfiltered = QDriftTrotterization(num_groups, rng=3480)
>>> qdrift_trivial = QDriftTrotterization(
... num_groups, filter_trivial=True, rng=3480
... )
>>>
>>> for instruction in FermionicPassManager(qdrift_unfiltered).run(hf_circ)._inner.data:
... if instruction.operation.name == "Evolution":
... print(sorted(instruction.operation.operator.get_support()))
[2, 4]
[41, 45, 52]
[15, 45, 55]
[41, 52, 53]
[10, 16, 37, 38]
>>>
>>> for instruction in FermionicPassManager(qdrift_trivial).run(hf_circ)._inner.data:
... if instruction.operation.name == "Evolution":
... print(sorted(instruction.operation.operator.get_support()))
[0, 1, 6, 7]
[0, 1, 28, 29]
[4, 13, 55]
[13, 20, 40, 41]
[0, 1, 28, 29][] C
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.Ninguna de las excitaciones muestreadas sin filtrar afecta en absoluto al conjunto ocupado (0-6 y 28-34), por lo que ninguna de ellas puede desplazar una partícula entre un modo ocupado y uno desocupado; todas y cada una de ellas son triviales y no tendrían ningún efecto sobre las cadenas de bits muestreadas. Con filter_trivial=True, los cinco se rechazan y se sustituyen por excitaciones que sí acoplan un modo ocupado con uno desocupado. Por ejemplo, la primera excitación aceptada [0, 1, 6, 7] desplaza a una partícula entre los modos ocupados 0, 1, y 6 y el modo desocupado 7.
Una vez que se acepta una excitación, todos los modos de su soporte pasan a ser «inciertos» y, por lo tanto, pueden desempeñar cualquiera de los dos roles en muestras posteriores, de modo que los conjuntos de modos ocupados y desocupados siguen creciendo a medida que se aceptan más excitaciones. Esto es lo que hace que la segunda excitación, [0, 1, 28, 29], sea aceptable. Sus cuatro modos se encuentran entre los que estaban ocupados inicialmente, por lo que no se acopla a ningún modo que estuviera desocupado inicialmente. Solo se acepta porque los modos y 0 1 pasaron a ser inciertos (y, por lo tanto, aptos para actuar como el lado «desocupado» del acoplamiento) en cuanto la primera excitación los alcanzó.
Sin una InitializeModes puerta anterior, no filter_trivial=True dispone de información sobre la ocupación con la que filtrar los datos. Emite un UserWarning y deja la muestra sin filtrar para esa Evolution puerta.
(Opcional) Optimizar la indexación de los modos fermiónicos
Se puede añadir un paso de optimización adicional al proceso de transpilación que minimice la distancia entre los intervalos de excitación fermiónicos, reasignando los índices de los modos fermiónicos. Esta optimización se presentó en el artículo « SqDRIFT » y ha sido implementada por build_excitation_span_minimization_model(). Se puede insertar fácilmente en el proceso del transpilador utilizando el paso RelabelModes :
[x] PYTHON
>>> from pyomo.environ import SolverFactory
>>> from qiskit_fermions.transpiler.passes import RelabelModes
>>>
>>> solver = SolverFactory("appsi_highs")
>>> solver.options["time_limit"] = 10
>>>
>>> qdrift = QDriftTrotterization(5, rng=19)
>>> relabel = RelabelModes(solver=solver)
>>>
>>> pm.optimization = FermionicPassManager([qdrift, relabel])
>>>
>>> relabeled_circ = pm.run(circ)
>>> # if the automatic mode relabeling was successful, the circuit's
>>> # metadata will contain the mode `permutation` information[] C
// WARNING: This feature is not available via the C API.Para utilizar la optimización automática integrada ( RelabelModes que aprovecha build_excitation_span_minimization_model()) es necesario contar con la dependencia opcional gestionada por HAS_PYOMO.
Para llevar a cabo la diagonalización correcta del subespacio, las cadenas de bits muestreadas de los circuitos que se han transpilado con la pasada de optimización RelabelModes deben someterse a un posprocesamiento basado en la información permutation contenida en los metadatos de los circuitos
Próximos pasos
Ahora que has generado correctamente un conjunto de circuitos, puedes obtener muestras de cadenas de bits a partir de ellos. Para ello, los circuitos deben ejecutarse en el hardware. Consulta la documentación de Qiskit para obtener instrucciones detalladas.
Una vez obtenidas las muestras de cadenas de bits, estas pueden utilizarse junto con los coeficientes hamiltonianos para llevar a cabo el posprocesamiento SQD, tal y como se explica en los tutoriales del complemento SQD.