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?

Designprinzipien der Operatordarstellungen

Dieser Leitfaden erläutert die gemeinsamen Designprinzipien und Kernkonzepte, die alle Operatordarstellungen im Modul operators teilen.

Überblick​

Die von diesem Modul bereitgestellten Operatordarstellungen teilen mehrere grundlegende Designprinzipien:

  • Dünnbesetzte Datenstruktur: Operatoren kodieren nur Nicht-Identitätsoperationen. Das interne Datenlayout ist im Allgemeinen von dünnbesetzten Matrixformaten inspiriert und ermöglicht effiziente Speicherung und Berechnung für Systeme mit vielen Moden, aber relativ wenigen wesentlichen Beiträgen.

  • Term-Iteration und Rekonstruktion: Unabhängig vom internen dünnbesetzten Speicher bieten Operatoren eine konsistente Iterationsschnittstelle, sodass du Terme untersuchen, filtern und transformieren kannst, ohne die zugrunde liegende Datenstruktur zu verstehen, und anschließend aus den veränderten Termen neue Operatoren rekonstruierst.

  • Modenbasierte Indizierung: Operatoren verwenden abstrakte Mode-Indizes, um fermionische Freiheitsgrade zu benennen, was eine flexible Abbildung von physikalischen Systemen auf die Operatordarstellung ermöglicht.

  • Termgruppierung und Kommutationsbeziehungen: Operatoren unterstützen nativ Gruppierungsinformationen, die Terme mit Gruppenindizes verknüpfen. Das ermöglicht Optimierungen und den Erhalt der physikalischen Struktur, ohne separate Datenstrukturen zu benötigen. Die praktische Verwendung findest du im Gruppierungsleitfaden.

  • Arithmetische und mathematische Operationen: Alle Operatoren implementieren über das Protokoll OperatorTrait einen konsistenten Satz arithmetischer Operationen (wie Addition, Multiplikation und Komposition) und mathematischer Funktionen, was einheitlichen Code über verschiedene Operatortypen hinweg ermöglicht.

  • Termreihenfolge und Normalformen von Operatoren: Mathematisch äquivalente Operatoren können in Quantenalgorithmen sehr unterschiedliche Darstellungen und ein sehr unterschiedliches Verhalten haben. Alle Operatordarstellungen unterstützen verschiedene Normalformen (basierend auf algebraspezifischen Kommutationsrelationen), um kanonische, vorhersagbare und optimierbare Operatordarstellungen zu erreichen.

Dünnbesetzte Datenstruktur​

Alle Operatoren verwenden eine dünnbesetzte Darstellung, in der nur Nicht-Identitätsoperationen verfolgt werden. Jede Nicht-Identitätsoperation besteht aus einem Koeffizienten (komplexe Zahl) und einer Folge von Aktionen auf bestimmten Moden.

Dieser Ansatz reduziert Speicherbedarf und Rechenzeit drastisch, besonders bei Systemen mit vielen Moden, aber relativ wenigen wesentlichen Beiträgen. Indem nur Nicht-Identitätsoperationen kodiert werden, konzentrieren sich die Operationen nur auf das, was zählt, und ermöglichen die Arbeit mit großen Systemen, die mit dichten Darstellungen nicht machbar wären. Außerdem skalieren Operatoren natürlich auf beliebig viele Moden; ein Operator, der auf die Moden {0, 1} wirkt, funktioniert unverändert in Systemen mit viel mehr Moden, da nicht betroffene Moden implizit die Identität sind.

Identische Terme bleiben bei arithmetischen Operationen getrennt erhalten und müssen bei Bedarf explizit zusammengeführt werden.

Internes Speicherformat​

Intern werden Operatoren in Arrays gespeichert, die von dünnbesetzten Matrixformaten inspiriert sind:

  • Koeffizienten-Array: Der komplexe Koeffizient jedes Terms

  • Mode-Index-Array: Die fermionischen Moden, auf die jede Aktion wirkt

  • Grenzen-Array: Indizes, die markieren, wo die Moden jedes Terms im Mode-Array beginnen und enden

Wichtig

Je nach Operatortyp können zusätzliche Arrays vorhanden sein. FermionOperator-Instanzen enthalten zum Beispiel ein Aktions-Array aus Booleans, das den Typ der fermionischen Aktion auf dem jeweiligen Mode-Index angibt. Die Klasse MajoranaOperator benötigt diese Unterscheidung dagegen nicht, da sie diese Information in der Parität des Mode-Index kodiert. Das vollständige Speicherformat findest du in der API-Dokumentation deines Operatortyps.

Die folgenden Beispiele zeigen, wie diese Arrays organisiert sind. Zuerst eine direkte Konstruktion mit den dünnbesetzten Arrays:

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

Praktische Konstruktionsmethoden​

