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?

Raggruppare i termini dell'operatore: usa la struttura dell'operatore

Il raggruppamento è una delle operazioni in qiskit_fermions.operators.terms, che raccoglie routine che partizionano i termini di un operatore in base alla loro struttura.

Come descritto nella guida agli operatori, gli operatori possono memorizzare un array dei gruppi, una parte facoltativa della struttura dati sparsa che associa a ogni termine un indice di gruppo. I termini che condividono lo stesso indice di gruppo formano un gruppo. Questo ti permette di sfruttare sistematicamente la struttura nelle elaborazioni successive.

Raggruppando termini correlati (per proprietà fisiche, relazioni algebriche o simmetrie specifiche del problema), puoi ottenere diversi vantaggi:

  • Sintesi ottimizzata dei circuiti: i termini raggruppati portano informazioni specifiche del problema lungo tutto lo stack, consentendo un'ottimizzazione più semplice dei circuiti sintetizzati. Ne risultano una profondità del circuito e un numero di gate ridotti.

  • Conservazione della struttura fisica: i gruppi codificano struttura significativa del problema originale (per esempio, schemi di interazione o simmetrie) che può essere usata per decomposizioni fisicamente più significative.

  • Selezione dei flow set: il raggruppamento può codificare naturalmente flow set e altre strutture di teoria dei grafi rilevanti per la simulazione quantistica, come mostrato in [1].

  • Maggiore stabilità algoritmica: come dimostrato nella routine SqDRIFT, alcuni algoritmi possono trarre vantaggio da una maggiore stabilità o da prestazioni migliori grazie alla conservazione delle proprietà fisiche.

  • Fattori della formula di prodotto: una formula di prodotto fermionica suddivide un operatore nei suoi gruppi, quindi il raggruppamento determina sia il costo sia l'errore di Trotter di un'evoluzione temporale. Vedi Sintetizzare un gate Evolution nello spazio fermionico.

Utilizzo​

Il modo più semplice per definire un raggruppamento dell'operatore è impostare l'attributo groups dell'operatore di destinazione. Di seguito un esempio di raggruppamento dei termini secondo i line flow set, come mostrato nella Figura 1c di [1].

Suggerimento

Il modulo qiskit_fermions.operators.terms.grouping fornisce metodi di utilità per identificare automaticamente la struttura in un operatore e assegnare di conseguenza gli indici di gruppo.

