Aller au contenu principal
Ce projet est en phase bêta. Tu as trouvé un bug ? Ouvrir un ticket GitHub. Une question ou une idée ? Pose ta question dans Discussions — tes retours sont les bienvenus !
Quelle est la qualité de la traduction sur cette page ?

Comment ce paquet se rapporte à ffsim

Important

Les concepts de ce guide sont disponibles uniquement dans l'API Python.

ffsim et ce paquet se partagent le travail au lieu de se chevaucher, selon une seule ligne de partage :

ffsim s'occupe deqiskit-fermions s'occupe de
Les opérateurs d'ansatz de haut niveau (UCJOpSpinBalanced, UCCSDOpRestrictedReal et leurs variantes), construits à partir d'amplitudes de cluster couplé ou d'un vecteur de paramètresLe circuit fermionique (FermionicCircuit) en lequel ces opérateurs se transforment, et sa transpilation en circuit de qubits sous n'importe quel encodage fermion-vers-qubit
La simulation rapide dans un espace compact à nombre de particules fixeLe framework de mappers et les structures de données d'opérateurs à partir desquelles le circuit est construit

La conséquence pratique va dans les deux sens. Les mathématiques de l'ansatz résident dans ffsim, donc UCJ et UCC prennent directement un opérateur ffsim en entrée. À l'inverse, les portes Qiskit propres à ffsim sont spécifiques à la transformation de Jordan-Wigner, donc ce que t'apporte leur utilisation ici, c'est de pouvoir abaisser l'un de ses opérateurs d'ansatz via un encodage différent.

La simulation est le point de rencontre des deux, et ce guide explique comment. ffsim est le backend de simulation de ce paquet, atteint par ses propres protocoles plutôt que par une enveloppe : le guide SKQD appelle ffsim.apply_unitary(), ffsim.linear_operator() et ffsim.sample_state_vector() directement sur des objets FermionicCircuit et FermionOperator, sans étape de conversion.

S'intégrer à l'interface de simulation de ffsim​

ffsim est un simulateur haute performance de circuits quantiques fermioniques qui exploite la conservation du nombre de particules et du spin-Z pour représenter les vecteurs d'état de façon plus compacte qu'un statevector de qubits générique de dimension 2n2^n. Il définit des protocoles que n'importe quel objet peut implémenter pour participer à sa mécanique de simulation (voir qiskit_fermions.protocols pour les protocoles propres à ce paquet, qui suivent la même conception) :

Ce paquet implémente cette interface : chaque porte fermionique de qiskit_fermions.circuit.library fournit _apply_unitary_, et FermionOperator fournit _linear_operator_ et _trace_. C'est ce qui rend les opérateurs et circuits de ce paquet compatibles avec ffsim, de sorte que ses outils fonctionnent dessus nativement, sans étape de conversion. ffsim.apply_unitary() peut simuler un FermionicCircuit de bout en bout, ffsim.linear_operator() peut diagonaliser un FermionOperator, et des utilitaires d'échantillonnage comme ffsim.sample_state_vector() (utilisé dans le guide SKQD pour transformer un statevector simulé en comptages de mesure) fonctionnent sans modification.

Ce que retourne _linear_operator_ est un LinearOperator SciPy ordinaire, donc scipy.sparse.linalg.eigsh() et ses semblables fonctionnent aussi dessus.

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

Se coupler via les protocoles de ffsim est ce qui offre cela : son écosystème d'utilitaires de simulation et d'échantillonnage, activement développé, s'applique tel quel aux circuits et opérateurs de ce paquet, et les utilisateurs qui travaillent déjà avec ffsim peuvent y mêler les portes et opérateurs de ce paquet sans apprendre une autre API de simulation.

ffsim est une dépendance facultative​

ffsim est déclaré comme un extra facultatif (pip install "qiskit-fermions[ffsim]", ou de façon transitive par [all]), protégé à l'exécution par HAS_FFSIM (comme montré plus haut). L'installer débloque la simulation : ffsim.SupportsLinearOperator et ffsim.SupportsTrace sont implémentés en convertissant le FermionOperator de ce paquet en un ffsim.FermionOperator, donc sans ffsim ils lèvent MissingOptionalLibraryError. Tout ce qui ne simule pas (construire des opérateurs, les mapper et transpiler les circuits obtenus) n'en a aucun besoin.