Für Python-Entwickler:innen stehen mehrere praktische Konstruktionsmethoden zur Verfügung, die die Details der dünnbesetzten Speicherung abstrahieren. Damit lassen sich Operatoren leichter aufbauen, ohne sich um die Verwaltung der Arrays für Koeffizienten, Moden und Grenzen kümmern zu müssen:

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

Einzelne Operator-Implementierungen unterstützen möglicherweise zusätzliche Konstruktionsmethoden, die auf ihren jeweiligen Anwendungsfall zugeschnitten sind. Sieh in der API-Dokumentation deines Operatortyps nach, um alle verfügbaren Konstruktionsoptionen zu sehen.

Iteration über Terme und Rekonstruktion​

Operatoren bieten über OperatorTrait.iter_terms() unabhängig von ihrer internen dünnbesetzten Darstellung eine einheitliche Iterationsschnittstelle. Du kannst Terme daher untersuchen, filtern oder transformieren, ohne die zugrunde liegende Datenstruktur verstehen zu müssen. Aus den transformierten Termen kannst du anschließend mit OperatorTrait.from_terms() einen neuen Operator rekonstruieren.

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

Modenbasierte Indizierung​

Alle Operatordarstellungen bezeichnen die Indizes, auf die ihre Terme wirken, als Moden. Eine Mode ist schlicht ein Index, der einen fermionischen Freiheitsgrad in deinem System identifiziert. Die Abbildung von physikalischen Freiheitsgraden (etwa räumlichen Orbitalen, Spinzuständen oder anderen Quantenzahlen) auf Modenindizes bleibt dir überlassen, was maximale Flexibilität ermöglicht.

Diese Abstraktion findet sich auch im Modul qiskit_fermions.circuit, in dem FermionicCircuit auf einem Register fermionischer Moden arbeitet. Sowohl in der Operator- als auch in der Circuit-Darstellung bieten Moden eine einheitliche, algebraunabhängige Möglichkeit festzulegen, welche Freiheitsgrade an einer Operation beteiligt sind.

Wichtig

Aktuelle Implementierungen verwenden spinlose Moden: Alle derzeit von diesem Modul bereitgestellten Operatordarstellungen behandeln Moden als spinlose fermionische Freiheitsgrade. Das bedeutet: Wenn dein System sowohl Spin-up- als auch Spin-down-Elektronen oder -Fermionen enthält, musst du diese explizit auf unterschiedliche Moden abbilden (zum Beispiel Moden 0-3 für Spin-up von vier räumlichen Orbitalen, Moden 4-7 für Spin-down, oder eine beliebige andere Konvention deiner Wahl).

Dieser Entwurf hält die zentralen Darstellungen einfach und allgemein und vermeidet es, eine bestimmte Spin-Ordnungskonvention vorzuschreiben. Hilfsmodule wie qiskit_fermions.operators.library stellen Komfortfunktionen bereit (zum Beispiel FCIDump.from_file()), die solche Abbildungen für dich übernehmen, wenn du Daten zur Elektronenstruktur lädst.

Hinweis

Im Zuge der Weiterentwicklung des Pakets könnten spinbehaftete Operatordarstellungen hinzukommen, die Spinfreiheitsgrade nativ in ihrem Datenmodell unterstützen. Diese werden klar von den aktuellen spinlosen Implementierungen unterschieden und existieren neben ihnen im Modul.

Gruppierung von Termen und Kommutationsbeziehungen​

Wie die Koeffizienten und Modenindizes können Operatoren optional ein Gruppen-Array speichern, das jedem Term einen Gruppenindex zuordnet. Da die Gruppierung direkt als Teil der dünnbesetzten Datenstruktur in die Operatordarstellung integriert ist, wandert die Gruppierungsinformation ganz natürlich mit dem Operator durch Transformationen. So lässt sich Struktur systematisch nutzen, sei sie aus physikalischen Eigenschaften, algebraischen Beziehungen oder problemspezifischen Symmetrien abgeleitet. Die strukturierte Information kann anschließend in nachgelagerten Operationen wie der Circuit-Synthese und -Zerlegung verwendet werden, etwa mit Methoden wie OperatorTrait.split_out_groups().

Eine ausführliche Anleitung zur Gruppierung von Operatortermen in deinen Workflows findest du im Leitfaden Gruppierung.

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

Arithmetische und mathematische Operationen​

Alle Operatoren implementieren das Protokoll OperatorTrait, das einen einheitlichen Satz von Operationen über verschiedene Operatortypen hinweg bereitstellt. Dadurch funktioniert Code, der für eine Operatordarstellung geschrieben wurde, einheitlich auch mit anderen. In qiskit_fermions.protocols erfährst du, wie dieses Protokoll mit den anderen Protokollen dieses Pakets zusammenhängt.

