Skip to main content
IBM Quantum Platform

Conditions relatives aux opérateurs de groupe : utiliser la structure des opérateurs

Le regroupement est l'une des opérations relevant de qiskit_fermions.operators.terms, qui regroupe les routines permettant de partitionner les termes d'un opérateur en fonction de leur structure.

Comme indiqué dans le guide de l'opérateur, les opérateurs peuvent stocker un tableau de groupes, élément facultatif de la structure de données clairsemée qui associe chaque terme à un index de groupe. Les termes partageant le même index de groupe forment un groupe. Cela vous permet d'exploiter systématiquement cette structure dans les étapes de traitement en aval.

En regroupant des termes apparentés (que ce soit en fonction de leurs propriétés physiques, de leurs relations algébriques ou de symétries propres à un problème), vous pouvez bénéficier de plusieurs avantages :

  • Synthèse optimisée des circuits : les termes regroupés transmettent des informations spécifiques au problème tout au long de la pile, ce qui permet une optimisation plus simple des circuits synthétisés. Cela se traduit par une réduction de la profondeur du circuit et du nombre de grilles.
  • Préservation de la structure physique : les groupes codent la structure significative issue du problème d'origine (par exemple, des modèles d'interaction ou des symétries) qui peut être utilisée pour des décompositions plus significatives d'un point de vue physique.
  • Sélection d'ensembles de flux : le regroupement permet naturellement de coder des ensembles de flux et d'autres structures issues de la théorie des graphes pertinentes pour la simulation quantique, comme le montre [1].
  • Amélioration de la stabilité algorithmique : comme le montre la routine « SqDRIFT », certains algorithmes peuvent bénéficier d’une stabilité ou de performances accrues grâce à la préservation des propriétés physiques.
  • Facteurs liés à la formule du produit : une formule de produit fermionique décompose un opérateur en ses différents groupes; le regroupement détermine donc à la fois le coût et l'erreur de Trotter d'une évolution temporelle. Voir « Synthèse d’une porte d’évolution dans l’espace fermionique ».

Utilisation

La manière la plus simple de définir un regroupement d'opérateurs consiste à définir l'attribut groups de l'opérateur cible. Vous trouverez ci-dessous un exemple de regroupement de termes selon les ensembles de flux de lignes, comme illustré à la figure 1c de [1].

Conseil

Ce module qiskit_fermions.operators.terms.grouping propose des méthodes pratiques permettant d'identifier automatiquement la structure d'un opérateur et d'attribuer les indices de groupe en conséquence.

>>> 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>
Un graphe orienté simple sur lequel définir une « MajoranaOperator ».

Il s'agit d'un simple réseau de deux colonnes sur trois lignes de modes de Majorana reliés par des arêtes orientées. Chaque arête représente un terme de l'opérateur agissant sur les deux modes connectés. L'objectif est de regrouper ces termes en fonction des étiquettes de leurs arêtes (indiquées sur le graphe). Les termes reliés par des arêtes portant la même étiquette sont placés dans le même groupe. Le code suivant montre comment procéder :

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

Analyser un regroupement

Les groupes ne leur attribuent aucune signification propre : ils répertorient simplement les termes qui vont ensemble, sans jamais expliquer pourquoi. Aucune routine de cette bibliothèque n'indique si un regroupement est « correct », car il n'existe pas de notion unique de « correction » à laquelle se référer. Les consommateurs en aval présentent toutefois les propriétés d'un regroupement, et les fonctions de cette section vous permettent de vérifier cette hypothèse dès le départ, plutôt que d'en payer le prix à chaque appel.

La convention la plus courante veut que chaque groupe soit hermitien en soi. C'est précisément ce qui rend possibles les formules de produit aléatoire fermionique : une « ap†aqa^\dagger_p a_q » isolée, avec p≠qp \neq q, n'est pas hermitienne; par conséquent, son évolution temporelle n'est pas unitaire et elle ne peut pas être échantillonnée isolément. En l'associant à aq†apa^\dagger_q a_p, on obtient un groupe qui peut être.

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

Un seul index de groupe par trimestre est autorisé

Le tableau « groups » contient un index par terme, et l'affectation d'un index d'une longueur différente provoque une exception. Aucune étape en aval ne vérifie à nouveau l'invariant; celui-ci est donc appliqué au moment où les indices sont transmis à l'opérateur : un tableau trop court omettrait silencieusement les termes finaux, tandis qu'un tableau trop long créerait des groupes que nul terme ne contient.

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

Il est None toujours autorisé de vider les index de groupe en leur attribuant la valeur.

Ces deux vérifications sont indépendantes l’une de l’autre

groups_have_uniform_coeffs() indique si les coefficients d'un groupe concordent, ce qui correspond à l'hypothèse sur laquelle group_coeff_means() se fonde lorsqu'elle calcule la moyenne des valeurs absolues. Il ne s'agit pas d'une forme affaiblie du test d'hermiticité : aucun des deux tests n'implique l'autre, dans un sens comme dans l'autre.

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

Par défaut, ce sont les grandeurs qui sont comparées, car c'est ce qui correspond à la « group_coeff_means() moyenne ». Une paire de conjugués hermitiens à coefficients complexes présente des amplitudes égales mais des coefficients inégaux; elle ne satisfait donc qu'à la forme par défaut.

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

groups_are_hermitian() hérite de la garantie unilatérale suivante is_hermitian(): une True entrée est toujours fiable, tandis qu'une False entrée n'est fiable que pour les types d'opérateurs dont la forme normale est une forme canonique véritable. Consultez la rubrique consacrée au type d'opérateur concerné pour savoir lequel s'applique.

[1] ( 1, 2 )

A. Gandon et al., Simulation quantique de la dynamique des fermions à l'aide de stabilisateurs avec codage local des qubits, arXiv:2512.11418v2.

Cette page a-t-elle été utile ?
Signaler un bogue, une coquille ou proposer du contenu sur GitHub.