Aller au contenu principal
Ce projet est en phase bêta. Tu as trouvé un bug ? Ouvrir un ticket GitHub. Une question ou une idée ? Pose ta question dans Discussions — tes retours sont les bienvenus !
Quelle est la qualité de la traduction sur cette page ?

Principes de conception des représentations d'opérateurs

Ce guide explique les principes de conception communs et les concepts fondamentaux partagés par toutes les représentations d'opérateurs du module operators.

Vue d'ensemble​

Les représentations d'opérateurs fournies par ce module partagent plusieurs principes de conception fondamentaux :

  • Structure de données creuse : les opérateurs n'encodent que les opérations non triviales. La disposition interne des données s'inspire généralement des formats de matrices creuses, ce qui permet un stockage et un calcul efficaces pour des systèmes avec de nombreux modes mais relativement peu de contributions significatives.

  • Itération sur les termes et reconstruction : indépendamment du stockage creux interne, les opérateurs offrent une interface d'itération cohérente, de sorte que tu peux inspecter, filtrer et transformer les termes sans comprendre la structure de données sous-jacente, puis reconstruire de nouveaux opérateurs à partir des termes modifiés.

  • Indexation par modes : les opérateurs utilisent des indices de modes abstraits pour étiqueter les degrés de liberté fermioniques, ce qui permet une correspondance flexible entre les systèmes physiques et la représentation de l'opérateur.

  • Regroupement des termes et relations de commutation : les opérateurs prennent nativement en charge des informations de regroupement qui associent les termes à des indices de groupe. Cela permet des optimisations et la préservation de la structure physique sans structures de données séparées. Consulte le guide sur le regroupement pour un usage pratique.

  • Opérations arithmétiques et mathématiques : tous les opérateurs implémentent un ensemble cohérent d'opérations arithmétiques (comme l'addition, la multiplication et la composition) et de fonctions mathématiques via le protocole OperatorTrait, ce qui permet d'écrire un code uniforme pour différents types d'opérateurs.

  • Ordonnancement des termes d'opérateur et formes normales : des opérateurs mathématiquement équivalents peuvent avoir des représentations et des comportements très différents dans les algorithmes quantiques. Toutes les représentations d'opérateurs prennent en charge diverses formes normales (fondées sur les relations de commutation propres à chaque algèbre) afin d'obtenir des représentations d'opérateurs canoniques, prévisibles et optimisables.

Structure de données creuse​

Tous les opérateurs utilisent une représentation creuse où seules les opérations non triviales sont suivies. Chaque opération non triviale se compose d'un coefficient (nombre complexe) et d'une séquence d'actions sur des modes précis.

Cette approche réduit considérablement l'utilisation de la mémoire et le temps de calcul, surtout pour les systèmes avec de nombreux modes mais relativement peu de contributions significatives. En n'encodant que les opérations non triviales, les opérations se concentrent sur l'essentiel, ce qui permet de travailler avec de grands systèmes qui seraient irréalisables avec des représentations denses. De plus, les opérateurs passent naturellement à l'échelle pour n'importe quel nombre de modes ; un opérateur agissant sur les modes {0, 1} fonctionne sans changement dans des systèmes comptant beaucoup plus de modes, puisque les modes non affectés sont implicitement l'identité.

Les termes identiques sont conservés séparément lors des opérations arithmétiques et doivent être combinés explicitement si nécessaire.

Format de stockage interne​

En interne, les opérateurs sont stockés dans des tableaux inspirés des formats de matrices creuses :

  • Tableau des coefficients : le coefficient complexe de chaque terme

  • Tableau des indices de modes : les modes fermioniques sur lesquels agit chaque action

  • Tableau des frontières : des indices marquant où les modes de chaque terme commencent et finissent dans le tableau des modes

Important

D'autres tableaux peuvent être présents selon le type d'opérateur. Par exemple, les instances de FermionOperator incluent un tableau des actions de booléens qui précise le type d'action fermionique agissant sur l'indice de mode correspondant. En revanche, la classe MajoranaOperator n'a pas besoin de cette distinction, car elle encode cette information dans la parité de l'indice de mode. Consulte la documentation de l'API pour ton type d'opérateur afin de comprendre le format de stockage complet.

