Skip to main content
IBM Quantum Platform

Modèle de circuit quantique

qiskit.circuit

L'élément fondamental de l'informatique quantique est le circuit quantique. Il s'agit d'une routine de calcul qui peut être exécutée, un coup à la fois, sur une unité de traitement quantique (QPU). Un circuit agit sur une quantité prédéfinie de données quantiques (dans Qiskit, nous ne prenons en charge que les qubits) avec des opérations unitaires (portes), des mesures et des réinitialisations. En outre, un circuit quantique peut contenir des opérations sur des données classiques, y compris des calculs en temps réel et des constructions de flux de contrôle, qui sont exécutés par les contrôleurs de la QPU.

Les circuits se situent à un niveau d'abstraction peu élevé lors de l'élaboration de programmes quantiques. Ils constituent la construction utilisée pour atteindre des niveaux d'abstraction plus élevés, tels que les primitives de l'informatique quantique, qui accumulent des données provenant de nombreuses exécutions de circuits quantiques, ainsi que des techniques avancées d'atténuation des erreurs et d'optimisation des mesures, dans des données classiques bien typées et des statistiques d'erreur.

Dans Qiskit, les circuits peuvent être définis dans l'un des deux régimes suivants :

  • un circuit abstrait, défini en termes de qubits virtuels et d'opérations arbitraires de haut niveau, comme des algorithmes encapsulés et des portes définies par l'utilisateur.
  • un circuit physique, qui est défini en termes de qubits matériels d'un backend particulier, et qui contient uniquement des opérations que ce backend prend en charge de manière native. Ce concept est également appelé circuit ISA.

Vous passez d'un circuit abstrait à un circuit physique en utilisant le paquetage de transpilation de Qiskit, dont le point d'accès de premier niveau est transpile(). Si vous définissez un circuit, où vous voulez que les indices des qubits fassent référence à des qubits physiques, vous pouvez utiliser QuantumCircuit.ensure_physical() pour réécrire les métadonnées du circuit afin de s'assurer que Qiskit reconnaisse le circuit comme un circuit physique, bien que contrairement à la transpilation, cela n'implique pas que les contraintes de portes de base et de couplage matériel soient respectées.

Dans Qiskit, un circuit quantique est représenté par la classe QuantumCircuit classe. Voici un exemple de circuit quantique qui produit un état de Greenberger-Horne-Zeilinger (GHZ) à trois qubits, défini comme suit :

∣ψ⟩=(∣000⟩+∣111⟩)/2|\psi\rangle = \left( |000\rangle + |111\rangle \right) / \sqrt{2}
from qiskit import QuantumCircuit

# Create a circuit with a register of three qubits
circ = QuantumCircuit(3)
# H gate on qubit 0, putting this qubit in a superposition of |0> + |1>.
circ.h(0)
# A CX (CNOT) gate on control qubit 0 and target qubit 1 generating a Bell state.
circ.cx(0, 1)
# CX (CNOT) gate on control qubit 0 and target qubit 2 resulting in a GHZ state.
circ.cx(0, 2)
# Draw the circuit
circ.draw('mpl')
Schéma de circuit produit par le code précédent.

Concepts et définitions relatifs aux circuits

Les circuits quantiques font l'objet d'une terminologie très spécialisée. La plupart de ces éléments sont communs à la littérature sur l'informatique quantique, tandis que d'autres sont plus spécifiques aux progiciels quantiques, et une petite partie est spécifique à Qiskit. Il s'agit d'une liste alphabétique de certains des concepts importants, à titre de référence rapide, mais qui n'entre pas dans le détail des concepts fondamentaux. Envisagez d'utiliser la plateforme IBM Quantum Learning si vous souhaitez commencer par le début.

circuit abstrait

Un circuit défini en termes d'opérations mathématiques abstraites et de qubits virtuels. Un circuit abstrait peut être constitué d'opérations unitaires complètement arbitraires, de mesures et potentiellement de calculs classiques en temps réel, sans aucune restriction quant aux qubits qui peuvent interagir entre eux.

Vous transformez un circuit abstrait en un circuit physique en utilisant le package de transpilation de Qiskit.

ancilla qubit

Un qubit supplémentaire qui est utilisé pour aider à mettre en œuvre des opérations sur d'autres qubits, mais dont l'état final n'est pas important pour le programme.

circuit

Une routine de calcul qui définit une exécution unique à effectuer sur une QPU. Il peut s'agir d'un circuit abstrait ou d'un circuit physique.

clbit

Abréviation spécifique à Qiskit signifiant un seul bit classique de données.

porte

Opération unitaire sur un ou plusieurs qubits.

qubit matériel

La représentation d'un seul qubit sur une QPU particulière. Un qubit matériel est soutenu par un système physique de mécanique quantique, tel que des circuits supraconducteurs; contrairement à un qubit virtuel, il est soumis à des contraintes de couplage particulières et seules certaines portes peuvent être appliquées à certains groupes de qubits matériels.

Qiskit ne distingue pas les qubits logiques des qubits physiques individuels lorsqu'il parle de qubits matériels. Une QPU peut mettre en œuvre ses qubits matériels sous forme de qubits logiques, où chaque qubit matériel comprend de nombreux qubits physiques qui sont contrôlés et corrigés des erreurs de manière opaque pour Qiskit par l'électronique de contrôle. Plus vraisemblablement, pour les applications à court terme, une QPU exposera directement ses qubits physiques en tant que qubits matériels sur lesquels Qiskit pourra raisonner.

Les qubits physiques et logiques auront des contraintes de couplage entre eux, n'autoriseront que certaines opérations quantiques sur eux et auront des problèmes de programmation entre eux. Qiskit résume ces préoccupations dans le concept de qubits matériels. Dans les premiers temps de la correction quantique des erreurs, certains backends peuvent vous permettre d'accéder à leurs ressources de qubits, soit sous forme de qubits logiques de haut niveau, soit sous forme de qubits physiques de bas niveau, par l'intermédiaire de Qiskit.

l'architecture du jeu d'instructions (ISA)

Modèle abstrait indiquant quelles opérations sont disponibles sur quels ensembles de qubits matériels dans une QPU donnée. Par exemple, une QPU peut autoriser les opérations X\sqrt X et RZR_Z sur tous les qubits matériels uniques, et les opérations CXCX sur certaines paires de qubits matériels.

qubit logique