La simulation fermionique se déroule dans un secteur à nombre de particules fixe​

_apply_unitary_ comme _linear_operator_ prennent un argument nelec et représentent le vecteur d'état sur la base de déterminants à nombre de particules fixe pour ce secteur (norb, nelec), en reprenant la configuration de l'espace FCI de ffsim (et, par transitivité, de PySCF), plutôt que l'espace complet de dimension 2num_modes2^{\text{num\_modes}} qu'utiliserait un simulateur de qubits générique. C'est un espace bien plus petit (sa taille est un produit de coefficients binomiaux plutôt qu'une puissance de deux), mais il s'accompagne d'une restriction stricte. Seuls les opérateurs et les portes qui conservent le nombre de particules (et, dans le cas avec spin, le nombre de particules de chaque espèce de spin individuellement) peuvent y être représentés. Un opérateur dont l'action déplacerait de l'amplitude vers un autre nombre de particules n'a nulle part où la mettre.

ffsim résout cela en rejetant purement et simplement un tel opérateur : le convertir en opérateur linéaire lève une ValueError qui nomme la loi de conservation violée. C'est le bon comportement par défaut, car appliquer exp⁡(−itH)\exp(-i t H) pour un hamiltonien HH avec un terme qui ne conserve pas le nombre de particules transformerait autrement ce qui devrait être un unitaire en une application non unitaire et physiquement dénuée de sens.

  • Evolution fait remonter ce rejet depuis l'opérateur qu'elle fait évoluer.

  • OrbitalRotation vérifie que sa matrice de rotation (intégrée) est diagonale par blocs selon la séparation alpha/bêta dans le cas avec spin, et lève une ValueError si elle mélange les secteurs de spin.

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

La simulation qui ne conserve pas le nombre de particules est donc hors de portée dans l'espace fermionique, de par la construction de la représentation à secteur fixe, et c'est le prix de l'espace FCI compact qui rend la simulation fermionique praticable. Si ton algorithme a besoin d'opérateurs qui violent le nombre de particules (par exemple un canal d'erreur natif aux qubits, ou un opérateur construit pour un Hamiltonien mappé qui ne conserve pas le nombre de particules terme par terme), transpile d'abord vers des qubits et simule le QuantumCircuit obtenu avec un simulateur au niveau des qubits. N'importe quelle voie de transpilation convient ; les guides circuit fermionique et transpilation donnent plus de détails, mais generate_preset_jw_pass_manager() est un choix par défaut raisonnable.

La convention de spin par blocs pour les systèmes avec spin​

nelec est typé int | tuple[int, int], et c'est son type, et non un indicateur séparé, qui sélectionne l'une des disposisitions de modes prises en charge :

  • Un int sélectionne l'interprétation sans spin. Les norb modes sont traités directement comme des orbitales sans spin, et nelec est le nombre total de particules.

  • Une paire (n_alpha, n_beta) sélectionne l'interprétation avec spin de 2 * norb modes selon une convention de spin par blocs fixe. Les modes 0 .. norb sont les orbitales alpha (spin haut) et les modes norb .. 2 * norb sont les orbitales bêta (spin bas). Chaque espèce de spin est conservée indépendamment ; un terme purement alpha peut déplacer un électron entre modes alpha mais jamais vers un mode bêta, et inversement.

Cette distribution a lieu à chaque point d'entrée du protocole ffsim dans ce paquet (constructeurs de portes, implémentations de _apply_unitary_placed_ et compilation des secteurs du noyau Rust natif), et c'est la convention utilisée dans les guides LUCJ et SKQD (par exemple, InitializeModes.from_hartree_fock() place les occupations alpha dans les modes 0 .. n_alpha et les occupations bêta dans les modes norb .. norb + n_beta). Il est utile de la comparer avec le module qiskit_fermions.operators et avec FermionicCircuit en général, qui utilisent des indices de modes génériques sans sémantique de spin inhérente (voir le guide du circuit fermionique). La signification de spin par blocs n'est imposée que lorsqu'un nelec avec spin est fourni à un appel de simulation, et n'est pas intégrée à la représentation de l'opérateur ou du circuit elle-même.

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

Étapes suivantes​