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

Ein BosonOperator kann jeden seiner Terme mit einem Gruppenindex versehen. Die Markierungen haben keine eigene Bedeutung: Sie sagen nur, welche Terme zusammengehören. Was die Markierungen bedeuten, entscheidet, was sie verwendet. In diesem Paket sind die Verbraucher das Zeitentwicklungs-Gate, das in einen Faktor pro Gruppe aufgeteilt wird, und der randomisierte qDRIFT-Pass, der ganze Gruppen samplet.

Diese Anleitung zeigt, wie du Gruppen zuweist, wie du die Eigenschaften prüfst, auf die sich ein Verbraucher verlässt, und wie du den Operator so anordnest, dass es günstig bleibt, wenige Gruppen aus einer großen herauszugreifen.

Hinweis

Dieses Paket liefert keine automatische Gruppierungsstrategie mit. Du weist die Indizes selbst zu, anhand dessen, was du über dein Modell weißt.

Gruppen zuweisen​

Das durchgehende Beispiel ist eine Bose-Hubbard-Kette mit drei Plätzen. Jede Bindung liefert ein Hopping-Paar, und die Wechselwirkungen am Platz bilden einen diagonalen Teil. Der einfachste Weg, einen gruppierten Operator zu bauen, ist from_terms_with_groups(), das jeden Term zusammen mit seinem Gruppenindex als Tripel (actions, coefficient, group) entgegennimmt. Du entscheidest die Gruppe in dem Moment, in dem du den Term aufschreibst, wenn du noch weißt, warum er dorthin gehört:

>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>>
>>> bonds = [(0, 1), (1, 2)]
>>> terms = []
>>> for group, (left, right) in enumerate(bonds): # one group per bond
... terms.append(([cre(left), ann(right)], -1.0, group))
... terms.append(([cre(right), ann(left)], -1.0, group))
>>> for site in range(3): # the on-site interactions share one group
... terms.append(([cre(site), cre(site), ann(site), ann(site)], 1.0, len(bonds)))
>>> hamiltonian = BosonOperator.from_terms_with_groups(terms)
>>> len(hamiltonian), hamiltonian.num_groups()
(7, 3)

Die Terme werden in der Reihenfolge gespeichert, in der du sie angibst, sodass die Gruppenindizes nicht an den falschen Termen landen können.

Ein bereits vorhandener Operator muss nachträglich gruppiert werden, indem du das Attribut groups zuweist. Hier ist Sorgfalt nötig, weil from_dict() die Reihenfolge, in der es seine Terme speichert, nicht festlegt. Die Liste nach Position auszuschreiben, kann dann einen Index an den falschen Term hängen. Leite stattdessen jeden Index aus dem Term selbst ab:

>>> existing = BosonOperator.from_dict({tuple(actions): coeff for actions, coeff, _ in terms})
>>> existing.groups = [
... len(bonds)
... if len({mode for _, mode in term}) == 1
... else bonds.index(tuple(sorted({mode for _, mode in term})))
... for term, _ in existing.iter_terms()
... ]
>>> existing.num_groups(), existing.equiv(hamiltonian)
(3, True)

Die Anzahl der Indizes muss zur Anzahl der Terme passen. Alles andere wird abgelehnt, weil eine zu kurze Liste überall dort, wo Terme zusammen mit ihren Gruppen durchlaufen werden, stillschweigend nachfolgende Terme verlieren würde:

>>> try:
... hamiltonian.groups = [0, 1]
... except ValueError as error:
... print(error)
expected one group index per term, but got 2 for 7 terms

Prüfen, worauf sich der Verbraucher verlässt​

Drei Funktionen in qiskit_bosons.operators.terms beantworten jeweils eine enge Frage zu einer bestehenden Gruppierung. Keine von ihnen sagt, dass eine Gruppierung „korrekt“ ist, denn Korrektheit ist relativ zum Verbraucher.

Ist jede Gruppe hermitesch? Das ist die wichtigste Eigenschaft. Die Zeitentwicklung einer Gruppe ist nur unitär, wenn die Gruppe hermitesch ist, und ein einzelner Hopping-Term bi†bjb^\dagger_i b_j ist es nicht. Nur das Paar mit seinem Adjungierten ist hermitesch:

>>> from qiskit_bosons.operators.terms import groups_are_hermitian
>>> groups_are_hermitian(hamiltonian)
[True, True, True]

