Skip to main content
IBM Quantum Platform

Relación de este paquete con ffsim

Importante

Los conceptos que se recogen en esta guía solo están disponibles en la API de « Python ».

ffsim y este paquete se reparten el trabajo, en lugar de solaparse, siguiendo una única línea:

ffsim es propietario de
qiskit-fermions owns
Los operadores de aproximación de alto nivel (UCJOpSpinBalanced, y UCCSDOpRestrictedReal sus equivalentes), construidos a partir de amplitudes de clúster acopladas o de un vector de parámetrosEl circuito fermiónico (FermionicCircuit) en el que se convierten esos operadores, y su transpilación a un circuito de qubits mediante cualquier codificación de fermiones a qubits
Simulación rápida en un espacio compacto con un número fijo de partículasEl marco de trabajo del mapper y las estructuras de datos de los operadores a partir de las cuales se construye el circuito

La consecuencia práctica se aplica en ambos sentidos. Las matemáticas del ansatz se encuentran en ffsim, por lo que y UCJ UCC toman directamente un operador de ffsim como entrada. Por el contrario, las puertas de Qiskit propias de ffsim son específicas de la transformación de Jordan-Wigner, por lo que lo que se consigue al incorporarlas aquí es reducir uno de sus operadores de ansatz mediante una codificación diferente.

La simulación es el punto de encuentro entre ambos, y en esta guía se explica cómo. ffsim es el motor de simulación de este paquete, al que se accede a través de sus propios protocolos en lugar de mediante un envoltorio: la guía SKQD invoca ffsim.apply_unitary(), ffsim.linear_operator(), y directamente ffsim.sample_state_vector() sobre FermionOperatorlos FermionicCircuitobjetos y, sin ningún paso de conversión.