Une collection de plusieurs qubits physiques qui sont contrôlés ensemble par une QPU (du point de vue de l'utilisateur) pour appliquer une correction d'erreur quantique en temps réel. Un qubit logique est un type de qubit matériel pour Qiskit.

mesure

L'extraction d'un bit classique d'une donnée à partir d'un état de qubit unique. Il s'agit d'une opération irréversible qui détruit généralement l'intrication et la cohérence de phase entre le qubit cible et le reste du système.

circuit physique

Un circuit défini en termes de qubits matériels et uniquement les opérations quantiques disponibles dans l' ISA d'une QPU particulière. Les circuits physiques sont liés à une architecture QPU particulière et ne fonctionneront pas sur d'autres architectures incompatibles. Vous pouvez également entendre parler d'un circuit ISA.

Vous obtenez généralement un circuit physique en utilisant les routines de transpilation de Qiskit sur un circuit abstrait que vous avez construit.

qubit physique

Un système quantique contrôlable à deux niveaux. Il s'agit littéralement d'un qubit "physique", tel qu'un transmon ou l'état électronique d'un ion piégé. Une QPU peut l'exposer directement en tant que qubit matériel, ou combiner plusieurs qubits physiques en un qubit logique.

unité de traitement quantique (QPU)

Analogue à un processeur dans l'informatique classique ou à un GPU dans le traitement graphique, une QPU est le matériel qui exécute des opérations quantiques sur des données quantiques. On peut toujours s'attendre à ce qu'une QPU qui utilise le modèle de circuit de calcul soit capable d'effectuer un certain nombre de portes et d'opérations de mesure. En fonction de la technologie utilisée, ils peuvent également être en mesure d'effectuer certains calculs classiques en temps réel, tels que le flux de contrôle classique et les calculs bit à bit sur des données classiques.

bit quantique

L'unité de base de l'information quantique.

calcul classique en temps réel

Tout calcul classique qui peut se produire au cours de l'exécution d'un seul plan d'un circuit, où les résultats du calcul classique peuvent affecter l'exécution ultérieure du circuit. La quantité de calcul classique en temps réel disponible avec des QPU particulières variera considérablement en fonction de nombreux facteurs, tels que l'électronique de commande et la technologie des qubits utilisée. Vous devez consulter la documentation de votre fournisseur de matériel pour plus d'informations à ce sujet.

opération unitaire

Opération réversible sur un état quantique. Toutes les portes quantiques sont des opérations unitaires (par définition).

qubit virtuel

Un qubit mathématique abstrait utilisé pour construire un circuit abstrait. Les qubits virtuels sont la façon dont on envisage généralement les algorithmes quantiques à un niveau élevé; nous supposons que toutes les portes quantiques sont valables sur tous les qubits virtuels et que tous les qubits virtuels sont toujours connectés à tous les autres qubits virtuels.

Lors de la mise en correspondance avec le matériel, les qubits virtuels doivent être assignés aux qubits matériels. Cette correspondance n'est pas nécessairement biunivoque. Typiquement, un qubit virtuel devra être échangé d'un qubit matériel à un autre au cours de l'exécution d'un circuit afin de satisfaire aux contraintes de couplage de la QPU sous-jacente. Il n'est pas strictement nécessaire que tous les qubits virtuels utilisés dans un circuit soient mis en correspondance avec un qubit physique à un moment donné dans un circuit physique; il se peut qu'un qubit virtuel soit mesuré (effondrement de son état) et qu'il ne soit plus jamais utilisé, de sorte qu'un nouveau qubit virtuel pourrait prendre sa place. L'évaluation de ces conditions pour faire correspondre un circuit virtuel à un circuit physique est la tâche du logiciel de transpilation de Qiskit.


Présentation de l'API d' qiskit.circuit

Tous les objets présentés ici sont décrits plus en détail et dans leur contexte dans les sections suivantes. Cette section présente une vue d'ensemble des éléments de l'API documentés ici.

La classe principale est QuantumCircuitqui possède sa propre page de documentation, y compris une section approfondie sur la construction de circuits. Les données quantiques et les données classiques les plus simples sont représentées par des "bits" et des "registres" :

Au sein d'un circuit, chaque instruction complète CircuitInstruction est composée d'un Operation (qui peut être un Instruction, un Gate, ou une autre sous-classe) et des opérandes qubit et clbit. Les classes de base principales sont les suivantes :

La sous-classe concrète la plus courante de l'interface abstraite Operation minimale est la Instruction. Bien qu'un Operation puisse inclure des objets mathématiques abstraits, un Instruction est quelque chose qui pourrait en théorie s'exécuter directement sur du matériel. Celles-ci sont à leur tour sous-classées par Gate et ControlledGate , qui y ajoutent respectivement l'unitarité et une sémantique contrôlée :

  • Instruction, représentant une instruction matérielle
  • Gate, représentant une instruction matérielle unitaire
  • ControlledGate, représentant une porte dotée d'une structure de commande.

Qiskit comprend une vaste bibliothèque de portes et de circuits standard, qui est documentée dans le document qiskit.circuit.library. Nombre d'entre eux sont déclarés comme Python -objet unique. Les mécanismes nécessaires à cet effet sont décrits en détail dans qiskit.circuit.singletondont les principales classes sont chacune une forme singleton de la hiérarchie standard des portes d'instruction :

Certaines instructions sont particulièrement spéciales en ce sens qu'elles affectent le flux de contrôle ou le flux de données du circuit. Les plus importants sont les suivants :

  • Barrierpour marquer les parties du circuit qui doivent être optimisées de manière indépendante

  • Delaypour insérer une période d'attente en temps réel

  • Measurepour mesurer un Qubit en a Clbit

  • Resetpour réinitialiser de manière irréversible un qubit à l'état ∣0⟩\lvert0\rangle

  • Storepour écrire une expression classique en temps réel dans un emplacement de stockage

  • ControlFlowOp, qui comporte des sous-classes spécifiques :

    • BreakLoopOp, pour sortir de la boucle de contenance la plus proche
    • ContinueLoopOp, pour passer immédiatement à l'itération suivante de la boucle englobante
    • BoxOp, un simple ensemble d'instructions
    • ForLoopOp, pour parcourir une plage fixe de valeurs
    • IfElseOp, pour accéder de manière conditionnelle à l'un des deux sous-circuits
    • SwitchCaseOp, pour accéder de manière conditionnelle à l'un des nombreux sous-circuits
    • WhileLoopOp, pour répéter un sous-circuit jusqu’à ce qu’une condition soit réfutée.

Certaines instructions peuvent être « annotées » à l'aide de métadonnées, qui sont généralement destinées à être utilisées par un passage de compilation, soit localement, soit lors d'un traitement ultérieur en arrière-plan. Pour l'instant, cela se limite à BoxOp. Ces annotations sont représentées par des sous-classes personnalisées de Annotation, et l'infrastructure de prise en charge est abordée plus en détail dans qiskit.circuit.annotation.

Les circuits peuvent inclure des expressions classiques qui sont évaluées en temps réel, tandis que la QPU exécute une seule image du circuit. Ils sont principalement décrits dans la documentation du module qiskit.circuit.classical. Vous pourriez être particulièrement intéressé par les classes de base (qui ne sont pas exposées à partir de la qiskit.circuit ) :

  • Var, un emplacement de stockage classique typé dans un circuit
  • Exprune expression évaluée en temps réel
  • Typele type classique d'une expression.

En plus de cette évaluation d'expression en temps réel, qui est limitée par les représentations matérielles classiques des données, Qiskit possède le concept de paramétrage "à la compilation", qui est effectué dans l'algèbre symbolique abstraite. Ils sont typiquement utilisés pour représenter les angles de porte dans les algorithmes de haut niveau qui pourraient vouloir effectuer des dérivées numériques, mais ils sont une partie plus ancienne de Qiskit que l'évaluation en temps réel, et sont donc encore utilisés à certains endroits pour faire de la paramétrisation générale. Les principales classes concernées sont les suivantes

Le module qiskit.circuit expose également quelques classes de calcul qui fonctionnent avec des circuits pour faciliter les processus de compilation. Exemples :

Il existe également des utilitaires permettant de générer des circuits aléatoires :

Enfin, le module circuit dispose de sa propre classe d'exceptions, pour indiquer quand les choses ont mal tourné de manière spécifique au circuit :


Représentation des circuits dans Qiskit

La principale classe utilisée par l'utilisateur pour représenter les circuits est QuantumCircuit. Il peut s'agir d'un circuit abstrait ou d'un circuit physique. Il y a beaucoup plus d'informations sur la classe QuantumCircuit et la multitude de méthodes disponibles dans la documentation de la classe.

En interne, un QuantumCircuit contient les qubits, les bits classiques, les paramètres de compilation, les variables en temps réel et d’autres informations de suivi concernant les données sur lesquelles il agit et la manière dont il est paramétré. Elle contient ensuite une séquence de CircuitInstructions, qui comprennent l'opération concernée (porte logique, mesure, etc.) et ses opérandes (les qubits et les bits classiques).

Bits et registres

Les Qubits et les bits classiques sont représentés par un type de base commun, qui n'est qu'une "étiquette de type" Bit qui n'est qu'une "étiquette de type"; les classes n'ont pas d'autre comportement que d'être des objets immuables :

Bit

class qiskit.circuit.Bit

GitHub

Bases : object

Mettre en œuvre un bit générique.

Remarque

Cette classe ne peut pas être instanciée directement. Son seul but est de permettre une vérification générique des types pour Clbit et Qubit.

Qubit

class qiskit.circuit.Qubit(register=None, index=None)

GitHub

Bases : Bit

Un qubit, qui peut être comparé entre différents circuits.

Clbit

class qiskit.circuit.Clbit(register=None, index=None)

GitHub

Bases : Bit

Un clbit, qui peut être comparé entre différents circuits.

Les Qubits et les clbits sont instanciés par les utilisateurs sans arguments, par exemple par Qubit(). Les bits se comparent entre eux s'il s'agit du même objet Python ou s'ils ont tous deux été créés par un registre de même nom et de même taille, et s'ils se réfèrent au même index dans ce registre. Il existe également une étiquette de type spécial pour les qubits "ancilla", mais elle est peu utilisée dans l'état actuel de Qiskit :

AncillaQubit

class qiskit.circuit.AncillaQubit

GitHub

Bases : Qubit

Un qubit utilisé comme ancilla.

Une collection de bits du même type peut être encapsulée dans un registre du type correspondant. La fonctionnalité de base se trouve dans une classe de base qui n'est pas directement instanciée :

Enregistrer

class qiskit.circuit.Register

GitHub

Bases : object

Mettre en œuvre un registre générique.

Remarque

Cette classe ne peut pas être instanciée directement. Son seul but est de permettre une vérification générique des types pour ClassicalRegister et QuantumRegister.

Chacun des sous-types de bits définis est associé à un registre, qui possède les mêmes signatures de constructeur, méthodes et propriétés que la classe de base :

QuantumRegister

class qiskit.circuit.QuantumRegister(size=None, name=None, bits=None)

GitHub

Bases : Register

Mettre en place un registre.

ClassicalRegister

class qiskit.circuit.ClassicalRegister(size=None, name=None, bits=None)

GitHub

Bases : Register

Mettre en place un registre.

AncillaRegister

class qiskit.circuit.AncillaRegister(size=None, name=None, bits=None)

GitHub

Bases : QuantumRegister

Mettre en place un registre d'ancillaire.

Une façon courante d'instancier plusieurs bits à la fois est de créer un registre, comme par exemple en QuantumRegister("my_qreg", 5). Cela présente l'avantage de pouvoir donner un nom à cette collection de bits, qui apparaîtra lors de la visualisation des circuits (QuantumCircuit.draw()) et les exportations vers les langages d'échange (voir qasm2 et qasm3). Il est également possible de transmettre un nom et une liste de bits préconstruits, mais cela crée un "registre d'aliasing", qui est très mal pris en charge par le matériel.

Les circuits suivent les registres, mais les registres eux-mêmes n'apportent pratiquement aucune différence de comportement aux circuits. La seule exception est que ClassicalRegisterpeuvent être implicitement transformés en entiers non signés pour être utilisés dans les comparaisons conditionnelles des opérations de flux de contrôle.

Les registres classiques et les bits étaient la manière originale de représenter les données classiques dans Qiskit, et restent la manière la plus supportée actuellement. À plus long terme, le modèle de données évolue vers une représentation plus complète et fortement typée d'une gamme de données classiques (voir Calcul classique en temps réel ), mais vous utiliserez encore très couramment des bits classiques dans Qiskit actuel.

Contextes d'enseignement

Le type scalaire de la QuantumCircuit.data séquence est l'objet « contexte d'instruction », CircuitInstruction. Il s'agit en substance d'une simple classe de données qui contient une représentation de ce qui doit être fait (son operation), ainsi que les données sur lesquelles elle agit (le qubits et clbitsle ).

Chronique « 1 »
Chronique « 2 »
CircuitInstruction(opération[, qubits, clbits] )Une instruction unique dans un QuantumCircuitest composée de l'adresse operation et de divers opérandes.

D'un point de vue programmatique, cette classe est en réalité implémentée en Rust et constitue un descripteur permettant d'accéder aux données internes de l'espace Rust. Les modifications apportées aux instances de cette classe ne seront pas répercutées dans le circuit. En règle générale, vous ne pouvez pas modifier directement les contextes d'instructions qui se trouvent déjà dans le circuit; l'interface QuantumCircuit est conçue pour le stockage et la construction de circuits, tandis que c'est au niveau du transpileur, de ses passes et de sa représentation intermédiaire DAGCircuit que vous devez chercher une interface permettant de modifier les circuits.

Les QuantumCircuit méthodes qui ajoutent des instructions aux circuits (telles que append(), ainsi que toutes les méthodes auxiliaires liées aux portes standard) renvoient un InstructionSet, qui sert de référence à plusieurs CircuitInstruction à la fois.

Chronique « 1 »
Chronique « 2 »
InstructionSet(*[, demandeur_de_ressource] )Collection d'instructions, et leurs contextes.

Cette fonctionnalité InstructionSet est désormais peu utilisée dans Qiskit. Il fournit un ensemble très restreint de méthodes permettant d'effectuer des modifications a posteriori sur les instructions (qui seront répercutées sur le circuit), mais leur utilisation est désormais déconseillée et vous devriez privilégier les alternatives indiquées dans ces méthodes.

Opérations, instructions et portes

Au sein d'un CircuitInstruction, l'interface minimale que toute opération doit respecter est Operation. Il s'agit d'une vue d'ensemble très générale, qui ne s'applique qu'aux circuits abstraits. L'objectif principal consistant à traiter les opérations comme Operation est de permettre d'ajouter directement des objets mathématiques arbitraires (tels que quantum_info.Operator) à des circuits abstraits.

Chronique « 1 »
Chronique « 2 »
Operation()Interface de fonctionnement quantique.

InstructionLa plupart des opérations, y compris toutes celles portant sur des circuits physiques, sont des cas particuliers de la notion définie de manière plus concrète. Il s'agit de toute instruction qu'un QPU pourrait être capable d'exécuter en mode natif, telle que Measure. Instruction n'a pas besoin d'être unitaire (tout comme Measure ne l'est pas); une instruction est spécifiquement unitaire si elle est un Gate.