Les exemples suivants montrent comment ces tableaux sont organisés. Le premier est une construction directe à l'aide des tableaux creux :

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

Méthodes de construction pratiques​

Pour les développeurs Python, plusieurs méthodes de construction pratiques sont disponibles et masquent les détails du stockage creux. Elles facilitent la construction d'opérateurs sans avoir à gérer les tableaux de coefficients, de modes et de limites :

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

Les implémentations individuelles d'opérateurs peuvent proposer des méthodes de construction supplémentaires adaptées à leur cas d'usage spécifique. Consulte la documentation de l'API de ton type d'opérateur pour voir toutes les options de construction disponibles.

Itération sur les termes et reconstruction​

Les opérateurs fournissent une interface d'itération cohérente grâce à OperatorTrait.iter_terms(), quelle que soit leur représentation creuse interne. Tu peux donc inspecter, filtrer ou transformer les termes sans avoir besoin de comprendre la structure de données sous-jacente. Tu peux ensuite reconstruire un nouvel opérateur à partir des termes transformés grâce à OperatorTrait.from_terms().

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

Indexation par modes​

Toutes les représentations d'opérateurs désignent les indices sur lesquels agissent leurs termes comme des modes. Un mode est simplement un indice qui identifie un degré de liberté fermionique de ton système. La correspondance entre les degrés de liberté physiques (comme les orbitales spatiales, les états de spin ou d'autres nombres quantiques) et les indices de modes est laissée à l'utilisateur, ce qui offre une flexibilité maximale.

Cette abstraction est également présente dans le module qiskit_fermions.circuit, où le FermionicCircuit opère sur un registre de modes fermioniques. Dans les représentations d'opérateurs comme de circuits, les modes offrent un moyen cohérent et indépendant de l'algèbre de préciser quels degrés de liberté participent à une opération donnée.

Important

Les implémentations actuelles utilisent des modes sans spin : toutes les représentations d'opérateurs actuellement fournies par ce module traitent les modes comme des degrés de liberté fermioniques sans spin. Cela signifie que si ton système contient à la fois des électrons ou des fermions de spin haut et de spin bas, tu dois les associer explicitement à des modes distincts (par exemple, les modes 0 à 3 pour le spin haut de quatre orbitales spatiales, les modes 4 à 7 pour le spin bas, ou toute autre convention de ton choix).

Cette conception garde les représentations de base simples et générales tout en évitant d'imposer une convention particulière d'ordonnancement des spins. Les modules utilitaires comme qiskit_fermions.operators.library fournissent des fonctions pratiques (par exemple, FCIDump.from_file()) qui gèrent ces correspondances à ta place lors du chargement de données de structure électronique.

Astuce

Au fil de l'évolution du paquet, des représentations d'opérateurs avec spin pourraient être ajoutées, prenant en charge nativement les degrés de liberté de spin dans leur modèle de données. Elles seront clairement distinguées des implémentations actuelles sans spin et coexisteront avec elles dans le module.

Regroupement des termes et relations de commutation​

Comme les coefficients et les indices de modes, les opérateurs peuvent éventuellement stocker un tableau de groupes qui associe chaque terme à un indice de groupe. En intégrant le regroupement directement dans la représentation de l'opérateur, au sein de la structure de données creuse, l'information de regroupement accompagne naturellement l'opérateur au fil des transformations. Cela permet d'exploiter systématiquement la structure, qu'elle provienne de propriétés physiques, de relations algébriques ou de symétries propres au problème. Cette information structurée peut ensuite servir dans des opérations en aval comme la synthèse et la décomposition de circuits, grâce à des méthodes comme OperatorTrait.split_out_groups().

Pour des indications détaillées sur la façon de regrouper les termes d'opérateurs dans tes workflows, consulte le guide regroupement.

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

Opérations arithmétiques et mathématiques​

Tous les opérateurs implémentent le protocole OperatorTrait, qui fournit un ensemble unifié d'opérations pour les différents types d'opérateurs. Cela garantit que le code écrit pour une représentation d'opérateur fonctionne de la même manière avec les autres. Consulte qiskit_fermions.protocols pour voir comment ce protocole se rapporte aux autres définis par ce paquet.