Conexión a 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 los vectores de estado de forma más compacta que un vector de estado genérico de un qubit de dimensión 2n2^n. Define los protocolos que cualquier objeto puede implementar para participar en su mecanismo de simulación (véanse los protocolos propios de qiskit_fermions.protocols 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 y _linear_operator_ _trace_. Eso es lo que hace que los operadores y circuitos de este paquete sean compatibles con ffsim, por lo que sus herramientas funcionan en ellos de forma nativa, sin necesidad de realizar ningún paso de conversión. ffsim.apply_unitary() puede simular un proceso «de FermionicCircuit extremo a extremo», ffsim.linear_operator() puede diagonalizar un FermionOperator, y las utilidades de muestreo como ffsim.sample_state_vector() (utilizadas en la guía de SKQD para convertir un vector de estado simulado en recuentos de mediciones) funcionan sin necesidad de modificaciones.

Lo que devuelve _linear_operator_ es un SciPy normal LinearOperator, por lo que scipy.sparse.linalg.eigsh() and friends 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

La integración a través de los protocolos de ffsim es lo que lo hace posible: su ecosistema de utilidades de simulación y muestreo, en constante desarrollo, se aplica tal cual a los circuitos y operadores de este paquete, y los usuarios que ya trabajan con ffsim pueden incorporar las puertas y los operadores de este paquete sin necesidad de 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 (tal y como se ha mostrado anteriormente). Al instalarlo se habilita la simulación: y ffsim.SupportsLinearOperator ffsim.SupportsTrace se implementan convirtiendo el de este paquete en FermionOperator un ffsim.FermionOperator, por lo que, sin ffsim, provocan un MissingOptionalLibraryError. Todo aquello que no implique simulación (operadores de construcción, su asignación y la transpilación de los circuitos resultantes) no necesita nada de eso.

ffsim depende transitivamente de PySCF, que no es compatible con Windows, por lo que en Windows el «extra» no da ningún resultado (una operación silenciosa sin efecto a través de un marcador sys_platform ) en lugar de un error de instalación. Los usuarios de Windows que quieran simular pueden hacerlo, por el momento, a través del Subsistema de Windows para Linux.


La simulación fermiónica se desarrolla en un sector con un número fijo de partículas

Tanto _apply_unitary_ como _linear_operator_ toman un argumento nelec y representan el vector de estado sobre la base determinante de número fijo de partículas para ese (norb, nelec) sector, reflejando la configuración del espacio FCI de ffsim (y, de forma transitiva, de PySCF’s ), en lugar del espacio completo de dimensión 2num_modes2^{\text{num\_modes}} que utilizaría un simulador general de qubits. Se trata de un espacio mucho más pequeño (su tamaño es el producto de los coeficientes binomiales, en lugar de una potencia de dos), pero conlleva una restricción estricta. En él solo pueden representarse los operadores y las puertas que conservan el número de partículas (y, en el caso de partículas con espín, el número de partículas de cada especie de espín por separado). Un operador cuya acción trasladara la amplitud a un número de partícula diferente no tendría dónde situarla.

ffsim resuelve esto rechazando directamente ese tipo de operador: al convertir uno de ellos en un operador lineal, se produce un error que indica ValueError qué ley de conservación no se cumple. Esa es la opción predeterminada correcta, ya que, de lo contrario, aplicar la condición de « exp⁡(−itH)\exp(-i t H) » a un hamiltoniano HH con un término que no conserva las partículas convertiría una aplicación que, en principio, sería unitaria en una no unitaria y sin sentido desde el punto de vista físico.

  • Evolution frente al rechazo del operador, va evolucionando.
  • OrbitalRotation comprueba que su matriz de rotación (incrustada) sea diagonal por bloques a lo largo de la división alfa/beta en el caso con espín, y genera una excepción 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 las partículas queda fuera del alcance en el espacio fermiónico, debido a la propia estructura de la representación de sector fijo, y ese es el precio que hay que pagar por el espacio FCI compacto, que es lo que hace que la simulación fermiónica sea factible en primer lugar. Si tu algoritmo necesita operadores que violen el número de partículas (por ejemplo, un canal de error nativo de qubits o un operador creado para un hamiltoniano mapeado que no conserve el número de partículas término por término), transpilalo primero a qubits y simula el resultado QuantumCircuit con un simulador a nivel de qubits. Cualquier ruta de transpilación sirve para esto; el circuito fermiónico y las guías de transpilación ofrecen más detalles, pero generate_preset_jw_pass_manager() es una opción predeterminada razonable.


La convención de «block-spin» para sistemas con espín

nelec se escribe int | tuple[int, int], y es su tipo —y no un indicador independiente— lo que selecciona una de las disposiciones de modo compatibles:

  • Un int selecciona la interpretación sin espín. Los modos norb 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 de espín de los modos 2 * norb según una convención fija de espín por bloques. Los modos 0 .. norb corresponden a los orbitales alfa (espín hacia arriba) y los modos norb .. 2 * norb a los orbitales beta (espín hacia abajo). Cada especie de espín se conserva de forma independiente; un término exclusivamente alfa puede desplazar un electrón entre modos alfa, pero nunca hacia un modo beta, y viceversa.

Esta distribución se produce en todos los puntos de entrada del protocolo ffsim de este paquete (constructores de puertas, _apply_unitary_placed_ implementaciones y la compilación por sectores del núcleo nativo de Rust), y es la convención utilizada en todas las guías de LUCJ y SKQD (por ejemplo, las ocupaciones alfa se InitializeModes.from_hartree_fock() asignan a los modos 0 .. n_alpha y las ocupaciones beta a los modos norb .. norb + n_beta). Merece la pena comparar esto con el módulo qiskit_fermions.operators y, en FermionicCircuit general, que utilizan índices de modo genéricos sin semántica de espín inherente (véase la guía de circuitos fermiónicos ). El significado del «block-spin» solo se aplica cuando nelec se proporciona un «spinful» a una llamada de simulación; no está integrado en la propia representación del operador o del circuito.

>>> 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

¿Le ha resultado útil esta página?
Informe de un error, de una errata o solicite contenido en GitHub.