Chronique « 1 »
Chronique « 2 »
Instruction(nom, nombre_de_qubits, nombre_de_clbits, paramètres)Instruction quantique générique.

InstructionLes fonctions s peuvent être pratiquement arbitraires, à condition qu'elles n'agissent que sur Qubits et Clbits, et qu'elles soient paramétrées par leur params; elles ne doivent pas tenter de « capturer » les registres du circuit externe, ni utiliser de paramètres cachés en leur sein. InstructionLes circuits peuvent être associés à d'autres circuits pour permettre des décompositions à l'aide de leur Instruction.definition attribut, ce qui permet d'obtenir une décomposition locale et ponctuelle. Cela peut se faire dans n'importe quel ensemble de base d'opérations qui vous convient le mieux, à condition que les définitions de toutes les portes qu'il contient respectent un certain ordre topologique; en d'autres termes, vous ne pouvez pas utiliser une porte dans une définition si sa propre définition dépend de la porte parente. Si le Instruction doit être considéré comme totalement opaque pour les optimiseurs, son definition peut être None. Pour plus d'informations, consultez la section « Création d'instructions personnalisées ».

La params d'une instruction peut techniquement être arbitraire, mais en général, il convient de s'en tenir, dans la mesure du possible, à des paramétrisations en termes de nombres réels. Qiskit enfreint lui-même cette règle à de nombreux endroits, et vous trouverez toutes sortes de types inhabituels dans Instruction.params les champs; or, ceux-ci constituent une source gênante de bogues, car ils impliquent souvent la nécessité de traiter ces cas particuliers en tenant compte des types. Si votre instruction est paramétrée en termes d'angles, vous pourrez y utiliser de manière fiable le paramétrage à la compilation, et elle s'intégrera parfaitement à QuantumCircuit.assign_parameters().

Bien Instruction que ne soit pas nécessairement unitaire, sa sous-classe Gate implique l'unitarité et ajoute to_matrix() les méthodes et control() à toutes les méthodes héritées de Instruction.

Chronique « 1 »
Chronique « 2 »
Gate(nom, nombre_de_qubits, paramètres[, étiquette] )Porte unitaire.

Gate hérite de toutes les méthodes pour Instruction et de toutes les mêmes considérations concernant ses params champs et definition , sauf bien sûr que Gates ne peut agir sur aucune ressource classique.

definitionGate Les instances peuvent (et devraient) avoir une base, mais vous pouvez également spécifier plusieurs décompositions différentes dans différentes bases à l'aide d'un EquivalenceLibrary.

GateEn matière de sous-classement, Qiskit dispose également d'une classe spécifique ControlledGate . Cette classe sert de base à de nombreuses portes logiques de la bibliothèque standard qui sont contrôlées (telles que CXGate), et c'est là que vous êtes le plus susceptible de la rencontrer :

Chronique « 1 »
Chronique « 2 »
ControlledGate(nom, nombre_de_qubits, paramètres[,...] )Porte unitaire contrôlée.

Chacun des éléments Instruction, Gate et ControlledGate possède un type singleton correspondant, construit à l'aide du mécanisme décrit dans qiskit.circuit.singleton. La documentation au niveau du module contient toutes les informations nécessaires, ainsi que des descriptions de SingletonInstruction, SingletonGate et SingletonControlledGate. Du point de vue de l'utilisateur, cela ne change pas grand-chose que la classe de base soit ou non un singleton; l'objectif reste toujours d'appeler to_mutable() en premier si vous avez besoin d'obtenir une copie en propriété d'une instruction pouvant être modifiée en toute sécurité (vous ne pouvez pas partir du principe qu'une instruction quelconque est modifiable), et bien que l'inspection directe type soit déconseillée, si vous en avez besoin, la manière fiable de déterminer le type « de base » d'une instruction potentiellement singleton consiste à utiliser base_class.

ControlledGate utilise les mêmes mécanismes que les portes de sous-classification pour définir une synthèse fixe et paresseuse pour elle-même. Bien sûr, cette approche ne tient pas compte du matériel et est plus difficile à intégrer aux routines de synthèse du compilateur, mais elle fonctionne mieux en tant que solution concrète Instruction susceptible d'être exécutée en mode natif sur le matériel. Dans les cas où la synthèse et l'optimisation abstraite revêtent une importance particulière, Qiskit propose une classe composable appelée AnnotatedOperation, qui gère les « modificateurs de porte » (dont ControlModifier fait partie) à appliquer à la porte interne base_op.

Chronique « 1 »
Chronique « 2 »
AnnotatedOperation(op_base, modificateurs)Opération annotée.

Les modificateurs disponibles pour AnnotatedOperation sont les suivants :

InverseModifier

class qiskit.circuit.InverseModifier

GitHub

Bases : Modifier

Modificateur d'inversion : spécifie que l'opération est inversée.

ControlModifier

class qiskit.circuit.ControlModifier(num_ctrl_qubits=0, ctrl_state=None)

GitHub

Bases : Modifier

Modificateur de contrôle : spécifie que l'opération est contrôlée par num_ctrl_qubits et a l'état de contrôle ctrl_state.

Paramètres

  • num_ctrl_qubits (int)
  • ctrl_state (int |str | None)

PowerModifier

class qiskit.circuit.PowerModifier(power)

GitHub

Bases : Modifier

Modificateur de puissance : spécifie que l'opération est portée à la puissance power.

Paramètres

power (float)

Pour plus d'informations sur la création de portes et d'instructions personnalisées, y compris la création d'objets uniques et de portes paramétriques réutilisables via la sous-classification, voir la section Création d'instructions personnalisées ci-dessous. La bibliothèque de circuits Qiskit dans qiskit.circuit.library contient de nombreuses portes et circuits prédéfinis que vous pouvez utiliser.

Instructions spéciales intégrées

Qiskit contient quelques Instruction classes qui sont, d'une certaine manière, « spéciales ». Ces éléments font généralement l'objet d'un traitement particulier dans le code du circuit, dans le transpileur ou dans les modèles matériels. Il s'agit là, en général, d'instructions que vous connaissez peut-être déjà.

Les mesures dans Qiskit sont d'un seul Qubit dans un seul Clbit. C'est à ces deux personnes que s'applique l'instruction. Les mesures sont dans la base de calcul.

Mesure

class qiskit.circuit.Measure(label=None)

GitHub

Bases : SingletonInstruction

Mesure quantique dans la base de calcul.

Paramètres

label - étiquette de chaîne facultative pour cette instruction.

En relation avec les mesures, il existe une opération Reset qui ne produit aucune donnée classique mais demande au matériel de ramener le qubit à l'état ∣0⟩\lvert0\rangle. On suppose que cela se produit de manière incohérente et qu'il n'y a plus d'intrication.

Réinitialiser

class qiskit.circuit.Reset(label=None)

GitHub

Bases : SingletonInstruction

Réinitialisation incohérente d'un qubit à l'état ∣0⟩\lvert0\rangle.

Paramètres

label - étiquette de chaîne facultative de cette instruction.

Il est possible de configurer le matériel pour qu'il applique une période d'inactivité en temps réel à un qubit donné. DelayUn circuit planifié (voir qiskit.transpiler) inclura explicitement tous les temps d'inactivité des qubits en fonction de cette valeur. BoxOp peut également comporter une durée explicitement indiquée, dans son BoxOp.duration champ.

Retard

class qiskit.circuit.Delay(duration, unit=None)

GitHub

Bases : Instruction

Ne fait rien et se contente de retarder, d'attendre ou de rester en veille pendant une durée déterminée.

Paramètres

  • durée - la durée de la période. S'il s'agit d'un Expril doit s'agir d'une expression constante de type Duration et le paramètre unit doit être omis (ou DOIT être "expr" s'il est spécifié).
  • unit - l'unité de la durée, si duration est une valeur numérique. Doit être "dt", une unité de seconde préfixée par le SI, ou "expr".

