Travailler avec des circuits bosoniques
Un BosonicCircuit exprime un calcul directement sur des modes bosoniques. Il ne porte ni troncature, ni encodage, ni nombre de qubits : ceux-ci interviennent plus tard, pendant la transpilation, ce qui permet d'abaisser un même circuit sous plusieurs encodages et plusieurs troncatures sans le reconstruire.
Il expose délibérément une API réduite par rapport au QuantumCircuit de Qiskit, afin que des portes de qubits ne puissent pas être appliquées à un mode bosonique, ce qui ne serait pas une opération bien définie. Ce guide couvre les opérations qui sont exposées pour assembler de grands circuits à partir de plus petits.
Pourquoi utiliser des circuits bosoniques
Considère le flux de travail traditionnel pour porter l'évolution temporelle d'un hamiltonien bosonique sur une architecture à base de qubits :
-
Définir le hamiltonien bosonique, par exemple .
-
Choisir une troncature et un encodage, et mapper le hamiltonien vers un opérateur de qubits avec eux.
-
Implémenter l'évolution temporelle dans l'espace des qubits, par Trotterisation ou une autre méthode.
La deuxième étape arrive tôt, et elle prend deux décisions coûteuses à défaire. La troncature fixe le nombre de niveaux que garde chaque mode, et l'encodage fixe le nombre de qubits que cela coûte ; les deux sont donc figés dans l'opérateur de qubits avant que quoi que ce soit d'autre n'ait été fait avec le circuit. Pour regarder la même physique sous une autre coupure ou un autre encodage, tu mappes à nouveau le hamiltonien et reconstruis le circuit. De plus, ce que le mapping jette, c'est la structure bosonique : quels termes vont ensemble, et le fait que les termes d'une paire de saut sont l'adjoint l'un de l'autre.
Un BosonicCircuit repousse ces décisions à la fin :
-
Définir le hamiltonien bosonique.
-
Exprimer l'évolution temporelle directement sur des modes bosoniques, avec des portes bosoniques.
-
Choisir la troncature et l'encodage dans le cadre de la transpilation.
Le circuit décrit quel calcul effectuer, et la transpilation décide comment le placer sur des qubits. Le même circuit est ensuite abaissé à chaque coupure et sous chaque encodage, et le groupement de l'opérateur est toujours là pour les passes qui peuvent l'utiliser. Les sections suivantes montrent comment un tel circuit est assemblé ; Transpiler des circuits bosoniques le montre en cours d'abaissement.
Indexation générique des modes
Le module operators et le circuit partagent une même convention pour les modes. Un mode n'est qu'un indice qui étiquette un degré de liberté bosonique, et il n'a pas de signification propre. Tu peux donc faire évoluer tout opérateur qui implémente le protocole OperatorTrait, quoi qu'il représente, et le circuit n'a jamais besoin de savoir ce que le mode 2 désigne. Il en va de même pour la taille d'un mode : un circuit n'a pas de coupure d'occupation, donc rien ici n'indique combien de niveaux a un mode.
Ajouter des portes
Les portes sont ajoutées sur des modes. Seules les instances de BosonicGate sont acceptées :
>>> from qiskit_bosons.circuit import BosonicCircuit
>>> from qiskit_bosons.circuit.library import Evolution, InitializeModes
>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>>
>>> hopping = BosonOperator.from_dict({(cre(0), ann(1)): -1.0, (cre(1), ann(0)): -1.0})
>>> hopping.groups = [0] * len(hopping) # the pair is Hermitian, its terms alone are not
>>>
>>> prepare = BosonicCircuit(2)
>>> prepare.append(InitializeModes([2, 0]), prepare.modes)
>>> step = BosonicCircuit(2)
>>> step.append(Evolution(2, hopping, time=0.1), step.modes)
Tout le reste est refusé, et l'erreur nomme le type fautif :
>>> from qiskit.circuit.library import HGate
>>> try:
... prepare.append(HGate(), [0])
... except ValueError as error:
... print(error)
Unsupported instruction type: <class '...HGate'>
Composer des circuits
compose() place les instructions d'un circuit sur un autre. Elles sont insérées en ligne, et non enveloppées dans une seule porte ; le résultat est donc un circuit ordinaire que tu peux continuer à décomposer et à transpiler.
>>> program = prepare.compose(step)
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Par défaut, un nouveau circuit est renvoyé et les deux opérandes restent intacts. Passe inplace=True pour modifier plutôt le premier, et front=True pour placer l'opérande avant les instructions existantes :
>>> step.compose(prepare, front=True)._inner.data == program._inner.data
True
Un circuit plus étroit peut être placé sur un sous-ensemble choisi des modes :
>>> single = BosonicCircuit(1)
>>> single.append(Evolution(1, BosonOperator.from_dict({(cre(0), ann(0)): 1.0}), 0.1), single.modes)
>>> wide = BosonicCircuit(3).compose(single, [2])
>>> wide.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Seul un autre BosonicCircuit est accepté comme opérande, car le compose propre à Qiskit insérerait des portes de qubits et contournerait la vérification qu'impose append.
Répéter un circuit
repeat() répète un circuit bout à bout. Les répétitions sont aplaties : le résultat contient les instructions reps fois à la suite, plutôt qu'une porte enveloppant le circuit. Par conséquent, les décomptes d'opérations évoluent avec reps, et decompose agit sur le corps répété au lieu de retirer une répétition.
>>> len(step.repeat(0)._inner.data) # zero repetitions is the identity
0
>>> step.repeat(5).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
C'est la façon de Trotteriser au niveau du circuit : évoluer pendant un court temps, et répéter. Voir Synthétiser une porte d'évolution dans l'espace bosonique pour l'alternative consistant à choisir une formule de produit sur la porte elle-même, et pourquoi les deux sont des choses différentes qui partagent par hasard le mot « répétitions ».
Séparer les étapes avec des barrières
Une barrière marque un point de synchronisation à travers lequel les passes d'optimisation ne fusionnent pas de portes. barrier() en place une sur tous les modes, ou sur les modes que tu nommes :
>>> program = BosonicCircuit(2)
>>> program.append(InitializeModes([2, 0]), program.modes)
>>> program.barrier()
>>> program.barrier(program.modes[0], label="zero")
>>> program.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
repeat(..., insert_barriers=True) en place une entre répétitions consécutives, et aucune après la dernière :
>>> step.repeat(3, insert_barriers=True).draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Barrières et encodage
Un encodage a besoin de plusieurs qubits par mode ; le registre de modes et le registre de qubits diffèrent donc en taille, et un layout relie des registres entiers. Un mode n'a donc pas de qubits propres auxquels restreindre une barrière. Pendant la synthèse boson-vers-qubit, une barrière est mappée sur chaque qubit de chaque registre qu'elle touchait, ce qui est correct sous n'importe quel layout :
>>> from qiskit_bosons.mappers import LevelSpec
>>> from qiskit_bosons.transpiler.presets import generate_preset_occupation_pass_manager
>>> lowered = generate_preset_occupation_pass_manager(LevelSpec(2, "unary_projected")).run(program)
>>> lowered.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
Lire le résultat d'un circuit bosonique
Un BosonicCircuit n'a ni measure ni measure_all. Ce que signifie un bit mesuré dépend de l'encodage. Sous un encodage unaire, l'occupation d'un mode est répartie sur plusieurs qubits et doit être lue à partir d'un motif de bits, et sous un encodage compact c'est un nombre binaire ; une mesure qui nomme un mode devrait donc connaître l'encodage, ce qu'un circuit bosonique ne fait délibérément pas. Mesure plutôt le QuantumCircuit transpilé, et décode les chaînes de bits avec LevelEncoding.level_to_bits() et les décalages du LevelSpec avec lequel tu as abaissé le circuit. Simuler la dynamique de Bose-Hubbard avec un choix d'encodage le fait pour un modèle complet.
>>> hasattr(BosonicCircuit, "measure_all")
False
Étapes suivantes
-
Transpiler des circuits bosoniques explique comment un circuit bosonique devient un circuit de qubits, étape par étape.
-
Faire pivoter des modes bosoniques présente la porte qui mélange les modes et conserve le nombre de bosons.
-
Synthétiser une porte d'évolution dans l'espace bosonique montre comment Trotteriser une évolution dans l'espace bosonique.
-
Principes de conception des représentations d'opérateurs explique les opérateurs que portent les portes.