Se développe. https://github.com/Blueqat/Blueqat
Création d'un backend Blueqat ~ Partie 1 a expliqué comment créer un backend de simulateur qui peut accepter l'entrée OpenQ ASM. Cette fois, je vais vous expliquer comment faire un backend dans le cas général où il ne l'est pas.
Au minimum, vous pouvez créer un backend en héritant de Backend
et en implémentant la méthode run
.
Il réimplémente également la méthode copy
si une copie de l'objet backend lui-même n'est pas pratique pour copy.deepcopy
.
La méthode run
est sous la formerun (self, gates, n_qubits, * args, ** kwargs)
.
gates
est la liste des objets de porte quantique, n_qubits
est le nombre de bits quantiques, * args
, ** kwargs
est lorsque l'utilisateur appelle Circuit (). Run ()
L'argument de run
est passé.
Si vous implémentez cela, vous pouvez faire un backend, mais comme il est trop lancé au développeur, nous avons préparé le flux suivant.
Cela semble déroutant, mais si vous suivez le flux, cela peut être un peu plus facile à mettre en œuvre.
De plus, lors de la réimplémentation de la méthode run
, vous pouvez utiliser celle que vous avez préparée.
Par défaut, l'appel de la méthode run
appelle le code suivant.
def _run(self, gates, n_qubits, args, kwargs):
gates, ctx = self._preprocess_run(gates, n_qubits, args, kwargs)
self._run_gates(gates, n_qubits, ctx)
return self._postprocess_run(ctx)
def _run_gates(self, gates, n_qubits, ctx):
"""Iterate gates and call backend's action for each gates"""
for gate in gates:
action = self._get_action(gate)
if action is not None:
ctx = action(gate, ctx)
else:
ctx = self._run_gates(gate.fallback(n_qubits), n_qubits, ctx)
return ctx
Ce que les développeurs backend devraient implémenter
_preprocess_run
--Action pour chaque porte_postprocess_run
est.
Remarquez la variable intitulée «ctx» ici.
Cette variable est une variable pour maintenir l'état du début à la fin de run
. (Si vous n'en avez pas besoin, vous pouvez utiliser None
, mais je pense qu'il y a peu de cas où vous n'en avez pas du tout besoin.)
Puisque le backend lui-même est également un objet ordinaire, vous pouvez lui donner un état en définissant self.foo = ...
etc., mais cela causera des bogues, etc., veuillez donc utiliser ctx
autant que possible.
Comme exemple de ce qu'il faut enregistrer dans ctx
--Nombre de bits quantiques --Vecteur d'état au milieu du calcul
run
et son analyse
――Autres choses qui prennent du temps à faire ou à avoir un étatEtc.
(Attention à ne pas savoir pendant l'exécution à moins que vous ne laissiez le nombre de bits quantiques dans ctx
)
Aussi, si vous regardez le code ci-dessus, faites attention à ctx
--Créez un objet ctx
avec _preprocess_run
--Lors de l'appel de l'action pour chaque porte, un objet ctx
est passé. Une action devrait également être implémentée pour renvoyer un objet ctx
--_postprocess_run
reçoit ctx
et renvoie le résultat de run
C'est devenu un flux.
Les circuits quantiques sont réalisés en disposant des portes. Une méthode d'implémentation backend typique consiste à appliquer les portes dans une ligne en séquence.
L'opération d'application d'une porte est appelée ici action. Pour implémenter une action, ajoutez simplement une méthode,
def gate_{Nom de la porte}(self, gate, ctx):
#Quelque chose d'implémenté
return ctx
Faire.
En tant que fonctionnalité de Blueqat, il y avait une notation de tranche telle que Circuit (). H [:]
, mais il existe également une méthode pour séparer la notation de tranche, et pour idx dans gate.target_iter (quantité nombre de bits) Si vous aimez)
, vous pouvez obtenir l'indice de 1 porte de bit quantique. De plus, l'indice de 2 portes de bits quantiques telles que CNOT devrait être pour c, t dans gate.control_target_iter (nombre de bits quantiques)
.
Le nombre de bits quantiques est nécessaire ici afin de savoir combien de bits quantiques appliquer lorsque vous faites quelque chose comme Circuit (). H [:]
.
Toutes les portes ne doivent pas être implémentées. Par exemple, des portes T et des portes S peuvent être réalisées s'il y a une porte Z rotative (RZ), donc du côté Blueqat, si elle n'est pas implémentée, veillez à utiliser une autre porte à la place. Je suis.
De plus, s'il n'y a pas de porte alternative, ce sera une erreur si elle est utilisée, mais ce ne sera pas une erreur si elle n'est pas utilisée, vous pouvez donc créer un backend qui ne prend en charge que certaines portes. (Par exemple, si vous implémentez uniquement la porte X, la porte CX et la porte CCX, vous pouvez créer un backend spécialisé pour les circuits logiques classiques. Vous pouvez l'implémenter avec juste une opération de bit, afin de créer quelque chose qui fonctionne à grande vitesse.)
Les portes qui doivent être implémentées peuvent être triées à l'avenir, mais à l'heure actuelle Les mesures sont X, Y, Z, H, CZ, CX, RX, RY, RZ, CCZ, U3. (Les mesures définissent les actions de la même manière que les portes)
Un backend légèrement inhabituel est QasmOutputBackend
.
Ce n'est pas un simulateur, mais un backend pour convertir les circuits quantiques de Blueqat en OpenQ ASM.
ctx
contient une liste de lignes OpenQASM et le nombre de bits quantiques.
De plus, chaque action ajoute une ligne à la liste.
Cliquez ici pour le code complet.
class QasmOutputBackend(Backend):
"""Backend for OpenQASM output."""
def _preprocess_run(self, gates, n_qubits, args, kwargs):
def _parse_run_args(output_prologue=True, **_kwargs):
return { 'output_prologue': output_prologue }
args = _parse_run_args(*args, **kwargs)
if args['output_prologue']:
qasmlist = [
"OPENQASM 2.0;",
'include "qelib1.inc";',
f"qreg q[{n_qubits}];",
f"creg c[{n_qubits}];",
]
else:
qasmlist = []
return gates, (qasmlist, n_qubits)
def _postprocess_run(self, ctx):
return "\n".join(ctx[0])
def _one_qubit_gate_noargs(self, gate, ctx):
for idx in gate.target_iter(ctx[1]):
ctx[0].append(f"{gate.lowername} q[{idx}];")
return ctx
gate_x = _one_qubit_gate_noargs
gate_y = _one_qubit_gate_noargs
gate_z = _one_qubit_gate_noargs
gate_h = _one_qubit_gate_noargs
gate_t = _one_qubit_gate_noargs
gate_s = _one_qubit_gate_noargs
def _two_qubit_gate_noargs(self, gate, ctx):
for control, target in gate.control_target_iter(ctx[1]):
ctx[0].append(f"{gate.lowername} q[{control}],q[{target}];")
return ctx
gate_cz = _two_qubit_gate_noargs
gate_cx = _two_qubit_gate_noargs
gate_cy = _two_qubit_gate_noargs
gate_ch = _two_qubit_gate_noargs
gate_swap = _two_qubit_gate_noargs
def _one_qubit_gate_args_theta(self, gate, ctx):
for idx in gate.target_iter(ctx[1]):
ctx[0].append(f"{gate.lowername}({gate.theta}) q[{idx}];")
return ctx
gate_rx = _one_qubit_gate_args_theta
gate_ry = _one_qubit_gate_args_theta
gate_rz = _one_qubit_gate_args_theta
def gate_i(self, gate, ctx):
for idx in gate.target_iter(ctx[1]):
ctx[0].append(f"id q[{idx}];")
return ctx
def gate_u1(self, gate, ctx):
for idx in gate.target_iter(ctx[1]):
ctx[0].append(f"{gate.lowername}({gate.lambd}) q[{idx}];")
return ctx
def gate_u2(self, gate, ctx):
for idx in gate.target_iter(ctx[1]):
ctx[0].append(f"{gate.lowername}({gate.phi},{gate.lambd}) q[{idx}];")
return ctx
def gate_u3(self, gate, ctx):
for idx in gate.target_iter(ctx[1]):
ctx[0].append(f"{gate.lowername}({gate.theta},{gate.phi},{gate.lambd}) q[{idx}];")
return ctx
def gate_cu1(self, gate, ctx):
for c, t in gate.control_target_iter(ctx[1]):
ctx[0].append(f"{gate.lowername}({gate.lambd}) q[{c}],q[{t}];")
return ctx
def gate_cu2(self, gate, ctx):
for c, t in gate.control_target_iter(ctx[1]):
ctx[0].append(f"{gate.lowername}({gate.phi},{gate.lambd}) q[{c}],q[{t}];")
return ctx
def gate_cu3(self, gate, ctx):
for c, t in gate.control_target_iter(ctx[1]):
ctx[0].append(f"{gate.lowername}({gate.theta},{gate.phi},{gate.lambd}) q[{c}],q[{t}];")
return ctx
def _three_qubit_gate_noargs(self, gate, ctx):
c0, c1, t = gate.targets
ctx[0].append(f"{gate.lowername} q[{c0}],q[{c1}],q[{t}];")
return ctx
gate_ccx = _three_qubit_gate_noargs
gate_cswap = _three_qubit_gate_noargs
def gate_measure(self, gate, ctx):
for idx in gate.target_iter(ctx[1]):
ctx[0].append(f"measure q[{idx}] -> c[{idx}];")
return ctx
_preprocess_run
def _preprocess_run(self, gates, n_qubits, args, kwargs):
def _parse_run_args(output_prologue=True, **_kwargs):
return { 'output_prologue': output_prologue }
args = _parse_run_args(*args, **kwargs)
if args['output_prologue']:
qasmlist = [
"OPENQASM 2.0;",
'include "qelib1.inc";',
f"qreg q[{n_qubits}];",
f"creg c[{n_qubits}];",
]
else:
qasmlist = []
return gates, (qasmlist, n_qubits)
La première chose que nous faisons est d'analyser les options.
Puisqu'il peut être exécuté avec l'option ʻoutput_prologue comme
Circuit (). Run (backend = 'qasm_output', output_prologue = False) `, l'option est analysée.
Cette option est True par défaut, mais sera ajoutée au début d'OpenQ ASM si False est spécifié.
OPENQASM 2.0;
include "qelib1.inc";
qreg q[Nombre de bits];
creg c[Nombre de bits];
Est omis.
Ensuite, ctx est une liste de lignes OpenQASM et un tuple de bits quantiques.
Il renvoie gates et ctx, mais gates renvoie simplement ce qui a été passé en argument.
J'ai décidé de retourner des portes avec _preprocess_run
parce que je voulais manipuler la colonne de porte pour l'optimisation, etc., mais s'il n'y a pas besoin de le faire, l'argument reçu est retourné tel quel.
def _one_qubit_gate_noargs(self, gate, ctx):
for idx in gate.target_iter(ctx[1]):
ctx[0].append(f"{gate.lowername} q[{idx}];")
return ctx
gate_x = _one_qubit_gate_noargs
gate_y = _one_qubit_gate_noargs
gate_z = _one_qubit_gate_noargs
#Omis car il y en a beaucoup
def gate_cu3(self, gate, ctx):
for c, t in gate.control_target_iter(ctx[1]):
ctx[0].append(f"{gate.lowername}({gate.theta},{gate.phi},{gate.lambd}) q[{c}],q[{t}];")
return ctx
Je vais le faire comme ça. Puisqu'il est difficile de faire des portes x, y, z une par une, je les porte sur le côté.
Si vous ne pouvez pas le porter sur le côté, implémentez-le soigneusement comme gate_cu3
.
Ce que nous faisons, c'est que ctx
était ([liste de lignes], nombre de bits quantiques)
, alors ajoutez une nouvelle ligne à la liste de lignes avecctx [0] .append (...)
Je fais juste ça.
_postprocess_run
def _postprocess_run(self, ctx):
return "\n".join(ctx[0])
Il renvoie simplement une liste de lignes sous forme de chaîne délimitée par un saut de ligne. Ce résultat est le résultat de «run».
La dernière fois, nous avons examiné la méthode d'implémentation backend pour les systèmes de traitement capables de lire OpenQASM, mais cette fois, nous avons regardé la méthode d'implémentation backend plus générale.
Chez Blueqat, nous visons à prendre en charge autant que possible du côté bibliothèque et à permettre aux développeurs et aux utilisateurs de faire ce qu'ils veulent, et il est également possible de créer facilement un backend en s'appuyant sur le mécanisme fourni par Blueqat. Vous pouvez, et vous pouvez même implémenter la méthode complète run
sans vous embarquer.
N'importe qui peut créer le backend lui-même, et vous pouvez même utiliser votre propre simulateur avec l'interface Blueqat. Tout le monde, essayez d'implémenter le backend.
Recommended Posts