Augmentations

CircuitError - Une expression duration a été spécifiée avec un type résolu qui n'est pas basé sur le temps, ou l'expression unit a été incorrectement spécifiée.

Les durées de retard peuvent être spécifiées soit avec des temps concrets et constants, soit avec des "expressions de durée" à résolution différée construites à partir d'objets expr.Stretch objets. Pour en savoir plus, consultez la rubrique Programmation à résolution différée.

L'instruction Barrier peut s'étendre sur un nombre arbitraire de qubits et de clbits, et n'a pas d'effet sur le matériel. Cependant, pendant la transpilation et l'optimisation, il empêche les optimisations de "franchir" la barrière, c'est-à-dire qu'il les empêche d'entrer dans le système :

from qiskit.circuit import QuantumCircuit

qc = QuantumCircuit(1)
qc.x(0)
qc.barrier()
qc.x(0)

il est interdit à l'optimiseur d'annuler les deux instructions XX.

Barrière

class qiskit.circuit.Barrier(num_qubits, label=None)

GitHub

Bases : Instruction

Une directive pour la compilation de circuits afin de séparer les morceaux d'un circuit de manière à ce que toute optimisation ou réécriture soit contrainte d'agir uniquement entre les barrières.

Cela apparaîtra également dans les visualisations comme un marqueur visuel.

Paramètres

  • num_qubits (int) – le nombre de qubits pour la barrière.
  • label (str | None) – l'étiquette facultative de cette barrière.

L'instruction Store est particulièrement particulière, car elle permet d'écrire le résultat d'une expression de calcul classique en temps réel (an expr.Expr) dans une variable classique locale (a expr.Var). Il ne faut *ni l'un ni l'autre *Qubit ni Clbit opérandes, mais a une lvalue et rvalue.

Par exemple, pour déterminer la parité d'une chaîne de bits cr et la stocker dans un autre registre creg, l'instruction Store peut être utilisée de la manière suivante :

parity = expr.lift(cr[0])
for i in range(1,n):
    parity = expr.bit_xor(cr[i], parity)
qc.store(creg[0], parity)

Magasin

class qiskit.circuit.Store(lvalue, rvalue)

GitHub

Bases : Instruction

Stockage manuel d'une valeur classique dans un emplacement de mémoire classique.

Il s'agit d'une primitive de bas niveau de la gestion de l'expression classique (similaire à la façon dont Measure est une primitive pour les mesures quantiques) et ne peut pas être sous-classée en toute sécurité.

Paramètres

  • lvalue (expr.Expr) – l'emplacement de mémoire dans lequel il est stocké.
  • rvalue (expr.Expr) – le résultat de l'expression stockée.
lvalue

Obtenir la valeur l Expr qui est stocké dans le nœud.

rvalue

Obtenir le nœud de la valeur r Expr qui est écrit dans la valeur l.

Calcul classique en temps réel

Voir aussi

qiskit.circuit.classical

Documentation au niveau du module sur le fonctionnement des systèmes de variables, d'expressions et de types, sur les objets utilisés pour les représenter et sur les opérations classiques disponibles.

Travailler avec des données classiques typées en temps réel

Les QuantumCircuit les méthodes permettant de travailler avec ces variables dans le cadre d'un circuit unique.

Qiskit dispose d'un support rudimentaire de bas niveau pour représenter les calculs classiques en temps réel, qui se produisent pendant l'exécution de la QPU et affectent les résultats. Nous n'en sommes qu'au début de la prise en charge de ces concepts par le matériel, et il vous faudra donc travailler en étroite collaboration avec la documentation de votre fournisseur de matériel pour tirer le meilleur parti d'un calcul classique en temps réel.

Ces calculs en temps réel sont représentés par le système d'expressions et de types dans qiskit.circuit.classical. À un niveau élevé, toutes les expressions en temps réel sont représentées par un nœud Expr qui fait partie d'une représentation "arborescente" de l'expression, à laquelle est associée une adresse Type bien définie à chaque niveau. Voir la documentation au niveau du module pour plus de détails sur les représentations internes de ces classes.

Le résultat d'une opération en temps réel Expr peut être utilisé directement à certains endroits. SwitchCaseOpPour l'instant, cela se limite aux conditions et WhileLoopOp, ainsi qu'à IfElseOp la cible. Le résultat peut également être stocké dans un emplacement de stockage classique typé, à l'aide de Store l'instruction (ou de son QuantumCircuit.store() constructeur), adossée à un expr.Var nœud.

Un circuit peut contenir des emplacements de stockage classiques manuels, représentés en interne par le Var nœud de l'arbre Expr arbre. Elles ont un type classique attaché (comme toute autre expression). Ils peuvent être déclarés et initialisés à chaque exécution du circuit (add_var()), soit être des entrées dans le circuit (add_input()).

Paramétrage à la compilation

Diverses instances paramétriques Instruction de Qiskit peuvent être paramétrées de manière à ce que ces paramètres soient déterminés lors de la compilation. Celles-ci se caractérisent par l'utilisation des Parameter classes et ParameterExpression .

Chronique « 1 »
Chronique « 2 »
Parameter(nom[, uuid] )Un paramètre symbolique au moment de la compilation.
ParameterExpression(nom_map, expr)L'expression d'un paramètre.

La principale différence par rapport aux expr.Var variables utilisées dans le calcul classique en temps réel réside dans le fait que ParameterExpression est une représentation symbolique d'une expression mathématique. La sémantique de cette expression est celle des mathématiques classiques sur les nombres réels continus (et, dans certains cas particuliers, sur les nombres complexes). En revanche, Var est un descripteur pointant vers une variable stockée sur un ordinateur classique, telle qu'une valeur à virgule flottante ou un entier à largeur fixe, qui sont toujours discrètes.

En d'autres termes, vous pouvez vous attendre ParameterExpression à effectuer des simplifications symboliques valables en mathématiques, comme la simplification de (x+y−x)/y→1(x + y - x) / y \to 1. Une telle simplification n'est pas valable en arithmétique en virgule flottante, et expr.Expr ne la réalisera pas.

