Relación de este paquete con ffsim
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ámetros | El 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ículas | El 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 . 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):
ffsim.SupportsApplyUnitary, mediante el método_apply_unitary_(vec, norb, nelec, copy), aplicando el operador como un operador unitario al vector de estado de un número fijo de partículasffsim.SupportsLinearOperator, mediante el método_linear_operator_(norb, nelec), que devuelve una vistascipy.sparse.linalg.LinearOperatordel objeto en ese mismo sectorffsim.SupportsTrace, mediante el método_trace_(norb, nelec), que devuelve el rastro 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 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 callLa 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 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 « » a un hamiltoniano 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.
Evolutionfrente al rechazo del operador, va evolucionando.OrbitalRotationcomprueba 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ónValueErrorsi 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: FalsePor 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
norbse 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 de espín de los modos2 * norbsegún una convención fija de espín por bloques. Los modos0 .. norbcorresponden a los orbitales alfa (espín hacia arriba) y los modosnorb .. 2 * norba 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
-
Consulta la guía del SKQD para ver cómo se
ffsim.sample_state_vector()utilizan yffsim.apply_unitary()conjuntamente para realizar muestreos de circuitos construidos a partir de las puertas de este paquete, y para evaluar un valor esperado del hamiltoniano medianteffsim.linear_operator()(que a su vez se basa en el mismo método del protocolo_linear_operator_descrito aquí). -
Consulta la guía de LUCJ para conocer la otra parte de la historia: cómo llevar un operador de tipo «ffsim ansatz» a través del proceso de transpilación de este paquete hasta un mapa de acoplamiento de dispositivos.
-
Lee la guía de circuitos fermiónicos para conocer la indexación de modos genérica e independiente del espín que se utiliza fuera de las llamadas de simulación.
-
Lee la guía de transpilación para saber cómo salir del espacio fermiónico
y simularlo en qubits, lo cual es necesario para los operadores que no conservan partículas.
-
Consulta el catálogo completo de protocolos que
qiskit_fermions.protocolsdefine este paquete, incluidos los protocolos de conversión (SupportsFermionOperator,SupportsMajoranaOperator) que no guardan relación con ffsim. -
Lee las guías de ffsim sobre los flujos de trabajo que desarrolla, en particular las relativas a la creación de un ansatz UCJ y a su optimización mediante métodos variacionales. Los vectores de parámetros que controlan esas guías son los que
UCJyUCCaceptan, a través de los operadores que construyen.