Das Protokoll umfasst arithmetische Operationen (wie Addition, Multiplikation und Komposition), strukturelle Operationen (Iteration über Terme, Analyse der Modenunterstützung, Umbenennung), mathematische Funktionen (Normalordnung, Vereinfachung, Äquivalenzprüfung) und mehr. Eine vollständige Referenz aller verfügbaren Operationen findest du in der Dokumentation zu OperatorTrait.

>>> 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
>>> print(f"Operator is Hermitian: {op.is_hermitian(atol=1e-10)}")
Operator is Hermitian: True
Hinweis

is_hermitian() prüft, ob H−H†=0H - H^\dagger = 0 gilt, indem die Differenz auf eine Normalform reduziert und mit zero() verglichen wird. In Form der anderen Protokolloperationen ausgeschrieben, führt sie Folgendes aus:

>>> difference = (op - op.adjoint()).normal_ordered().simplify(atol=1e-10)
>>> difference.equiv(FermionOperator.zero(), atol=1e-10)
True

Die Aussagekraft dieser Prüfung hängt vom Operatortyp ab, denn sie hängt davon ab, wie vollständig die Normalform des jeweiligen Typs benachbarte Generatoren kontrahiert. Ein Ergebnis True ist immer zuverlässig, aber bei manchen Typen ist ein Ergebnis False konservativ: Details siehe is_hermitian().

Wichtig

Das Beispiel verwendet atol=1e-10. Der Parameter atol (absolute Toleranz) legt einen Schwellenwert fest. Koeffizienten mit einem Betrag unterhalb von atol werden als null behandelt und verworfen. Das ist für die numerische Stabilität beim Vergleich von Operatoren wesentlich, da Gleitkommaarithmetik kleine Rundungsfehler einführen kann, die andernfalls verhindern würden, dass äquivalente Operatoren als solche erkannt werden.

Hinweis

Das Protokoll OperatorTrait stellt zwar eine gemeinsame Schnittstelle bereit, einzelne Operator-Implementierungen bieten aber möglicherweise zusätzliche Komfortmethoden, die nicht Teil des Protokolls sind. FermionOperator und MajoranaOperator stellen beispielsweise eine Methode max_rank() bereit, die die Länge ihres längsten Terms angibt. Sieh immer in der API-Dokumentation deines Operatortyps nach, um alle verfügbaren Funktionen zu entdecken.

Termordnung und Normalformen von Operatoren​

Eine grundlegende Herausforderung der Quantenoperator-Algebra besteht darin, dass mathematisch äquivalente Operatoren auf viele verschiedene Arten dargestellt werden können, mit jeweils unterschiedlichen Folgen für Quantenalgorithmen. Derselbe Operator kann in algebraisch äquivalenten Formen geschrieben werden (zum Beispiel lässt sich a†ba^\dagger b als ba†+[a†,b]ba^\dagger + [a^\dagger,b] ausdrücken), doch diese Darstellungen führen zu unterschiedlichem Verhalten bei der Circuit-Synthese, der Vereinfachung und in numerischen Algorithmen.

Die Operatordarstellungen unterstützen Normalordnungs-Operationen, die Operatoren mithilfe von Kommutationsbeziehungen in algebraspezifische kanonische Formen überführen. Das ermöglicht verlässliche Vergleiche (zwei äquivalente Operatoren haben identische normalgeordnete Formen), macht Vereinfachungen sichtbar (Kommutationsbeziehungen lassen Terme sich aufheben oder zusammenfassen) und unterstützt Algorithmen, die für Korrektheit und Effizienz bestimmte Operatorformen benötigen.

Wichtig

Verschiedene Operatordarstellungen implementieren die Normalordnung möglicherweise auf Basis unterschiedlicher, zu ihrer Algebra passender Kommutationsbeziehungen. Die fermionische Normalordnung verwendet zum Beispiel Antikommutationsrelationen ({ci,cj†}=δij\{c_i, c_j^\dagger\} = \delta_{ij}), während die Majorana-Normalordnung andere algebraische Konventionen nutzt ({γi,γj}=2δij\{\gamma_i, \gamma_j\} = 2\delta_{ij}). Sieh immer in der Dokumentation deines Operatortyps nach, um zu verstehen, wie die Normalordnung implementiert ist.

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

Die Protokollmethode OperatorTrait.normal_ordered() lässt ihre Positions- und Schlüsselwortargumente bewusst unspezifiziert, sodass konkrete Operator-Implementierungen anpassbare Parameter definieren können, die die genaue erzeugte kanonische Form steuern. Das ermöglicht operatorspezifische Optimierungen und Normalform-Varianten, die auf ihre Algebra oder ihren Anwendungsfall zugeschnitten sind. Sieh in der API-Dokumentation deines Operatortyps nach, welche Parameter verfügbar sind.