Skip to main content
IBM Quantum Platform

Principi di progettazione delle rappresentazioni degli operatori

Questa guida illustra i principi di progettazione comuni e i concetti fondamentali condivisi da tutte le rappresentazioni degli operatori presenti nel operators modulo.


Panoramica

Le rappresentazioni degli operatori fornite da questo modulo condividono diversi principi fondamentali di progettazione:

  • Struttura di dati sparsa : gli operatori codificano solo le operazioni diverse dall'identità. La struttura interna dei dati si ispira in generale ai formati dei dati delle matrici sparse, consentendo un’archiviazione e un calcolo efficienti per sistemi con molte modalità ma con un numero relativamente esiguo di contributi significativi.
  • Iterazione e ricostruzione dei termini : indipendentemente dalla memorizzazione interna in formato sparsa, gli operatori forniscono un’interfaccia di iterazione coerente, che consente di esaminare, filtrare e trasformare i termini senza dover comprendere la struttura dei dati sottostante, per poi ricostruire nuovi operatori a partire dai termini modificati.
  • Indicizzazione basata sui modi : gli operatori utilizzano indici di modo astratti per etichettare i gradi di libertà fermionici, consentendo una mappatura flessibile dai sistemi fisici alla rappresentazione operatoria.
  • Raggruppamento dei termini e relazioni di commutazione : gli operatori supportano nativamente le informazioni di raggruppamento che associano i termini agli indici di gruppo. Ciò consente di effettuare ottimizzazioni e di preservare la struttura fisica senza richiedere strutture dati separate. Per un utilizzo pratico, consulta la guida al raggruppamento.
  • Operazioni aritmetiche e matematiche : tutti gli operatori implementano un insieme coerente di operazioni aritmetiche (come l'addizione, la moltiplicazione e la composizione) e di funzioni matematiche utilizzando il protocollo OperatorTrait , consentendo così l'uniformità del codice tra i diversi tipi di operatori.
  • Ordine dei termini degli operatori e forme normali : operatori matematicamente equivalenti possono avere rappresentazioni e comportamenti molto diversi negli algoritmi quantistici. Tutte le rappresentazioni degli operatori supportano varie forme normali (basate su relazioni di commutazione specifiche dell'algebra) per ottenere rappresentazioni degli operatori canoniche, prevedibili e ottimizzabili.

Struttura dati sparsa

Tutti gli operatori utilizzano una rappresentazione sparsa in cui vengono tracciate solo le operazioni diverse dall'identità. Ogni operazione non identitaria è costituita da un coefficiente (numero complesso) e da una sequenza di azioni su modalità specifiche.

Questo approccio riduce notevolmente l'utilizzo della memoria e il tempo di calcolo, soprattutto per i sistemi con molte modalità ma con un numero relativamente esiguo di contributi significativi. Codificando solo le operazioni non di identità, le operazioni si concentrano esclusivamente su ciò che conta, consentendo di lavorare con sistemi di grandi dimensioni, cosa che risulterebbe impossibile con rappresentazioni dense. Inoltre, gli operatori si adattano naturalmente a un numero qualsiasi di modi; un operatore che agisce sui modi funziona {0, 1} senza modifiche anche in sistemi con un numero molto maggiore di modi, poiché i modi non interessati sono implicitamente l’identità.

I termini identici vengono mantenuti separatamente durante le operazioni aritmetiche e, se necessario, devono essere combinati esplicitamente.

Formato di archiviazione interno

A livello interno, gli operatori sono memorizzati in array ispirati ai formati di dati delle matrici sparse:

  • Array dei coefficienti : il coefficiente complesso di ciascun termine
  • Array degli indici delle modalità : le modalità fermioniche su cui agisce ciascuna azione
  • Array dei limiti : indici che indicano dove iniziano e finiscono le modalità di ciascun termine nell'array delle modalità
Importante

A seconda del tipo di operatore, potrebbero essere presenti ulteriori array. Ad esempio, le istanze FermionOperator includono un array di azioni di tipo booleano che specifica il tipo di azione fermionica che agisce sul rispettivo indice di modo. Al contrario, la classe MajoranaOperator non richiede questa distinzione poiché codifica tale informazione nella parità dell’indice della modalità. Consulta la documentazione dell'API relativa al tuo tipo di operatore per comprendere appieno il formato di archiviazione.

Gli esempi seguenti mostrano come sono organizzati questi array. Per prima cosa, ecco una costruzione diretta che utilizza gli array sparsi:

[x] PYTHON

>>> from qiskit_fermions.operators import FermionOperator
>>>
>>> # Construct operators directly using sparse arrays
>>> # First operator: 1.0 * +0 -1
>>> op1 = FermionOperator(
...     coeffs=[1.0],
...     actions=[True, False],
...     modes=[0, 1],
...     boundaries=[0, 2],
... )
>>>
>>> # Second operator: 1.0 * +2 -3
>>> op2 = FermionOperator(
...     coeffs=[1.0],
...     actions=[True, False],
...     modes=[2, 3],
...     boundaries=[0, 2],
... )
>>>
>>> # Combine the sparse operators
>>> op1 += op2
>>> print(format(op1))
  1.000000e0 +0.000000e0j * (+0 -1)
  1.000000e0 +0.000000e0j * (+2 -3)

[] C

#include <qiskit_fermions.h>

// Construct first operator: 1.0 * c_0 a_1
QkComplex67 coeff1[1] = {{1.0, 0.0}};
uint32_t modes1[2] = {0, 1};
uint32_t boundaries1[2] = {0, 2};
QfFermionOperator *op1 = qf_ferm_op_new(1, 2, coeff1, modes1, boundaries1);

// Construct second operator: 1.0 * c_2 a_3
QkComplex67 coeff2[1] = {{1.0, 0.0}};
uint32_t modes2[2] = {2, 3};
uint32_t boundaries2[2] = {0, 2};
QfFermionOperator *op2 = qf_ferm_op_new(1, 2, coeff2, modes2, boundaries2);

// Add operators
qf_ferm_op_add_assign(op1, op2);

qf_ferm_op_free(op1);
qf_ferm_op_free(op2);

Metodi di costruzione pratici

Per gli sviluppatori di Python sono disponibili diversi metodi di costruzione pratici che nascondono i dettagli relativi alla memorizzazione dei dati sparsi. Questi facilitano la creazione di operatori senza doversi preoccupare di gestire gli array dei coefficienti, delle modalità e dei contorni:

[x] PYTHON

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct operators using operator algebra notation
>>> op1 = FermionOperator.from_dict({(cre(0), ann(1)): 1.0})
>>> op2 = FermionOperator.from_dict({(cre(2), ann(3)): 1.0})
>>>
>>> # The result is sparse even when combining them
>>> op1 += op2
>>> print(format(op1))
1.000000e0 +0.000000e0j * (+0 -1)
1.000000e0 +0.000000e0j * (+2 -3)

[] C

// The C API uses direct array construction; convenience methods are not available.
Suggerimento

Le singole implementazioni degli operatori potrebbero supportare metodi di costruzione aggiuntivi adatti al loro specifico caso d'uso. Consulta la documentazione dell'API relativa al tuo tipo di operatore per visualizzare tutte le opzioni di costruzione disponibili.


Iterazione e ricostruzione dei termini

Gli operatori forniscono un'interfaccia di iterazione coerente utilizzando OperatorTrait.iter_terms() indipendentemente dalla loro rappresentazione sparsa interna. È quindi possibile esaminare, filtrare o trasformare i termini senza dover comprendere la struttura dei dati sottostante. È quindi possibile ricostruire un nuovo operatore a partire dai termini trasformati utilizzando OperatorTrait.from_terms().

[x] PYTHON

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct an operator with terms of different orders
>>> op = FermionOperator.from_dict({
...     (): 0.5,  # constant term (order 0)
...     (cre(0), ann(1)): 1.0,  # two-body term (order 2)
...     (cre(0), cre(1), ann(1), ann(0)): 0.25  # four-body term (order 4)
... })
>>>
>>> # Filter to keep only terms of order 2
>>> order_two_terms = [
...     (term, coeff) for term, coeff in op.iter_terms()
...     if len(term) == 2
... ]
>>>
>>> # Reconstruct operator from filtered terms
>>> filtered_op = FermionOperator.from_terms(order_two_terms)
>>> print(f"Original operator has {len(op)} terms")
Original operator has 3 terms
>>> print(f"Filtered operator has {len(filtered_op)} term")
Filtered operator has 1 term

[] C

// WARNING: Term iteration and filtering are not yet available in the C API.

Indicizzazione basata sulle modalità

Tutte le rappresentazioni degli operatori si riferiscono agli indici su cui agiscono i loro termini come modi. Una modalità è semplicemente un indice che identifica un grado di libertà fermionico nel vostro sistema. La corrispondenza tra i gradi di libertà fisici (come gli orbitali spaziali, gli stati di spin o altri numeri quantici) e gli indici dei modi è lasciata all'utente, garantendo la massima flessibilità.

Questa astrazione è presente anche nel modulo qiskit_fermions.circuit , dove il FermionicCircuit agisce su un registro di modi fermionici. Sia nella rappresentazione per operatori che in quella per circuiti, le modalità offrono un modo coerente e indipendente dall’algebra per specificare quali gradi di libertà partecipano a una data operazione.

Importante

Le implementazioni attuali utilizzano modi privi di spin : tutte le rappresentazioni degli operatori attualmente fornite da questo modulo trattano i modi come gradi di libertà fermionici privi di spin. Ciò significa che, se il sistema presenta sia elettroni o fermioni con spin up che con spin down, è necessario assegnarli esplicitamente a modi distinti (ad esempio, i modi da 0 a 3 per lo spin up di quattro orbitali spaziali, i modi da 4 a 7 per lo spin down, o qualsiasi altra convenzione si scelga).

Questo approccio mantiene le rappresentazioni fondamentali semplici e generali, evitando al contempo di imporre una convenzione specifica per l’ordinamento degli spin. I moduli di utilità come qiskit_fermions.operators.library forniscono funzioni di comodità (ad esempio, FCIDump.from_file()) che gestiscono automaticamente tali mappature durante il caricamento dei dati relativi alla struttura elettronica.

Suggerimento

Man mano che il pacchetto si evolve, potrebbero essere aggiunte rappresentazioni di operatori spinful che supportino nativamente i gradi di libertà di spin nel proprio modello di dati. Queste saranno chiaramente distinte dalle attuali implementazioni senza spin e coesisteranno con esse all'interno del modulo.


Raggruppamento dei termini e relazioni di commutazione

Come i coefficienti e gli indici di modalità, gli operatori possono, facoltativamente, memorizzare un array di gruppi che associa ciascun termine a un indice di gruppo. Integrando il raggruppamento direttamente nella rappresentazione dell'operatore come parte della struttura dei dati sparsi, le informazioni relative al raggruppamento vengono naturalmente trasferite insieme all'operatore attraverso le trasformazioni. Ciò consente un utilizzo sistematico delle strutture, sia che si tratti di proprietà fisiche, relazioni algebriche o simmetrie specifiche del problema. Le informazioni strutturate possono quindi essere utilizzate in operazioni a valle, quali la sintesi e la scomposizione dei circuiti, ricorrendo a metodi quali OperatorTrait.split_out_groups().

Per indicazioni dettagliate su come raggruppare i termini degli operatori nei tuoi flussi di lavoro, consulta la guida al raggruppamento.

[x] PYTHON

>>> from qiskit_fermions.operators import MajoranaOperator, gamma
>>> op = MajoranaOperator.from_dict({
...     (gamma(0, False),): 1.0,
...     (gamma(1, False),): 1.0,
...     (gamma(2, False), gamma(3, False)): 1.0
... })
>>> # Assign group indices to terms
>>> op.groups = [0, 0, 1]
>>> # Partition operator by groups
>>> grouped_ops = op.split_out_groups()

[] C

#include <qiskit_fermions.h>

// Create operator with 3 terms
QkComplex67 coeffs[3] = {{1.0, 0.0}, {1.0, 0.0}, {1.0, 0.0}};
uint32_t modes[4] = {0, 1, 2, 3};
uint32_t boundaries[4] = {0, 1, 2, 4};
QfMajoranaOperator *op = qf_maj_op_new(3, 4, coeffs, modes, boundaries);

// Assign grouping information
uint32_t groups[3] = {0, 0, 1};
qf_maj_op_set_groups(op, groups, 3);

// Partition operator by groups
QfMajoranaOperator *grouped_ops[2];
qf_maj_op_split_out_groups(op, NULL, 0, grouped_ops);

Aritmetica e operazioni matematiche

Tutti gli operatori implementano il protocollo OperatorTrait , che fornisce un insieme unificato di operazioni per i diversi tipi di operatori. Ciò garantisce che il codice scritto per una rappresentazione dell'operatore funzioni in modo uniforme anche con le altre. Vedi qiskit_fermions.protocols per capire in che modo questo protocollo si relaziona con gli altri definiti in questo pacchetto.

Il protocollo comprende operazioni aritmetiche (come l'addizione, la moltiplicazione e la composizione), operazioni strutturali (iterazione dei termini, analisi del supporto modale, rietichettatura), funzioni matematiche (ordinamento normale, semplificazione, verifica dell'equivalenza) e altro ancora. Per un elenco completo di tutte le operazioni disponibili, consultare la documentazione OperatorTrait .

[x] PYTHON

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct a Hermitian operator: H = +0 -1 + +1 -0
>>> op = FermionOperator.from_dict({
...     (cre(0), ann(1)): 1.0,
...     (cre(1), ann(0)): 1.0
... })
>>>
>>> # Check if the operator is Hermitian by verifying H - H† = 0
>>> adjoint = op.adjoint()
>>> difference = op - adjoint
>>> difference = difference.normal_ordered()
>>> difference = difference.simplify(atol=1e-10)
>>> is_hermitian = difference.equiv(FermionOperator.zero(), atol=1e-10)
>>> print(f"Operator is Hermitian: {is_hermitian}")
Operator is Hermitian: True

[] C

#include <qiskit_fermions.h>

// Construct a Hermitian operator: H = +0 -1 + +1 -0
QkComplex67 coeffs[2] = {{1.0, 0.0}, {1.0, 0.0}};
uint32_t modes[4] = {0, 1, 1, 0};
uint32_t boundaries[3] = {0, 2, 4};
QfFermionOperator *op = qf_ferm_op_new(2, 4, coeffs, modes, boundaries);

// Check if Hermitian: compute H - H†, normal-order, and simplify
QfFermionOperator *adjoint = qf_ferm_op_adjoint(op);
QfFermionOperator *difference = qf_ferm_op_sub(op, adjoint);
QfFermionOperator *normal_ordered = qf_ferm_op_normal_ordered(difference);
qf_ferm_op_ichop(normal_ordered, 1e-10);

QfFermionOperator *zero = qf_ferm_op_zero();
bool is_hermitian = qf_ferm_op_equiv(normal_ordered, zero, 1e-10);
printf("Operator is Hermitian: %s\n", is_hermitian ? "true" : "false");

// Clean up
qf_ferm_op_free(op);
qf_ferm_op_free(adjoint);
qf_ferm_op_free(difference);
qf_ferm_op_free(normal_ordered);
qf_ferm_op_free(zero);
Importante

L'esempio utilizza sia atol=1e-10 in simplify() che in equiv(). Il parametro ( atol tolleranza assoluta) specifica una soglia. I coefficienti con valore assoluto inferiore a atol vengono considerati pari a zero e scartati. Ciò è fondamentale per la stabilità numerica nel confronto tra operatori, poiché l'aritmetica in virgola mobile può introdurre piccoli errori di arrotondamento che altrimenti impedirebbero di riconoscere come tali gli operatori equivalenti.

Suggerimento

Sebbene il protocollo OperatorTrait fornisca un'interfaccia comune, le singole implementazioni degli operatori potrebbero offrire metodi di comodità aggiuntivi che non fanno parte del protocollo. Ad esempio, alcuni operatori mettono a disposizione un metodo is_hermitian() che implementa questo controllo. Consulta sempre la documentazione dell'API relativa al tuo tipo di operatore per scoprire tutte le funzionalità disponibili.


Ordinamento dei termini dell’operatore e forme normali

Una sfida fondamentale nell'algebra degli operatori quantistici è che operatori matematicamente equivalenti possono essere rappresentati in molti modi diversi, ciascuno con implicazioni diverse per gli algoritmi quantistici. Lo stesso operatore può essere scritto in forme algebricamente equivalenti (ad esempio, l' aba^\dagger b e può essere espresso come ba+[a,b]ba^\dagger + [a^\dagger,b] ), ma queste rappresentazioni determinano comportamenti diversi nella sintesi dei circuiti, nella semplificazione e negli algoritmi numerici.

Le rappresentazioni degli operatori supportano le normali operazioni di ordinamento che trasformano gli operatori in forme canoniche specifiche dell'algebra utilizzando le relazioni di commutazione. Ciò consente di effettuare confronti affidabili (due operatori equivalenti hanno forme ordinate normalmente identiche), mette in luce le semplificazioni (le relazioni di commutazione fanno sì che i termini si annullino o si combinino) e supporta algoritmi che richiedono forme specifiche degli operatori per garantire correttezza ed efficienza.

Importante

Diverse rappresentazioni dell'operatore potrebbero implementare l'ordinamento normale sulla base di diverse relazioni di commutazione appropriate alla loro algebra. Ad esempio, l'ordinamento normale fermionico utilizza relazioni di anticommutazione ( {ci,cj}=δij\{c_i, c_j^\dagger\} = \delta_{ij} ), mentre l'ordinamento normale di Majorana utilizza convenzioni algebriche diverse ( {γi,γj}=2δij\{\gamma_i, \gamma_j\} = 2\delta_{ij} ). Consultare sempre la documentazione relativa al proprio tipo di operatore per comprendere come viene implementato l'ordinamento normale.

[x] PYTHON

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Two different representations of the same operator
>>> op1 = FermionOperator.from_dict({(ann(0), cre(0)): 1.0})
>>> op2 = FermionOperator.from_dict({(): 1.0, (cre(0), ann(0)): -1.0})
>>>
>>> # Direct comparison fails due to different forms
>>> op1.equiv(op2, atol=1e-10)
False
>>>
>>> # Normal-order both and compare again
>>> op1_normal = op1.normal_ordered()
>>> op2_normal = op2.normal_ordered()
>>> op1_normal.equiv(op2_normal, atol=1e-10)
True

[] C

#include <qiskit_fermions.h>
#include <stdbool.h>

// Two different representations of the same operator
QkComplex67 coeff1[1] = {{1.0, 0.0}};
uint32_t modes1[2] = {0, 0};
uint32_t boundaries1[3] = {0, 2};
QfFermionOperator *op1 = qf_ferm_op_new(1, 2, coeff1, modes1, boundaries1);

QkComplex67 coeff2[2] = {{1.0, 0.0}, {-1.0, 0.0}};
uint32_t modes2[2] = {0, 0};
uint32_t boundaries2[3] = {0, 0, 2};
QfFermionOperator *op2 = qf_ferm_op_new(2, 2, coeff2, modes2, boundaries2);

// Direct comparison fails due to different forms
bool equiv_before = qf_ferm_op_equiv(op1, op2, 1e-10);
printf("Equivalent before normal ordering: %s\n", equiv_before ? "true" : "false");

// Normal-order both and compare again
QfFermionOperator *op1_normal = qf_ferm_op_normal_ordered(op1);
QfFermionOperator *op2_normal = qf_ferm_op_normal_ordered(op2);
bool equiv_after = qf_ferm_op_equiv(op1_normal, op2_normal, 1e-10);
printf("Equivalent after normal ordering: %s\n", equiv_after ? "true" : "false");

// Clean up
qf_ferm_op_free(op1);
qf_ferm_op_free(op2);
qf_ferm_op_free(op1_normal);
qf_ferm_op_free(op2_normal);
Suggerimento

Il metodo del protocollo OperatorTrait.normal_ordered() lascia volutamente non specificati i propri argomenti posizionali e di chiave, consentendo alle implementazioni concrete dell'operatore di definire parametri regolabili che controllano l'esatta forma canonica prodotta. Ciò consente ottimizzazioni specifiche per l'operatore e varianti della forma normale adattate alla propria algebra o al proprio caso d'uso. Consulta la documentazione dell'API relativa al tuo tipo di operatore per verificare quali parametri sono disponibili.

Questa pagina è stata utile?
Segnala un bug, un errore di battitura o richiedi contenuti su GitHub.