Saltar al contenido principal
Este proyecto está en fase beta. ¿Encontraste un error? Abre un issue en GitHub. ¿Tienes una pregunta o una idea? Pregunta en Discussions — ¡nos encantaría tu opinión!
¿Cómo es la calidad de la traducción en esta página?

Cómo se relaciona este paquete con ffsim

Importante

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 deqiskit-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ámetrosEl 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 fijoEl 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 2n2^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):

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 2num_modes2^{\text{num\_modes}} 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 exp⁡(−itH)\exp(-i t H) para un hamiltoniano HH 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.

  • Evolution hace aflorar ese rechazo desde el operador que evoluciona.

  • OrbitalRotation comprueba 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 un ValueError si 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 norb modos se tratan directamente como orbitales sin espín, y nelec es el número total de partículas.

  • Un par (n_alpha, n_beta) selecciona la interpretación con espín de 2 * norb modos bajo una convención de espín por bloques fija. Los modos 0 .. norb son los orbitales alfa (espín arriba) y los modos norb .. 2 * norb son 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​