>>> import rustworkx as rx
>>> graph = rx.PyDiGraph()
>>> _ = graph.add_nodes_from(range(6))
>>> edges = [(0, 1), (1, 2), (3, 0), (1, 4), (5, 2), (5, 4), (4, 3)]
>>> groups = [0, 0, 1, 2, 1, 3, 3]
>>> edges_with_group_as_payload = [(i, j, g) for (i, j), g in zip(edges, groups)]
>>> _ = graph.add_edges_from(edges_with_group_as_payload)
>>> from rustworkx.visualization import mpl_draw
>>> mpl_draw(
... graph,
... pos={i: (i % 3, i // 3) for i in range(6)},
... edge_labels=str,
... with_labels=True,
... )
<Figure size ... with 1 Axes>

A simple directed graph on which to define a MajoranaOperator.

Questo è un semplice reticolo due per tre di modi di Majorana collegati da archi orientati. Ogni arco rappresenta un termine dell'operatore che agisce sui due modi collegati. L'obiettivo è raggruppare questi termini in base alle etichette degli archi (mostrate nel grafo). I termini collegati da archi con la stessa etichetta vengono inseriti nello stesso gruppo. Il codice seguente mostra come farlo:

>>> from qiskit_fermions.operators import MajoranaOperator
>>>
>>> # Construct operator directly using sparse arrays
>>> # 7 terms, each with coefficient 1.0, modes from edge endpoints
>>> op = MajoranaOperator(
... coeffs=[1.0] * 7,
... modes=[node for edge in edges for node in edge],
... boundaries=[0, 2, 4, 6, 8, 10, 12, 14],
... )
>>>
>>> # Assign group indices to terms (groups is part of the sparse data structure)
>>> op.groups = groups
>>>
>>> # Partition operator by groups
>>> grouped_ops = op.split_out_groups()
>>>
>>> # Inspect each group
>>> for i, g in enumerate(grouped_ops):
... print(f"Group {i}: {list(sorted(g.iter_terms()))}")
Group 0: [([0, 1], (1+0j)), ([1, 2], (1+0j))]
Group 1: [([3, 0], (1+0j)), ([5, 2], (1+0j))]
Group 2: [([1, 4], (1+0j))]
Group 3: [([4, 3], (1+0j)), ([5, 4], (1+0j))]

Analizzare un raggruppamento​

I gruppi non prescrivono alcun significato proprio: l'array dei gruppi registra quali termini stanno insieme, mai perché. Nessuna routine di questa libreria segnala se un raggruppamento è «corretto», perché non esiste un'unica nozione di correttezza rispetto a cui riferire. I consumatori a valle, tuttavia, assumono determinate proprietà di un raggruppamento, e le funzioni di questa sezione ti permettono di verificare un'assunzione in anticipo, invece di pagarne il costo a ogni chiamata.

La convenzione più comune è che ogni gruppo sia hermitiano separatamente. È ciò che rende possibili le formule di prodotto randomizzate fermioniche: un singolo ap†aqa^\dagger_p a_q con p≠qp \neq q non è hermitiano, quindi la sua evoluzione temporale non è unitaria e non può essere campionata da sola. Accoppiandolo con aq†apa^\dagger_q a_p si ottiene un gruppo che può esserlo.

>>> from qiskit_fermions.operators import FermionOperator
>>> from qiskit_fermions.operators.terms.grouping import (
... group_coeff_means,
... groups_are_hermitian,
... groups_have_uniform_coeffs,
... )
>>>
>>> # a conjugate pair, plus one unpaired term. The operator is built from the sparse
>>> # arrays rather than from a dictionary, because only the former fixes the term order
>>> # that the group indices below are paired with.
>>> pair_and_single = FermionOperator(
... [1.0, 1.0, 1.0],
... [True, False, True, False, True, False],
... [0, 1, 1, 0, 2, 3],
... [0, 2, 4, 6],
... )
>>> pair_and_single.groups = [0, 0, 1]
>>>
>>> groups_are_hermitian(pair_and_single) # group 1 is unpaired
[True, False]
>>> group_coeff_means(pair_and_single)
[1.0, 1.0]

Viene imposto un indice di gruppo per termine​

L'array dei gruppi contiene un indice per termine, e assegnarne uno di lunghezza diversa solleva un errore. Nessuna elaborazione a valle ricontrolla questo invariante, quindi viene imposto nel punto in cui gli indici entrano nell'operatore: un array troppo corto scarterebbe silenziosamente i termini finali, e uno troppo lungo inventerebbe gruppi che nessun termine possiede.

>>> pair_and_single.groups = [0, 0] # one index short, on a three-term operator
Traceback (most recent call last):
ValueError: expected one group index per term, but got 2 for 3 terms

Cancellare gli indici di gruppo assegnando None è sempre consentito.

I due controlli sono indipendenti​

groups_have_uniform_coeffs() segnala se i coefficienti di un gruppo coincidono, che è l'assunzione fatta da group_coeff_means() quando calcola la media delle magnitudini. Non è una forma più debole del controllo di hermiticità: nessuno dei due controlli implica l'altro, in nessuna direzione.

>>> from qiskit_fermions.operators.terms.grouping import (
... groups_are_hermitian,
... groups_have_uniform_coeffs,
... )
>>> # uniform coefficients, but not Hermitian: the terms are not each other's adjoint
>>> uniform = FermionOperator(
... [1.0, 1.0],
... [True, False, True, False],
... [0, 1, 2, 3],
... [0, 2, 4],
... )
>>> uniform.groups = [0, 0]
>>> groups_have_uniform_coeffs(uniform), groups_are_hermitian(uniform)
([True], [False])
>>> # Hermitian, but with mixed magnitudes: two conjugate pairs at different scales
>>> mixed = FermionOperator(
... [1.0, 1.0, 5.0, 5.0],
... [True, False, True, False, True, False, True, False],
... [0, 1, 1, 0, 2, 3, 3, 2],
... [0, 2, 4, 6, 8],
... )
>>> mixed.groups = [0, 0, 0, 0]
>>> groups_are_hermitian(mixed), groups_have_uniform_coeffs(mixed)
([True], [False])

Per impostazione predefinita vengono confrontate le magnitudini, perché è ciò di cui group_coeff_means() calcola la media. Una coppia coniugata hermitiana con coefficienti complessi ha magnitudini uguali ma coefficienti diversi, quindi soddisfa solo la forma predefinita.

>>> conjugate = FermionOperator(
... [1.0j, -1.0j],
... [True, False, True, False],
... [0, 1, 1, 0],
... [0, 2, 4],
... )
>>> conjugate.groups = [0, 0]
>>> groups_have_uniform_coeffs(conjugate)
[True]
>>> groups_have_uniform_coeffs(conjugate, abs=False)
[False]
Nota

groups_are_hermitian() eredita la garanzia unilaterale di is_hermitian(): una voce True è sempre affidabile, mentre una voce False è affidabile solo per i tipi di operatore la cui forma normale è una vera forma canonica. Consulta il tipo di operatore specifico per sapere quale caso si applica.

[1] (1,2)

A. Gandon et al., Stabilizer-based quantum simulation of fermion dynamics with local qubit encodings, arXiv:2512.11418v2.