Cómo se relaciona este paquete con ffsim
Los conceptos de esta guía solo están disponibles en la API de Python.
ffsim y este paquete se reparten el trabajo en lugar de solaparse, a lo largo de una sola línea:
| ffsim se encarga de | qiskit-fermions se encarga de |
|---|---|
Los operadores de ansatz de alto nivel (UCJOpSpinBalanced, UCCSDOpRestrictedReal y sus afines), construidos a partir de amplitudes de cúmulos acoplados o de un vector de parámetros | El circuito fermiónico (FermionicCircuit) en que se convierten esos operadores, y su transpilación a un circuito de qubits bajo cualquier codificación de fermión a qubit |
| Simulación rápida en un espacio compacto de número de partículas fijo | El marco de mapeadores y las estructuras de datos de operadores a partir de las cuales se construye el circuito |
La consecuencia práctica funciona en ambos sentidos. La matemática del ansatz vive en ffsim, así que UCJ y UCC aceptan directamente un operador de ffsim como entrada. A la inversa, las puertas de Qiskit propias de ffsim son específicas de la transformación de Jordan-Wigner, así que lo que obtienes al traer uno de sus operadores de ansatz aquí es poder bajarlo a través de una codificación distinta.
La simulación es donde ambos se encuentran, y esta guía explica cómo. ffsim es el backend de simulación de este paquete, al que se accede a través de sus propios protocolos en lugar de un envoltorio: la guía de SKQD llama a ffsim.apply_unitary(), ffsim.linear_operator() y ffsim.sample_state_vector() directamente sobre objetos FermionicCircuit y objetos FermionOperator, sin paso de conversión.
Conexión con la interfaz de simulación de ffsim
ffsim es un simulador de alto rendimiento para circuitos cuánticos fermiónicos que aprovecha la conservación del número de partículas y del espín Z para representar vectores de estado de forma más compacta que un vector de estado genérico de qubits de dimensión . Define protocolos que cualquier objeto puede implementar para participar en su maquinaria de simulación (consulta qiskit_fermions.protocols para los protocolos propios de este paquete, que siguen el mismo diseño):
-
ffsim.SupportsApplyUnitary, mediante el método_apply_unitary_(vec, norb, nelec, copy), que aplica el objeto como unitario a un vector de estado de número de partículas fijo -
ffsim.SupportsLinearOperator, mediante el método_linear_operator_(norb, nelec), que devuelve una vista del objeto comoscipy.sparse.linalg.LinearOperatoren ese mismo sector -
ffsim.SupportsTrace, mediante el método_trace_(norb, nelec), que devuelve la traza del objeto en ese sector.
Este paquete implementa esa interfaz: cada puerta fermiónica de qiskit_fermions.circuit.library proporciona _apply_unitary_, y FermionOperator proporciona _linear_operator_ y _trace_. Eso es lo que hace que los operadores y circuitos de este paquete sean compatibles con ffsim, de modo que sus herramientas funcionan con ellos de forma nativa, sin paso de conversión. ffsim.apply_unitary() puede simular un FermionicCircuit de principio a fin, ffsim.linear_operator() puede diagonalizar un FermionOperator, y utilidades de muestreo como ffsim.sample_state_vector() (usada en la guía de SKQD para convertir un vector de estado simulado en recuentos de medición) funcionan sin modificaciones.
Lo que devuelve _linear_operator_ es un LinearOperator ordinario de SciPy, así que scipy.sparse.linalg.eigsh() y similares también funcionan con él.
>>> import ffsim
>>> import numpy as np
>>>
>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.operators import FermionOperator, ann, cre
>>>
>>> norb, nelec = 2, (1, 1)
>>> hamiltonian = FermionOperator.from_terms([
... ([cre(0), ann(1)], 0.5),
... ([cre(1), ann(0)], 0.5),
... ])
>>>
>>> circuit = FermionicCircuit(2 * norb)
>>> circuit.append(Evolution(2 * norb, hamiltonian, time=1.0), circuit.modes)
>>>
>>> reference = ffsim.hartree_fock_state(norb, nelec)
>>> state = ffsim.apply_unitary(reference, circuit, norb=norb, nelec=nelec) # native ffsim call
Acoplarse a través de los protocolos de ffsim es lo que permite esto: su ecosistema, en desarrollo activo, de utilidades de simulación y muestreo se aplica sin cambios a los circuitos y operadores de este paquete, y los usuarios que ya trabajan con ffsim pueden incorporar las puertas y operadores de este paquete sin aprender otra API de simulación.
ffsim es una dependencia opcional
ffsim se declara como un extra opcional (pip install "qiskit-fermions[ffsim]", o de forma transitiva mediante [all]), protegido en tiempo de ejecución por HAS_FFSIM (como se mostró arriba). Instalarlo desbloquea la simulación: ffsim.SupportsLinearOperator y ffsim.SupportsTrace se implementan convirtiendo el FermionOperator de este paquete en un ffsim.FermionOperator, así que sin ffsim lanzan MissingOptionalLibraryError. Todo lo que no simula (construir operadores, mapearlos y transpilar los circuitos resultantes) no necesita nada de esto.
La simulación fermiónica vive en un sector de número de partículas fijo
Tanto _apply_unitary_ como _linear_operator_ toman un argumento nelec y representan el vector de estado sobre la base de determinantes de número de partículas fijo para ese sector (norb, nelec), reflejando la configuración del espacio FCI de ffsim (y, transitivamente, de PySCF), en lugar del espacio de dimensión completo que usaría un simulador general de qubits. Este es un espacio mucho más pequeño (su tamaño es un producto de coeficientes binomiales en lugar de una potencia de dos), pero viene con una restricción estricta. Solo se pueden representar en él los operadores y puertas que conservan el número de partículas (y, en el caso con espín, el número de partículas de cada especie de espín por separado). Un operador cuya acción movería amplitud a un número de partículas distinto no tiene dónde ponerla.
ffsim lo resuelve rechazando de plano un operador así: convertir uno en un operador lineal lanza un ValueError que indica qué ley de conservación falla. Es el comportamiento predeterminado correcto, porque aplicar para un hamiltoniano con un término que no conserva el número de partículas convertiría de otro modo lo que sería un unitario en un mapa no unitario y sin significado físico.
-
Evolutionhace aflorar ese rechazo desde el operador que evoluciona. -
OrbitalRotationcomprueba que su matriz de rotación (incrustada) sea diagonal por bloques respecto a la división alfa/beta en el caso con espín, y lanza unValueErrorsi mezcla sectores de espín.
>>> from qiskit_fermions.circuit.library import Evolution
>>>
>>> non_conserving = FermionOperator.from_terms([([cre(0), cre(1)], 1.0)]) # creates 2 particles
>>> gate = Evolution(2 * norb, non_conserving, time=1.0)
>>>
>>> try:
... gate._apply_unitary_(reference, norb, nelec, copy=True)
... except ValueError as exc:
... print("rejected:", exc)
rejected: The given FermionOperator could not be converted to a LinearOperator because it does not conserve particle number and the z component of spin. Conserves particle number: False Conserves spin z: False
Por lo tanto, la simulación que no conserva el número de partículas queda fuera de alcance en el espacio fermiónico, por construcción de la representación de sector fijo, y ese es el precio del espacio FCI compacto que hace tratable la simulación fermiónica en primer lugar. Si tu algoritmo necesita operadores que violan el número de partículas (por ejemplo, un canal de error nativo de qubits, o un operador construido para un hamiltoniano mapeado que no conserva el número de partículas término a término), transpila primero a qubits y simula el QuantumCircuit resultante con un simulador a nivel de qubits. Cualquier ruta de transpilación sirve para esto; las guías de circuito fermiónico y de transpilación profundizan más, pero generate_preset_jw_pass_manager() es una opción predeterminada razonable.
La convención de espín por bloques para sistemas con espín
nelec tiene el tipo int | tuple[int, int], y es su tipo, no un indicador aparte, lo que selecciona una de las disposiciones de modos admitidas:
-
Un int selecciona la interpretación sin espín. Los
norbmodos se tratan directamente como orbitales sin espín, yneleces el número total de partículas. -
Un par
(n_alpha, n_beta)selecciona la interpretación con espín de2 * norbmodos bajo una convención de espín por bloques fija. Los modos0 .. norbson los orbitales alfa (espín arriba) y los modosnorb .. 2 * norbson los orbitales beta (espín abajo). Cada especie de espín se conserva de forma independiente; un término solo alfa puede mover un electrón entre modos alfa pero nunca a un modo beta, y viceversa.
Este despacho ocurre en cada punto de entrada del protocolo ffsim de este paquete (constructores de puertas, implementaciones de _apply_unitary_placed_ y la compilación de sectores del núcleo nativo en Rust), y es la convención usada en todas las guías de LUCJ y SKQD (por ejemplo, InitializeModes.from_hartree_fock() coloca las ocupaciones alfa en los modos 0 .. n_alpha y las ocupaciones beta en los modos norb .. norb + n_beta). Vale la pena contrastarlo con el módulo qiskit_fermions.operators y con FermionicCircuit en general, que usan índices de modo genéricos sin semántica de espín inherente (consulta la guía de circuitos fermiónicos). El significado de espín por bloques solo se impone cuando se proporciona un nelec con espín a una llamada de simulación, no está incorporado en la representación del operador o del circuito en sí.
>>> from qiskit_fermions.circuit.library import InitializeModes
>>>
>>> norb, nelec = 3, (2, 1)
>>> init = InitializeModes.from_hartree_fock(norb, nelec)
>>> print([bool(occ) for occ in init.occupation]) # alpha modes 0,1; beta mode 3 (= norb + 0)
[True, True, False, True, False, False]
Próximos pasos
-
Recorre la guía de SKQD para ver cómo se usan juntas
ffsim.apply_unitary()yffsim.sample_state_vector()para muestrear circuitos construidos con las puertas de este paquete, y para evaluar un valor esperado de un hamiltoniano medianteffsim.linear_operator()(respaldado a su vez por el mismo método de protocolo_linear_operator_descrito aquí). -
Recorre la guía de LUCJ para ver la otra mitad de la historia: llevar un operador de ansatz de ffsim a través del flujo de transpilación de este paquete y hasta el mapa de acoplamiento de un dispositivo.
-
Lee la guía de circuitos fermiónicos para conocer la indexación de modos genérica, independiente del espín, que se usa fuera de las llamadas de simulación.
-
Lee la guía de transpilación para saber cómo salir del espacio fermiónico
y simular con qubits, algo necesario para operadores que no conservan las partículas.
-
Explora
qiskit_fermions.protocolspara ver el catálogo completo de protocolos que define este paquete, incluidos los protocolos de conversión (SupportsFermionOperator,SupportsMajoranaOperator) que no guardan relación con ffsim. -
Lee las guías propias de ffsim para los flujos de trabajo que le corresponden, en particular construir un ansatz UCJ y optimizarlo de forma variacional. Los vectores de parámetros que gobiernan esas guías son los que aceptan
UCJyUCC, a través de los operadores que construyen.