La partie "temps de compilation" de ces paramètres signifie que vous voudrez généralement "attribuer" des valeurs aux paramètres avant d'envoyer le circuit pour exécution. Ces paramètres peuvent être utilisés partout où l'on attend un angle mathématique (comme les paramètres d'une porte de rotation), à condition que le matériel exige qu'ils soient assignés à une valeur classique appropriée avant l'exécution. Vous pouvez effectuer ce travail en utilisant QuantumCircuit.assign_parameters().

Vous souhaiterez peut-être utiliser plusieurs paramètres liés les uns aux autres. ParameterVectorPour vous faciliter la tâche (et vous éviter d'avoir à trouver de nombreux noms), vous pouvez utiliser le constructeur pratique. ParameterVectorElementLes éléments du vecteur sont tous des instances valides Parameter d'une sous-classe spéciale.

Chronique « 1 »
Chronique « 2 »
ParameterVector(nom[, longueur, uuid] )Un conteneur regroupant plusieurs objets liés Parameter entre eux.
ParameterVectorElement(vecteur, index[, uuid] )Un élément d'un ParameterVector.

Contrôle du flux dans les circuits

InstructionDans QuantumCircuit, le flux de contrôle classique est représenté par des objets spécifiques, qui sont des sous-classes de ControlFlowOp.

Chronique « 1 »
Chronique « 2 »
ControlFlowOp(nom, nombre_de_qubits, nombre_de_clbits,...)Classe abstraite permettant d'encapsuler toutes les opérations du flux de contrôle.

Pour plus de commodité, il existe une frozenset instance contenant les Instruction.name attributs de chacune des opérations de flux de contrôle.

qiskit.circuit.CONTROL_FLOW_OP_NAMES

Ensemble des noms d'instructions des opérations de flux de contrôle connues de Qiskit.

La fonction get_control_flow_name_mapping() permet d'accéder aux classes d'opérations du flux de contrôle associées à chaque nom.

get_control_flow_name_mapping

qiskit.circuit.get_control_flow_name_mapping()

GitHub

Renvoyer un dictionnaire établissant une correspondance entre les noms des opérations de flux de contrôle et les classes correspondantes"

Exemples

from qiskit.circuit import get_control_flow_name_mapping

ctrl_flow_name_map = get_control_flow_name_mapping()
if_else_object = ctrl_flow_name_map["if_else"]

print(if_else_object)
<class 'qiskit.circuit.controlflow.if_else.IfElseOp'>

Ces opérations de contrôle de flux (IfElseOp, WhileLoopOp, SwitchCaseOp, ForLoopOp, et BoxOp) possèdent toutes un état spécifique qui définit les conditions et les stratégies de branchement, mais contiennent dans leur blocks propriété tous les différents blocs de sous-circuits auxquels on peut accéder.

Chronique « 1 »
Chronique « 2 »
IfElseOp(condition, corps_vrai[, corps_faux,...] )Opération de circuit qui exécute un programme (true_body) si une condition fournie (condition) est évaluée comme vraie, et évalue éventuellement un autre programme (false_body) dans le cas contraire.
WhileLoopOp(condition, corps[, libellé] )Opération de circuit qui exécute de manière répétée un sous-circuit (body) jusqu'à ce qu'une condition (condition) soit évaluée comme étant fausse.
SwitchCaseOp(cible, cas, *[, étiquette] )Opération de circuit qui exécute un bloc de circuit particulier sur la base de la correspondance entre un target donné et une liste ordonnée de values.
ForLoopOp(ensemble d'index, paramètre_de_boucle, corps[,...] )Opération de circuit qui exécute de manière répétée un sous-circuit (body) paramétré par un paramètre loop_parameter à travers l'ensemble des valeurs entières fournies dans indexset.
BoxOp(corps[, durée, unité, libellé, annotations] )Une "boîte" d'opérations sur un circuit qui sont traitées de manière atomique dans un contexte plus large.

Le SwitchCaseOp prend également en charge une valeur spéciale :

qiskit.circuit.CASE_DEFAULT

Valeur par défaut : <default case>

Un objet spécial qui représente le cas "par défaut" d'une instruction de commutation. Si vous l'utilisez comme cible de cas, il doit s'agir du dernier cas, et il correspondra à tout ce qui n'a pas déjà été pris en compte. Lorsque l'on utilise l'interface de construction de QuantumCircuit.switch()il est également possible d'y accéder en tant qu'attribut DEFAULT de l'objet de construction de cas lié.

Outre les opérations de flux de contrôle de la structure en blocs, il existe également deux instructions spéciales qui affectent le flux de contrôle à l'intérieur des boucles. Elles correspondent aux utilisations typiques des instructions break et continue dans les langages de programmation classiques.

Chronique « 1 »
Chronique « 2 »
BreakLoopOp(nombre_qubits, nombre_clbits[, étiquette] )Opération de circuit qui, lorsqu'elle est rencontrée, saute à l'extrémité de la boucle englobante la plus proche.
ContinueLoopOp(nombre_qubits, nombre_clbits[, étiquette] )Opération de circuit qui, lorsqu'elle est rencontrée, permet de passer à l'itération suivante de la boucle englobante la plus proche.
Remarque

Les représentations des classes sont documentées ici, mais veuillez noter que la construction manuelle de ces classes est une opération de bas niveau que nous ne nous attendons pas à ce que les utilisateurs aient à faire fréquemment.

Les utilisateurs doivent lire Ajouter un flux de contrôle aux circuits pour connaître les flux de travail recommandés pour construire des circuits compatibles avec le flux de contrôle.

Étant donné que ControlFlowOp les sous-classes sont elles-mêmes Instruction des sous-classes, cela signifie que la manière dont elles sont stockées dans CircuitInstruction les instances implique qu’elles sont « appliquées » à une séquence de qubits et de clbits dans leurs qubits attributs et clbits . Cela peut entraîner de subtils problèmes de cohérence des données : les Qubit objets et Clbit utilisés à l'intérieur des blocs de sous-circuits des opérations de flux de contrôle ne seront pas nécessairement identiques aux objets correspondants dans le CircuitInstruction. Tout code qui utilise des opérations de flux de contrôle dans Qiskit doit en tenir compte; au sein d'un sous-circuit, vous devez traiter subcircuit.qubits[i] comme s'il s'agissait en réalité outer_instruction.qubits[i]de, et ainsi de suite. Pour cela, vous pouvez créer facilement un tableau de correspondance en procédant comme suit :

cf_instruction: CircuitInstruction = ...
cf_operation: ControlFlowOp = cf_instruction.operation
for block in blocks:
    # Mappings of "inner" qubits/clbits to the outer ones.
    qubit_map = dict(zip(block.qubits, cf_instruction.qubits))
    clbit_map = dict(zip(block.clbits, cf_instruction.clbits))

    # ... do something with `block` ...

N'oubliez pas que vous devrez propager ces informations si vous vous retrouvez dans des sous-blocs d'opérations de flux de contrôle.

ClbitTous les blocs de sous-circuits d'un ControlFlowOp doivent contenir le même nombre de Qubits et de s, se référant aux mêmes bits extérieurs dans le même ordre, afin que la zip boucle indiquée dans le bloc de code ci-dessus fonctionne. Les objets du circuit Bit interne ne doivent pas nécessairement être littéralement les mêmes objets. Lorsque vous utilisez l'interface du générateur de flux de contrôle (dont on ne saurait trop insister sur le fait qu'elle est vivement recommandée aux utilisateurs), les générateurs veilleront à ce que les objets « bit » internes soient identiques aux objets « bit » externes; le qubit_map dans le bloc de code ci-dessus sera toujours un mappage {x: x}, mais si vous utilisez ces blocs, vous devez vous préparer à l'éventualité où le mappage soit requis.

Tout ClassicalRegisterregistre s utilisé dans un sous-circuit de contrôle de flux doit également être présent dans tous les blocs qui le contiennent ( c'est-à-dire toutes les opérations de contrôle de flux qui le contiennent, ainsi que le circuit le plus externe), et tous les blocs appartenant à ce même ControlFlowOp sous-circuit doivent contenir les mêmes registres. Là encore, l'interface du générateur s'assurera que ce soit le cas (ou générera une erreur précoce si cela s'avère impossible).

Lorsque la construction de bas niveau est utilisée, les blocs internes doivent fermer manuellement toutes les variables de calcul classique en temps réel à portée extérieure qu'ils utilisent QuantumCircuit doivent fermer manuellement toutes les variables de calcul classique en temps réel du périmètre extérieur qu'ils utilisent. Ceci est marqué par leur présence dans l'itérateur iter_captured_vars() pour ce bloc. Les bibliothèques qui construisent ces blocs manuellement devront suivre ces captures lors de la construction des blocs de circuits à flux de contrôle et les ajouter au bloc à l'aide de add_capture() (ou l'argument du constructeur captures ), mais le code utilisateur utilisera généralement l'interface du constructeur de circuits à flux de contrôle, qui s'en charge automatiquement.

Consultez la documentation sur la construction à flux de contrôle pour plus d'informations sur la façon de construire des circuits à flux de contrôle.

Annotations locales d'instructions

Voir aussi

qiskit.circuit.annotation

La discussion au niveau du module du cadre d'annotation, y compris la façon de définir des annotations personnalisées, et la façon dont le système interagit avec le compilateur et avec la sérialisation vers d'autres formats.

Certaines instructions de circuit peuvent être « annotées » à l'aide d'annotations locales à l'instruction. Selon la page « 2.1.0 » de Qiskit, cette fonctionnalité est limitée à BoxOp. Toutes les annotations sont des sous-classes d'un objet de base définissant une interface, mais elles représentent généralement des analyses et des commandes entièrement personnalisées.

Chronique « 1 »
Chronique « 2 »
Annotation(*args, **kwargs)Une annotation arbitraire pour les instructions.

Étude des relations de commutation

Si deux opérations d'un circuit se combinent, nous pouvons intervertir l'ordre dans lequel elles sont appliquées. Cela peut permettre des optimisations et des simplifications, par exemple, si cela permet de fusionner ou d'annuler des portes :

     ┌─────────┐     ┌─────────┐               ┌─────────┐
q_0: ┤ Rz(0.5) ├──■──┤ Rz(1.2) ├──■──     q_0: ┤ Rz(1.7) ├
     └─────────┘┌─┴─┐└──┬───┬──┘┌─┴─┐  =       └──┬───┬──┘
q_1: ───────────┤ X ├───┤ X ├───┤ X ├     q_1: ───┤ X ├───
                └───┘   └───┘   └───┘             └───┘

La mise en œuvre de ces optimisations relève du transcompilateur, mais les outils permettant d'étudier les commutations sont disponibles dans le CommutationChecker.

Chronique « 1 »
Chronique « 2 »
CommutationChecker([...])Vérifier les commutations de deux opérations.

Planification à résolution différée

En règle générale, le résultat généré par le compilateur de Qiskit ne peut pas être exécuté directement sur un QPU. Tout d'abord, il est probable qu'il passe par un compilateur de niveau d'impulsion propre au fournisseur, qui convertit les portes logiques et les mesures en signaux destinés à l'électronique de commande du QPU. Bien que Qiskit Target puisse représenter certaines des contraintes temporelles auxquelles ces impulsions seront soumises, cette représentation n'est généralement pas tout à fait exhaustive. Cela est particulièrement vrai lorsqu'il s'agit de circuits dynamiques (opérations de type « feed-forward »); les délais induits par les composants classiques dépendent souvent de détails de bas niveau liés à l'optimisation finale des compilateurs backend, et ne peuvent donc pas être connus de Qiskit.

Dans ces situations, l'utilisateur peut encore exercer un contrôle sur l'ordonnancement relatif des impulsions, par exemple pour le découplage dynamique, en utilisant des durées "d'étirement". Ils sont construits par QuantumCircuit.add_stretch()et interagissent avec le système d'expression classique décrit dans qiskit.circuit.classicalbien qu'ils ne soient pas mutables en temps réel.

Par exemple, nous pouvons ajouter des étirements et des boîtes pour mettre en place un système dans lequel deux séquences distinctes de découplage dynamique sont appliquées au même qubit, tandis qu'une paire d'autres qubits subit un retard d'une durée inconnue. Les deux séquences sont contraintes d'avoir la même longueur, même si, en interne, les impulsions DD concrètes ont des longueurs différentes.

from qiskit import QuantumCircuit
from qiskit.circuit.classical import expr

qc = QuantumCircuit(3, 3)
# This sets up three duration "degrees of freedom" that will
# be resolved later by a backend compiler.
a = qc.add_stretch("a")
b = qc.add_stretch("b")
c = qc.add_stretch("c")

# This set of operations involves feed-forward operations that
# Qiskit cannot know the length of.
with qc.box():
    qc.h(1)
    qc.cx(1, 2)
    qc.measure([1, 2], [1, 2])
    with qc.if_test(expr.equal(qc.clbits[1], qc.clbits[2])):
        qc.h(1)

# While that stuff is happening to qubits (1, 2), we want
# qubit 0 to do two different DD sequences.  The two DD
# sequences are fixed to be the same length as each other,
# even though they're both internally stretchy.
with qc.box(duration=a):
    # Textbook NMRish XX DD.
    qc.delay(b, 0)
    qc.x(0)
    qc.delay(expr.mul(2, b), 0)
    qc.x(0)
    qc.delay(b, 0)
with qc.box(duration=a):
    # XY4-like DD.
    for _ in range(2):
        qc.delay(c, 0)
        qc.y(0)
        qc.delay(expr.mul(2, c), 0)
        qc.x(0)
        qc.delay(c, 0)

Création d'instructions personnalisées

Si vous souhaitez créer des instructions ou des portes simples et ponctuelles destinées à être ajoutées à un circuit, et que les blocs ne servent qu’à des fins de visualisation ou de regroupement, le moyen le plus simple de créer une instruction ou une porte personnalisée consiste simplement à définir celle-ci sous la forme d’un QuantumCircuit, puis à utiliser sa to_instruction() méthode ou to_gate() selon le cas. Les résultats peuvent être transmis directement sur QuantumCircuit.append() le circuit principal. Ces méthodes créeront des Gate instances de base Instruction dont definition l'attribut correspondra au circuit tel qu'il a été fourni, ce qui signifie qu'il sera automatiquement accessible au transpileur, ainsi qu'aux autres fonctions de Qiskit qui tentent de décomposer des circuits.

Notez que les instructions et les portes autonomes ne doivent agir que sur les qubits et les clbits; les instructions qui doivent utiliser un flux de contrôle complexe devront être intégrées dans le QuantumCircuit en utilisant compose().

Création de sous-classes d'instructions

InstructionLes classes de base, Gate et ControlledGate sont toutes conçues pour permettre la création de sous-classes en toute sécurité et comportent des points d'accrochage que les sous-classes peuvent implémenter. Si votre porte personnalisée est sans paramètres et sans état, vous pouvez également choisir de la dériver de la classe singleton correspondante dans qiskit.circuit.singleton, telle que SingletonGate. Nous vous recommandons de consulter la documentation disponible à l'adresse qiskit.circuit.singleton pour découvrir d'autres méthodes et points d'accrochage liés au mécanisme de singleton.

Les sous-classes doivent généralement définir un constructeur par défaut qui appelle le constructeur :class`super` avec les arguments adaptés à votre instruction. Il est possible d'inclure des états supplémentaires dans la classe, mais vos sous-classes s'intégreront de manière plus fiable au reste de l'environnement Qiskit si vous ne vous appuyez que sur vos Instruction.params, et si ces paramètres correspondent uniquement à des angles de porte.

Les sous-classes de Instruction (ou l'une de ses sous-classes) doivent implémenter la méthode privée Instruction._define() , qui alimente de manière paresseuse le cache caché _definition qui sert de support à la méthode publique definition .

_define

Instruction._define()

GitHub

InstructionRemplissez le champ « cached _definition » de cet objet.

Les sous-classes doivent implémenter cette méthode afin de permettre la construction différée de leur attribut public definition . Une sous-classe peut utiliser son params au moment de l'appel. La méthode doit attribuer _definition une QuantumCircuit valeur à et ne pas renvoyer de valeur.

Dans les sous-classes de ControlledGate, la _define() méthode doit implémenter la décomposition uniquement pour l'état de commande « tout à 1 ». Le ControlledGate.definition système adaptera cela en fonction de l'état réel de la commande.

Si la sous-classe utilise la machinerie singleton, il faut se méfier du fait que _define() sera appelé avec empressement immédiatement après l'exécution de la déclaration du corps de la classe, afin de produire l'objet de définition de l'objet singleton canonique. Cela signifie que votre définition ne doit utiliser que des portes déjà définies; si vous écrivez une bibliothèque avec de nombreuses portes singleton, vous devrez ordonner vos fichiers et vos importations de manière à ce que cela soit possible.

Les sous-classes de Gate auront probablement aussi intérêt à redéfinir la méthode d'instance du protocole de tableau Numpy, __array__. Cette fonction est utilisée par Gate.to_matrix(), et présente la signature suivante :

__array__

object.__array__(dtype=None, copy=None)

Renvoie un tableau Numpy représentant la porte logique. Cela permet d'utiliser le champ de params la porte et de supposer qu'il s'agit de valeurs numériques (en partant du principe que la sous-classe s'y attend) et non de paramètres définis à la compilation.

Pour plus d'efficacité, le tableau retourné doit avoir par défaut un dtype de complex.

Si votre sous-classe personnalisée dispose de représentations naturelles de ses formes contrôlées ou inverses, vous pouvez également choisir de redéfinir les inverse() méthodes et control() .

À titre d'exemple de définition d'une porte « RxzR_{xz} » personnalisée, c'est-à-dire une rotation à angle unique autour de l'axe XZXZ. RZXGateEn gros, si les qubits étaient dans l'autre sens, c'est donc ainsi que nous allons formuler notre définition. Nous sommes paramétriques, donc nous ne pouvons pas être un singleton, mais nous sommes unitaires, donc nous devrions être un Gate:

import math
import numpy as np
from qiskit.circuit import Gate, QuantumCircuit

class RXZGate(Gate):
    def __init__(self, theta):
        # Initialize with our name, number of qubits and parameters.
        super().__init__("rxz", 2, [theta])

    def _define(self):
        # Our base definition is an RZXGate, applied "backwards".
        defn = QuantumCircuit(2)
        defn.rzx(1, 0)
        self._definition = defn

    def inverse(self, annotated = False):
        # We have an efficient representation of our inverse,
        # so we'll override this method.
        return RXZGate(-self.params[0])

    def power(self, exponent: float):
        # Also we have an efficient representation of power.
        return RXZGate(exponent * self.params[0])

    def __array__(self, dtype=None, copy=None):
        if copy is False:
            raise ValueError("unable to avoid copy while creating an array as requested")
        cos = math.cos(0.5 * self.params[0])
        isin = 1j * math.sin(0.5 * self.params[0])
        return np.array([
            [cos, -isin, 0, 0],
            [-isin, cos, 0, 0],
            [0, 0, cos, isin],
            [0, 0, isin, cos],
        ], dtype=dtype)

Dans cet exemple, nous avons défini une base en fonction de RZXGate, mais pour permettre des décompositions plus rapides en fonction de différentes bases, nous pourrions vouloir ajouter d'autres équivalences à SessionEquivalenceLibrary. Notez que la BasisTranslator recherche de traduction passera en revue toutes les équivalences possibles à toutes les profondeurs possibles; ainsi, fournir une équivalence en termes de (par exemple) XGate rendra automatiquement disponibles les décompositions en termes de RXGate également.

Ajoutons une équivalence en termes de HH, CXCX et RzR_z pour un paramètre symbolique arbitraire :

from qiskit.circuit import SessionEquivalenceLibrary, Parameter

theta = Parameter("theta")

equiv = QuantumCircuit(2)
equiv.h(0)
equiv.cx(1, 0)
equiv.rz(theta, 0)
equiv.cx(1, 0)
equiv.h(0)

SessionEquivalenceLibrary.add_equivalence(RZXGate(theta), equiv)

Par la suite, pendant toute la durée de la session de l'interpréteur Python, les traducteurs comme BasisTranslator trouveront notre nouvelle définition dans leurs résultats de recherche.


Travailler avec des objets au niveau du circuit

Conversion de circuits abstraits en circuits physiques

Un logiciel abstrait QuantumCircuit ne peut pas être exécuté de manière fiable sur du matériel. Vous pouvez utiliser certains des simulateurs de haut niveau mentionnés dans la section Simulation de circuits pour obtenir des résultats rapides pour des circuits à petite échelle, mais pour faire fonctionner des circuits à grande échelle, vous devrez utiliser du matériel réel, ce qui implique de compiler un circuit physique.

La fonction de haut niveau permettant d'effectuer cette opération est transpile(); elle prend en paramètre un circuit abstrait et un matériel backend ou target, et renvoie un circuit physique. Pour bénéficier d'un meilleur accès et d'un meilleur contrôle sur les étapes des passes qui seront exécutées, utilisez generate_preset_pass_manager() pour créer d'abord un StagedPassManager , que vous pourrez ensuite modifier.

L'ensemble des mécanismes de transpilation et de compilation est décrit en détail dans la documentation du module qiskit.transpiler et le détail de toutes les passes intégrées à Qiskit est disponible dans le fichier qiskit.transpiler.passes.

Simulation de circuits

Bien que cela ne fasse pas partie de l'interface qiskit.circuit l'un des besoins les plus courants est d'obtenir rapidement des résultats de simulation pour les objets QuantumCircuit les objets. Cette section fournit un point de départ rapide vers d'autres endroits de la documentation pour trouver les informations pertinentes.

Pour les circuits unitaires, vous pouvez simuler les effets sur l'état de l' ∣0⋯0⟩\lvert0\dotsm0\rangle en le QuantumCircuit transmettant directement au constructeur Statevector par défaut. De la même manière, vous pouvez obtenir une matrice unitaire représentant le circuit en tant qu'opérateur en la transmettant au constructeur Operator par défaut. Si vous disposez d'un circuit physique, vous pouvez choisir de le transmettre à Operator.from_circuit() la méthode afin d'appliquer les transformations du circuit QuantumCircuit.layout pour le remapper vers l'espace de qubits « abstrait ».

Pour une expérience de simulation plus proche du backend, il existe des implémentations de toutes les interfaces matérielles de Qiskit soutenues par des simulateurs. En particulier, vous pourriez être intéressé par

Définition des relations d'équivalence

Une tâche courante, lorsqu'il s'agit de transposer des circuits abstraits sur du matériel physique et d'optimiser le résultat, consiste à trouver des relations d'équivalence qui permettent d'associer une porte à un ensemble de base différent. Qiskit stocke ces informations dans une classe de base de données appelée EquivalenceLibrary.

Chronique « 1 »
Chronique « 2 »
EquivalenceLibrary( [base] )Une bibliothèque fournissant une correspondance unidirectionnelle entre les portes et leurs implémentations équivalentes sous QuantumCircuits.

Qiskit est livré avec un grand nombre de relations d'équivalence prédéfinies pour toutes ses portes standard. Cette bibliothèque de base s'appelle StandardEquivalenceLibraryet doit être considérée comme immuable.

qiskit.circuit.StandardEquivalenceLibrary

Un EquivalenceLibrary qui stocke toutes les relations entre les portes standard intégrées à Qiskit. SessionEquivalenceLibrarybaseVous ne devez pas modifier cette variable; vous devez plutôt soit créer votre propre variable EquivalenceLibrary en vous basant sur celle-ci, soit modifier l'état global.

Qiskit définit également un objet d'état global partagé, SessionEquivalenceLibrary, qui correspond aux équivalences par défaut utilisées à divers endroits dans Qiskit, notamment dans la BasisTranslator passe de transpilation. N'hésitez pas à ajouter vos propres équivalences à l'aide de sa add_equivalence() méthode; elles seront automatiquement prises en compte par les instances par défaut de BasisTranslator.

qiskit.circuit.SessionEquivalenceLibrary

L'instance par défaut de EquivalenceLibrary, qui sera utilisée par la plupart des objets Qiskit si aucune bibliothèque n'est spécifiée manuellement. N'hésitez pas à ajouter des équivalences à cette liste à l'aide de add_equivalence(). Il hérite de toutes les règles intégrées de StandardEquivalenceLibrary.

Appliquer le principe de Pauli à un circuit

Il existe deux principaux types de bruit lors de l'exécution de circuits quantiques. Le premier est le bruit stochastique, ou incohérent, qui est principalement dû à l'interaction indésirable entre le processeur quantique et l'environnement externe dans lequel il réside. La seconde est connue sous le nom d'erreur cohérente, et ces erreurs sont dues à un contrôle imparfait d'un système quantique. Il peut s'agir de termes indésirables dans un hamiltonien du système, c'est-à-dire d'une évolution unitaire incorrecte, ou d'erreurs dues à un contrôle temporel incorrect du système quantique, ce qui inclut des éléments tels que des formes d'impulsion incorrectes pour les portes.

Le tourbillon de Pauli est une technique de suppression des erreurs quantiques qui utilise la randomisation pour transformer les erreurs cohérentes en erreurs stochastiques en combinant les résultats de nombreux circuits aléatoires, mais logiquement équivalents. Qiskit fournit une fonction permettant d'appliquer le tourbillon de Pauli à un circuit donné pour des portes standard à deux qubits. Pour plus de détails, vous pouvez vous référer à la documentation de la fonction ci-dessous :

pauli_twirl_2q_gates

qiskit.circuit.pauli_twirl_2q_gates(circuit, twirling_gate=None, seed=None, num_twirls=None, target=None)

GitHub

Créer des copies d'un circuit donné en appliquant le tourbillon de Pauli autour des portes de deux qubits spécifiés.

Si vous exécutez cette fonction dans l'intention de faire tourner un circuit pour l'exécuter sur du matériel, ce n'est peut-être pas la manière la plus efficace d'effectuer le tournoiement. En particulier si le fournisseur de matériel a implémenté l'interface d'exécution primitives avec SamplerV2 et EstimatorV2 , ce n'est probablement pas la meilleure façon d'appliquer le twirling à votre circuit et vous devrez vous référer à l'implémentation de SamplerV2 et/ou EstimatorV2 pour le fournisseur de matériel spécifié.

Si cette fonction est destinée à être exécutée après transpile() ou PassManager.run() , l'argument facultatif target peut être utilisé afin que les portes de Pauli à 1 qubit insérées soient synthétisées de manière à être compatibles avec la valeur donnée de Target , de sorte que le ou les circuits de sortie restent compatibles.

Paramètres

  • circuit (QuantumCircuit) – Le circuit pour tourner
  • twirling_gate (None | str |Gate |list[str] | list[Gate]) – Le paramètre « gate to twirl » est défini par défaut sur « None », ce qui signifie que toutes les portes par défaut sont soumises à l'effet « twirl » : CXGate, CZGate, ECRGate, et iSwapGate. S'il est fourni, il peut s'agir soit d'une porte unique, soit d'une liste de portes, sous la forme soit d'un objet « porte », soit de son nom sous forme de chaîne de caractères. À l'heure actuelle, seuls les noms « cx », « cz », « ecr » et « iswap » sont pris en charge. Si un objet « gate » est défini en dehors des « gates » par défaut, il doit disposer d'une matrice définie via sa to_matrix méthode pour que la « gate » puisse éventuellement être pivotée. Si aucune configuration de rotation valide ne peut être calculée, cette porte sera ignorée sans message d'erreur et ne fera pas l'objet d'une rotation.
  • seed (int | None) – Une graine entière pour le générateur de nombres aléatoires utilisé en interne par cette fonction. Si elle est spécifiée, elle doit être comprise entre 0 et 18 446 744 073 709 551 615.
  • num_twirls (int | None) – Le nombre de circuits tournants à construire. La valeur par défaut est None , ce qui permet d'obtenir un seul circuit. S'il s'agit d'un nombre entier, une liste de circuits avec des circuits num_twirls sera renvoyée.
  • target (Target | None) – Si une Target instance est spécifiée, celle-ci sera utilisée pour effectuer la décomposition d'un qubit unique dans le cadre de la rotation de Pauli, afin d'optimiser et de mapper les portes de Pauli ajoutées au circuit vers la cible spécifiée.

Retours

Une copie du circuit donné avec un tourbillon de Pauli appliqué à chaque instance de la porte de tourbillon spécifiée.

Type de retour

QuantumCircuit | liste [ QuantumCircuit ]


Exceptions

Presque toutes les fonctions et méthodes du circuit lèveront un CircuitError lorsqu'elles rencontrent une erreur propre à l'utilisation de Qiskit (contrairement aux problèmes de typage ou d'indexation ordinaires, qui soulèvent généralement l'erreur standard correspondante sur Python ).

CircuitError

exception qiskit.circuit.CircuitError(*message)

GitHub

Bases : QiskitError

Classe de base pour les erreurs survenues lors du traitement d'un circuit.

Définir le message d'erreur.


Conventions de circuit

Lors de la construction de circuits à partir d'objets abstraits et de matrices plus concrètes, il existe plusieurs conventions possibles concernant l'étiquetage des bits, l'ordre des bits et la manière dont le produit tensoriel abstrait est réalisé dans l'algèbre matricielle concrète.

Les conventions de Qiskit sont :

  • dans les représentations de chaînes de bits, les bits sont étiquetés avec le bit le plus à droite de la chaîne appelé 00 et le bit le plus à gauche de la chaîne de bits nn appelé n−1n - 1.
  • lors de l'utilisation d'entiers comme indices de spécification de bits dans les fonctions de construction de circuits, l'entier est traité comme un indice dans QuantumCircuit.qubits (ou clbits).
  • lorsque l'on dessine des circuits, on place les bits d'indice le plus bas en haut.
  • dans les représentations des vecteurs d'état, nous réalisons le produit tensoriel abstrait comme le produit de Kronecker, et nous ordonnons les arguments de ce produit de manière à ce que l'amplitude de l'état de la base de calcul ∣x⟩\lvert x\rangle, où xx est la chaîne de bits interprétée comme un nombre entier, se trouve à l'emplacement statevector[x].
  • lors du contrôle d'une porte, le(s) qubit(s) de contrôle est (sont) placé(s) en premier dans la liste des arguments, par exemple dans l'appel qc.cx(0, 1), le qubit 0 sera le contrôle et le qubit 1 sera la cible. De même, dans l'appel manuel qc.append(CXGate(), [0, 1]), le qubit 0 sera le contrôle et le qubit 1 sera la cible.

Illustrons ces conventions par quelques exemples.

Étiquetage des bits

Prendre le circuit :

from qiskit import QuantumCircuit

qc = QuantumCircuit(5, 5)
qc.x(0)
qc.x(1)
qc.x(4)
qc.measure(range(5), range(5))

Cette opération inverse les états des qubits 0, 1 et 4 de ∣0⟩\lvert0\rangle à ∣1⟩\lvert1\rangle, puis mesure tous les qubits nn dans le clbit correspondant nn en utilisant la base de calcul ( ZZ ). Si elle est simulée sans bruit, la chaîne de bits produite par ce circuit sera toujours 1001110011; les qubits 0, 1 et 4 sont inversés et les valeurs "un" de la chaîne de bits se trouvent dans le zéro, le premier et le quatrième chiffre en partant de la droite.

Dans Qiskit, nous écririons l'état du qubit immédiatement avant la mesure en abrégé ket-notation sous la forme ∣10011⟩\lvert10011\rangle. Notez que l'étiquette ket correspond à la chaîne de bits classique et a la valeur numérique binaire de 19.

Si nous dessinons ce circuit, nous verrons que Qiskit place le qubit zeroth en haut du dessin du circuit :

qc.draw("mpl")
Schéma de circuit produit par le code précédent.

Représentations matricielles

Les vecteurs d'état sont définis selon la convention suivante : pour un système à deux niveaux, la relation entre la représentation abstraite et la représentation matricielle est telle que

α∣0⟩+β∣1⟩↔(αβ)\alpha\lvert0\rangle + \beta\lvert1\rangle \leftrightarrow \begin{pmatrix} \alpha \\ \beta \end{pmatrix}

où α\alpha et β\beta sont des nombres complexes. Nous stockons le vecteur d'état sous la forme d'un fichier 1D Numpy ndarray avec les données sv = [alpha, beta], c'est-à-dire sv[0] == alpha et sv[1] == beta; notez que les indices dans le vecteur d'état correspondent aux étiquettes de ket.

Nous construisons le produit tensoriel de deux états de qubits dans l'algèbre matricielle en utilisant le produit de Kronecker, avec le qubit 0 à droite et le qubit 1 à gauche, de telle sorte que l'état de base ZZ ∣x⟩\lvert x\rangle (où xx est l'interprétation entière de la chaîne de bits) a son terme non nul dans le vecteur d'état sv à sv[x]:

import numpy
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

state_0 = [1, 0]  # defined representation of |0>
state_1 = [0, 1]  # defined representation of |1>

# Circuit that creates basis state |10011>, where
# binary 10011 has the decimal value 19.
qc = QuantumCircuit(5)
qc.x(0)
qc.x(1)
qc.x(4)
qiskit_sv = Statevector(qc)

# List index 'n' corresponds to qubit 'n'.
individual_states = [
    state_1,
    state_1,
    state_0,
    state_0,
    state_1,
]
# Start from a scalar.
manual_sv = [1]
for qubit_state in individual_states:
    # Each new qubit goes "on the left".
    manual_sv = numpy.kron(qubit_state, manual_sv)

# Now `qiskit_sv` and `manual_sv` are the same, and:
assert manual_sv[19] == 1
assert qiskit_sv[19] == 1

Cela se répercute sur la représentation matricielle des opérateurs et s'inscrit dans le cadre des conventions relatives à l'ordre des bits pour les opérateurs contrôlés. Par exemple, la forme matricielle de CXGate est la suivante :

import numpy
from qiskit.circuit.library import CXGate

numpy.array(CXGate())
array⁡(CX)=(1000000100100100)\operatorname{array}(CX) = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \end{pmatrix}

Cela peut être différent des autres représentations matricielles que vous avez vues pour CXCX, mais rappelez-vous que le choix de la représentation matricielle est conventionnel et que cette forme correspond aux conventions de Qiskit selon lesquelles les qubits de contrôle viennent en premier et le produit tensoriel est représenté de manière à ce qu'il y ait une correspondance entre l'indice de l'"amplitude unique" et la valeur de la chaîne de bits d'un état.

Dans le cas où une porte comporte plusieurs commandes, comme par exemple pour CCXGate, l'argument ctrl_state est interprété comme la valeur sous forme de chaîne de bits des qubits de commande, en utilisant les mêmes conventions de numérotation à partir de zéro. CCXGatePar exemple, étant donné que la valeur par défaut ctrl_state est la chaîne de bits composée uniquement de « 1 », on constate que la forme matricielle de CCXGate avec ctrl_state = 1 est identique à celle que l'on obtiendrait en prenant l'état de contrôle composé uniquement de « 1 », mais en inversant la valeur du qubit de contrôle ayant l'indice le plus élevé à l'entrée et à la sortie de la porte :

from qiskit import QuantumCircuit
from qiskit.quantum_info import Operator

# Build the natural representation of `CCX` with the
# control qubits being `[0, 1]`, relative to the
# bitstring state "01", such that qubit 0 must be in |1>
# and qubit 1 must be in |0>.  The target qubit is 2.
ccx_natural = QuantumCircuit(3)
ccx_natural.ccx(0, 1, 2, ctrl_state=1)

# Build the same circuit in terms of the all-ones CCX.
# Note that we flip _qubit 1_, because that's the one
# that differs from the all-ones state.
ccx_relative = QuantumCircuit(3)
ccx_relative.x(1)
ccx_relative.ccx(0, 1, 2)
ccx_relative.x(1)

assert Operator(ccx_relative) == Operator(ccx_natural)

Dans ces deux cas, la forme matricielle de CCXGate dans ctrl_state = 1 est la suivante :

array⁡(CCX(ctrl_state=1))=(1000000000000100001000000001000000001000010000000000001000000001)\operatorname{array}\bigl(CCX(\text{ctrl\_state}=1)\bigr) = \begin{pmatrix} 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \end{pmatrix}
Cette page a-t-elle été utile ?
Signaler un bogue, une coquille ou proposer du contenu sur GitHub.