Eine Gruppierung, die ein Hopping-Paar trennt, wird sofort erkannt:

>>> split = BosonOperator.from_terms_with_groups(
... [(actions, coeff, index) for index, (actions, coeff, _) in enumerate(terms)]
... ) # one term per group
>>> [bool(flag) for flag in groups_are_hermitian(split)]
[False, False, False, False, True, True, True]

Die vier Hopping-Terme werden erfasst. Die drei True-Einträge sind die On-Site-Terme bi†bi†bibib^\dagger_i b^\dagger_i b_i b_i, die für sich genommen hermitesch sind. Ein Modus darf innerhalb eines Terms legitim wiederholt vorkommen, und dafür wird nichts verworfen. Die Flags folgen der Reihenfolge von terms, weshalb sich jedes einzelne mit dem Term vergleichen lässt, den es beschreibt.

Sind die Koeffizienten einheitlich? Eine randomisierte Produktformel gewichtet eine Gruppe mit dem Mittelwert der Beträge ihrer Koeffizienten, was die Gruppe nur dann treffend beschreibt, wenn die Beträge übereinstimmen:

>>> from qiskit_bosons.operators.terms import group_coeff_means, groups_have_uniform_coeffs
>>> groups_have_uniform_coeffs(hamiltonian)
[True, True, True]
>>> group_coeff_means(hamiltonian)
[1.0, 1.0, 1.0]

Keine der beiden Prüfungen impliziert die andere: Einheitliche Koeffizienten machen eine Gruppe nicht hermitesch, und eine hermitesche Gruppe kann unterschiedliche Beträge mischen. Standardmäßig werden die Beträge verglichen, weil group_coeff_means() genau diese mittelt. Ein hermitesches Paar mit komplexen Koeffizienten hat gleiche Beträge, aber ungleiche Koeffizienten, und erfüllt daher nur die Standardform:

>>> conjugate = BosonOperator.from_terms_with_groups(
... [([cre(0), ann(1)], 1.0j, 0), ([cre(1), ann(0)], -1.0j, 0)]
... )
>>> groups_have_uniform_coeffs(conjugate)
[True]
>>> groups_have_uniform_coeffs(conjugate, abs=False)
[False]
Hinweis

groups_are_hermitian() basiert auf is_hermitian(), das einen Operator nach der Normalordnung mit seinem Adjungierten vergleicht. Das ist eine Identität der nicht abgeschnittenen Algebra, daher ist eine Gruppe, die die Prüfung besteht, als bosonischer Operator hermitesch. Unter Entwurfsprinzipien der Operatordarstellungen erfährst du, wie Produkte von Leiteroperatoren geordnet und verglichen werden.

Die Terme nach Gruppen ordnen​

split_out_groups() gibt einen Operator pro angeforderter Gruppe zurück. Wenn die Terme einer Gruppe über den Operator verstreut sind, muss die Methode jeden Term durchsuchen, um sie zu finden. Bildet jede Gruppe einen zusammenhängenden Abschnitt, sucht sie stattdessen per Binärsuche nach den Gruppengrenzen, sodass die Kosten einer Abfrage von der Anzahl der angeforderten Gruppen abhängen und nicht von der Anzahl der gespeicherten Terme.

group_order() erzeugt dieses Layout. Es ist eine stabile Sortierung nach Gruppenindex: Der Wert des Operators ändert sich nicht, und die Terme innerhalb einer Gruppe behalten ihre relative Reihenfolge.

>>> from qiskit_bosons.operators.terms import group_order
>>> scattered = BosonOperator.from_terms_with_groups(
... [(actions, coeff, group) for (actions, coeff, _), group in zip(terms, [1, 0, 1, 0, 2, 2, 2])]
... )
>>> ordered = group_order(scattered)
>>> ordered.groups
[0, 0, 1, 1, 2, 2, 2]
>>> ordered.equiv(scattered)
True

Beide Layouts liefern dieselben Operatoren; nur die Kosten unterscheiden sich:

>>> request = [2, 0]
>>> all(
... a.equiv(b)
... for a, b in zip(
... scattered.split_out_groups(request), ordered.split_out_groups(request), strict=True
... )
... )
True

Nächste Schritte​

Die Gruppierung wird hier auf bosonischer Ebene gewählt, solange die Struktur des Modells noch sichtbar ist.