Le protocole comprend des opérations arithmétiques (comme l'addition, la multiplication et la composition), des opérations structurelles (itération sur les termes, analyse du support des modes, réétiquetage), des fonctions mathématiques (ordre normal, simplification, vérification d'équivalence), et plus encore. Consulte la documentation de OperatorTrait pour une référence complète de toutes les opérations disponibles.

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

is_hermitian() vérifie que H−H†=0H - H^\dagger = 0 en réduisant cette différence à une forme normale et en la comparant à zero(). Détaillée en termes des autres opérations du protocole, elle effectue les étapes suivantes :

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

La force de cette vérification dépend du type d'opérateur, car elle dépend de la complétude avec laquelle la forme normale de ce type contracte les générateurs adjacents. Un résultat True est toujours fiable, mais pour certains types un résultat False est prudent : consulte is_hermitian() pour plus de détails.

Important

L'exemple utilise atol=1e-10. Le paramètre atol (tolérance absolue) précise un seuil. Les coefficients dont la magnitude est inférieure à atol sont traités comme nuls et supprimés. C'est essentiel pour la stabilité numérique lors de la comparaison d'opérateurs, car l'arithmétique à virgule flottante peut introduire de petites erreurs d'arrondi qui empêcheraient sinon de reconnaître des opérateurs équivalents comme tels.

Astuce

Bien que le protocole OperatorTrait fournisse une interface commune, les implémentations individuelles d'opérateurs peuvent offrir des méthodes pratiques supplémentaires qui ne font pas partie du protocole. Par exemple, FermionOperator et MajoranaOperator fournissent une méthode max_rank() qui indique la longueur de leur terme le plus long. Consulte toujours la documentation de l'API de ton type d'opérateur pour découvrir toutes les fonctionnalités disponibles.

Ordonnancement des termes d'un opérateur et formes normales​

Un défi fondamental de l'algèbre des opérateurs quantiques est que des opérateurs mathématiquement équivalents peuvent être représentés de nombreuses façons différentes, chacune ayant des implications différentes pour les algorithmes quantiques. Un même opérateur peut s'écrire sous des formes algébriquement équivalentes (par exemple, a†ba^\dagger b peut s'exprimer comme ba†+[a†,b]ba^\dagger + [a^\dagger,b]), et pourtant ces représentations conduisent à des comportements différents dans la synthèse de circuits, la simplification et les algorithmes numériques.

Les représentations d'opérateurs prennent en charge des opérations d'ordre normal qui transforment les opérateurs en formes canoniques propres à chaque algèbre en utilisant les relations de commutation. Cela permet des comparaisons fiables (deux opérateurs équivalents ont des formes en ordre normal identiques), révèle des simplifications (les relations de commutation font s'annuler ou se combiner des termes) et prend en charge les algorithmes qui exigent des formes d'opérateurs spécifiques pour leur exactitude et leur efficacité.

Important

Différentes représentations d'opérateurs peuvent implémenter l'ordre normal à partir de relations de commutation différentes, adaptées à leur algèbre. Par exemple, l'ordre normal fermionique utilise des relations d'anticommutation ({ci,cj†}=δij\{c_i, c_j^\dagger\} = \delta_{ij}), tandis que l'ordre normal de Majorana utilise d'autres conventions algébriques ({γi,γj}=2δij\{\gamma_i, \gamma_j\} = 2\delta_{ij}). Consulte toujours la documentation de ton type d'opérateur pour comprendre comment l'ordre normal est implémenté.

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

La méthode de protocole OperatorTrait.normal_ordered() laisse volontairement non spécifiés ses arguments positionnels et nommés, ce qui permet aux implémentations concrètes d'opérateurs de définir des paramètres ajustables qui contrôlent la forme canonique exacte produite. Cela permet des optimisations propres à chaque opérateur et des variantes de formes normales adaptées à leur algèbre ou à leur cas d'usage. Consulte la documentation de l'API de ton type d'opérateur pour voir quels paramètres sont disponibles.