Vai al contenuto principale
Questo progetto è in fase beta. Hai trovato un bug? Apri una issue su GitHub. Hai una domanda o un'idea? Chiedi su GitHub Discussions — apprezziamo il tuo feedback!
Com'è la qualità della traduzione su questa pagina?

Come questo pacchetto si rapporta a ffsim

Importante

I concetti di questa guida sono disponibili solo nell'API Python.

ffsim e questo pacchetto si dividono il lavoro anziché sovrapporsi, lungo un'unica linea:

ffsim si occupa diqiskit-fermions si occupa di
Gli operatori di ansatz di alto livello (UCJOpSpinBalanced, UCCSDOpRestrictedReal e i loro simili), costruiti da ampiezze coupled-cluster o da un vettore di parametriIl circuito fermionico (FermionicCircuit) in cui quegli operatori si trasformano, e la sua transpilazione in un circuito di qubit con qualsiasi codifica da fermioni a qubit
Simulazione veloce in uno spazio compatto a numero di particelle fissoIl framework dei mapper e le strutture dati degli operatori da cui è costruito il circuito

La conseguenza pratica vale in entrambe le direzioni. La matematica dell'ansatz risiede in ffsim, quindi UCJ e UCC accettano direttamente un operatore ffsim come input. Viceversa, i gate Qiskit propri di ffsim sono specifici della trasformazione di Jordan-Wigner, quindi ciò che ottieni portandolo qui è poter abbassare uno dei suoi operatori di ansatz attraverso una codifica diversa.

La simulazione è il punto in cui i due si incontrano, e questa guida spiega come. ffsim è il backend di simulazione di questo pacchetto, raggiunto tramite i suoi protocolli anziché un wrapper: la guida SKQD chiama ffsim.apply_unitary(), ffsim.linear_operator() e ffsim.sample_state_vector() direttamente su oggetti FermionicCircuite su oggetti FermionOperatorsenza alcun passaggio di conversione.

Integrarsi con l'interfaccia di simulazione di ffsim​

ffsim è un simulatore ad alte prestazioni per circuiti quantistici fermionici che sfrutta la conservazione del numero di particelle e dello spin-Z per rappresentare i vettori di stato in modo più compatto di un generico statevector di qubit a 2n2^n dimensioni. Definisce protocolli che qualsiasi oggetto può implementare per partecipare al suo meccanismo di simulazione (vedi qiskit_fermions.protocols per i protocolli propri di questo pacchetto, che seguono lo stesso design):

Questo pacchetto implementa quell'interfaccia: ogni gate fermionico in qiskit_fermions.circuit.library fornisce _apply_unitary_, e FermionOperator fornisce _linear_operator_ e _trace_. Questo è ciò che rende gli operatori e i circuiti di questo pacchetto compatibili con ffsim, così i suoi strumenti funzionano su di essi in modo nativo, senza alcun passaggio di conversione. ffsim.apply_unitary() può simulare un FermionicCircuit da un capo all'altro, ffsim.linear_operator() può diagonalizzare un FermionOperator, e utilità di campionamento come ffsim.sample_state_vector() (usata nella guida SKQD per trasformare uno statevector simulato in conteggi di misura) funzionano senza modifiche.

Ciò che _linear_operator_ restituisce è un normale LinearOperator di SciPy, quindi anche scipy.sparse.linalg.eigsh() e simili funzionano su di esso.

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

L'accoppiamento tramite i protocolli di ffsim è ciò che rende possibile tutto questo: il suo ecosistema, in sviluppo attivo, di utilità di simulazione e campionamento si applica invariato ai circuiti e agli operatori di questo pacchetto, e chi già lavora con ffsim può integrare i gate e gli operatori di questo pacchetto senza imparare un'altra API di simulazione.

ffsim è una dipendenza opzionale​

ffsim è dichiarato come extra opzionale (pip install "qiskit-fermions[ffsim]", oppure in modo transitivo da [all]), protetto a runtime da HAS_FFSIM (come mostrato sopra). Installarlo sblocca la simulazione: ffsim.SupportsLinearOperator e ffsim.SupportsTrace sono implementati convertendo il FermionOperator di questo pacchetto in un ffsim.FermionOperator, quindi senza ffsim sollevano MissingOptionalLibraryError. Tutto ciò che non simula (costruire operatori, mapparli e transpilare i circuiti risultanti) non ne ha bisogno.

