Zum Hauptinhalt springen
Dieses Projekt befindet sich in der Beta-Phase. Einen Fehler gefunden? GitHub Issue erstellen. Frage oder Idee? In Discussions fragen — wir freuen uns über dein Feedback!
Wie ist die Übersetzungsqualität auf dieser Seite?

Operatorterme gruppieren: die Operatorstruktur nutzen

Das Gruppieren ist eine der Operationen unter qiskit_fermions.operators.terms, das Routinen bündelt, die die Terme eines Operators anhand ihrer Struktur partitionieren.

Wie im Operatoren-Leitfaden beschrieben, können Operatoren ein Gruppen-Array speichern, einen optionalen Teil der dünnbesetzten Datenstruktur, der jedem Term einen Gruppenindex zuordnet. Terme mit demselben Gruppenindex bilden eine Gruppe. So kannst du die Struktur in der nachgelagerten Verarbeitung systematisch nutzen.

Durch das Gruppieren verwandter Terme (sei es nach physikalischen Eigenschaften, algebraischen Beziehungen oder problemspezifischen Symmetrien) kannst du mehrere Vorteile erschließen:

  • Optimierte Circuit-Synthese: Gruppierte Terme tragen problemspezifische Informationen durch den Stack und ermöglichen so eine einfachere Optimierung der synthetisierten Circuits. Das führt zu geringerer Circuit-Tiefe und weniger Gates.

  • Erhalt der physikalischen Struktur: Gruppen kodieren bedeutsame Struktur des ursprünglichen Problems (zum Beispiel Wechselwirkungsmuster oder Symmetrien), die für physikalisch aussagekräftigere Zerlegungen genutzt werden kann.

  • Auswahl von Flussmengen: Das Gruppieren kann auf natürliche Weise Flussmengen und andere graphentheoretische Strukturen kodieren, die für die Quantensimulation relevant sind, wie in [1] gezeigt.

  • Verbesserte algorithmische Stabilität: Wie die SqDRIFT-Routine zeigt, können manche Algorithmen durch den Erhalt physikalischer Eigenschaften von verbesserter Stabilität oder Leistung profitieren.

  • Produktformel-Faktoren: Eine fermionische Produktformel zerlegt einen Operator in seine Gruppen, sodass die Gruppierung sowohl die Kosten als auch den Trotter-Fehler einer Zeitentwicklung bestimmt. Siehe Synthese eines Evolution-Gates im fermionischen Raum.

Verwendung​

Der einfachste Weg, eine Operatorgruppierung zu definieren, ist das Setzen des Attributs groups des Zieloperators. Unten findest du ein Beispiel für die Gruppierung von Termen nach den Leitungsflussmengen, wie in Abbildung 1c von [1] gezeigt.

Hinweis

The qiskit_fermions.operators.terms.grouping module provides convenience methods for automatically identifying structure in an operator and assigning group indices accordingly.

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

Dies ist ein einfaches Zwei-mal-drei-Gitter aus Majorana-Modi, die durch gerichtete Kanten verbunden sind. Jede Kante stellt einen Term im Operator dar, der auf die beiden verbundenen Modi wirkt. Ziel ist es, diese Terme nach ihren Kantenbeschriftungen (im Graphen gezeigt) zu gruppieren. Terme, die durch Kanten mit derselben Beschriftung verbunden sind, kommen in dieselbe Gruppe. Der folgende Code zeigt, wie das geht:

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

Eine Gruppierung analysieren​

Gruppen schreiben keine eigene Bedeutung vor: Das Gruppen-Array hält fest, welche Terme zusammengehören, nie warum. Keine Routine in dieser Bibliothek meldet, ob eine Gruppierung „korrekt“ ist, weil es keinen einzelnen Korrektheitsbegriff gibt, gegen den man melden könnte. Nachgelagerte Verbraucher setzen jedoch Eigenschaften einer Gruppierung voraus, und mit den Funktionen in diesem Abschnitt kannst du eine solche Annahme vorab prüfen, statt bei jedem Aufruf dafür zu bezahlen.

Die gängigste Konvention ist, dass jede Gruppe für sich hermitesch ist. Das macht fermionische randomisierte Produktformeln überhaupt erst möglich: Ein einzelnes ap†aqa^\dagger_p a_q mit p≠qp \neq q ist nicht hermitesch, seine Zeitentwicklung ist also nicht unitär und kann nicht für sich allein gesampelt werden. Die Paarung mit aq†apa^\dagger_q a_p ergibt eine Gruppe, die das kann.

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

Ein Gruppenindex pro Term wird erzwungen​

Das Gruppen-Array enthält einen Index pro Term, und die Zuweisung eines Arrays anderer Länge löst einen Fehler aus. Nachgelagert prüft nichts diese Invariante erneut, deshalb wird sie dort erzwungen, wo die Indizes in den Operator gelangen: Ein zu kurzes Array würde stillschweigend nachfolgende Terme verwerfen, ein zu langes würde Gruppen erfinden, die kein Term trägt.

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

Das Löschen der Gruppenindizes durch Zuweisen von None ist immer erlaubt.

Die beiden Prüfungen sind unabhängig​

groups_have_uniform_coeffs() meldet, ob die Koeffizienten einer Gruppe übereinstimmen, was die Annahme ist, die group_coeff_means() beim Mitteln der Beträge macht. Sie ist keine schwächere Form der Hermitizitätsprüfung: Keine der beiden Prüfungen impliziert die andere, in keiner Richtung.

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

Standardmäßig werden die Beträge verglichen, weil group_coeff_means() diese mittelt. Ein hermitesch konjugiertes Paar mit komplexen Koeffizienten hat gleiche Beträge, aber ungleiche Koeffizienten und erfüllt daher nur die Standardform.

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

groups_are_hermitian() erbt die einseitige Garantie von is_hermitian(): Ein True-Eintrag ist immer verlässlich, ein False-Eintrag dagegen nur bei Operatortypen, deren Normalform eine echte kanonische Form ist. Sieh im jeweiligen Operatortyp nach, was zutrifft.

[1] (1,2)

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