La simulazione fermionica avviene in un settore a numero di particelle fisso​

Sia _apply_unitary_ sia _linear_operator_ accettano un argomento nelec e rappresentano il vettore di stato sulla base dei determinanti a numero di particelle fisso per quel settore (norb, nelec), rispecchiando la configurazione dello spazio FCI di ffsim (e, transitivamente, di PySCF), anziché sullo spazio completo di dimensione 2num_modes2^{\text{num\_modes}} che userebbe un simulatore di qubit generico. Questo è uno spazio molto più piccolo (la sua dimensione è un prodotto di coefficienti binomiali anziché una potenza di due), ma comporta una restrizione rigida. Solo gli operatori e i gate che preservano il numero di particelle (e, nel caso con spin, il numero di particelle di ciascuna specie di spin singolarmente) possono esservi rappresentati. Un operatore la cui azione sposterebbe ampiezza su un numero di particelle diverso non ha dove metterla.

ffsim risolve il problema rifiutando del tutto un operatore di questo tipo: convertirne uno in un operatore lineare solleva un ValueError che indica quale legge di conservazione non è rispettata. È l'impostazione predefinita corretta, perché applicare exp⁡(−itH)\exp(-i t H) per un Hamiltoniano HH con un termine che non conserva il numero di particelle trasformerebbe altrimenti una presunta mappa unitaria in una mappa non unitaria e fisicamente priva di significato.

  • Evolution fa emergere quel rifiuto dall'operatore che evolve.

  • OrbitalRotation verifica che la sua matrice di rotazione (incorporata) sia diagonale a blocchi rispetto alla suddivisione alpha/beta nel caso con spin, e solleva un ValueError se mescola i settori di 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 simulazione che non conserva il numero di particelle è quindi fuori portata nello spazio fermionico, per costruzione della rappresentazione a settore fisso, e questo è il prezzo dello spazio FCI compatto che rende trattabile la simulazione fermionica fin dall'inizio. Se il tuo algoritmo richiede operatori che violano il numero di particelle (per esempio, un canale di errore nativo dei qubit, o un operatore costruito per un Hamiltoniano mappato che non conserva il numero di particelle termine per termine), esegui prima la transpilazione sui qubit e simula il QuantumCircuit risultante con un simulatore a livello di qubit. Qualsiasi percorso di transpilazione funziona a questo scopo; le guide sul circuito fermionico e sulla transpilazione entrano più nel dettaglio, ma generate_preset_jw_pass_manager() è una scelta predefinita ragionevole.

La convenzione block-spin per i sistemi con spin​

nelec è di tipo int | tuple[int, int], e il suo tipo, non un flag separato, determina quale dei layout di modi supportati viene selezionato:

  • Un int seleziona l'interpretazione senza spin (spinless). I norb modi sono trattati direttamente come orbitali senza spin, e nelec è il numero totale di particelle.

  • Una coppia (n_alpha, n_beta) seleziona l'interpretazione con spin di 2 * norb modi secondo una convenzione block-spin fissa. I modi 0 .. norb sono gli orbitali alpha (spin up) e i modi norb .. 2 * norb sono gli orbitali beta (spin down). Ogni specie di spin si conserva indipendentemente; un termine solo alpha può spostare un elettrone tra modi alpha ma mai in un modo beta, e viceversa.

Questo smistamento avviene in ogni punto di ingresso del protocollo ffsim in questo pacchetto (costruttori di gate, implementazioni di _apply_unitary_placed_ e compilazione dei settori del kernel Rust nativo), ed è la convenzione usata in tutte le guide LUCJ e SKQD (per esempio, InitializeModes.from_hartree_fock() inserisce le occupazioni alpha nei modi 0 .. n_alpha e le occupazioni beta nei modi norb .. norb + n_beta). Vale la pena contrapporla al modulo qiskit_fermions.operators e a FermionicCircuit in generale, che usano indici di modo generici senza alcuna semantica di spin intrinseca (vedi la guida al circuito fermionico). Il significato block-spin viene imposto solo quando si fornisce un nelec con spin a una chiamata di simulazione, non è incorporato nella rappresentazione dell'operatore o del circuito stessa.

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

Prossimi passi​