From 3295f213306a2cb3e798c6e3b2f20d1895065717 Mon Sep 17 00:00:00 2001 From: chensgit169 Date: Mon, 31 Jul 2023 17:10:01 +0800 Subject: [PATCH 01/11] another small example of variation --- .../variational/brickwall_circuit.py | 34 +++++++++++++++++++ .../benchmark/variational/ladder_circuit.py | 2 -- 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 src/quafu/benchmark/variational/brickwall_circuit.py diff --git a/src/quafu/benchmark/variational/brickwall_circuit.py b/src/quafu/benchmark/variational/brickwall_circuit.py new file mode 100644 index 0000000..8532f15 --- /dev/null +++ b/src/quafu/benchmark/variational/brickwall_circuit.py @@ -0,0 +1,34 @@ +from quafu.circuits.quantum_circuit import QuantumCircuit +from numpy import random + +qubit_num = 6 +n_layers = 2 + + +def brickwall_layout_circuit(params, pbc=False): + """ + `params` is for circuit trainable parameters + """ + c = QuantumCircuit(qubit_num) + offset = 0 if pbc else 1 + for j in range(n_layers): + for i in range(0, qubit_num - offset, 2): + c.cnot(i, (i + 1) % qubit_num) + for i in range(qubit_num): + c.rx(i, params[j, i, 0]) + for i in range(1, qubit_num - offset, 2): + c.cnot(i, (i + 1) % qubit_num) + for i in range(qubit_num): + c.rx(i, params[j, i, 1]) + return c + + +def plot(): + para = random.random((n_layers, qubit_num, 2)) + qc = brickwall_layout_circuit(para) + qc.plot_circuit(title='Brickwall Layout for \nVariational Circuit', + show=True, + save=False, + ) + +plot() diff --git a/src/quafu/benchmark/variational/ladder_circuit.py b/src/quafu/benchmark/variational/ladder_circuit.py index 297326e..ee985db 100644 --- a/src/quafu/benchmark/variational/ladder_circuit.py +++ b/src/quafu/benchmark/variational/ladder_circuit.py @@ -27,5 +27,3 @@ def plot(): show=True, save=False, ) - -plot() \ No newline at end of file From 6a1a2492d974cfb050465b3fe1fc678eda8f4f27 Mon Sep 17 00:00:00 2001 From: chensgit169 Date: Wed, 2 Aug 2023 00:05:02 +0800 Subject: [PATCH 02/11] add QuantumRegister --- src/quafu/circuits/qreg.py | 80 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/quafu/circuits/qreg.py diff --git a/src/quafu/circuits/qreg.py b/src/quafu/circuits/qreg.py new file mode 100644 index 0000000..dc8b2ae --- /dev/null +++ b/src/quafu/circuits/qreg.py @@ -0,0 +1,80 @@ +# (C) Copyright 2023 Beijing Academy of Quantum Information Sciences +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class Qubit: + """ + Representation of logical qubits. + """ + def __init__(self, logic_pos: int, reg_name: str = None, label: str = None, ): + self.pos = logic_pos + self.reg_name = 'q' if reg_name is None else reg_name + self.label = self.reg_name if label is None else label + self.physical_info = None + self._depth = 0 # present depth + + def __repr__(self): + return self.reg_name + '_%s' % self.pos + + def load_physical_info(self, *args, **kwargs): + raise NotImplementedError + + @property + def used(self): + return self._depth > 0 + + def add_depth(self, num: int = 1): + self._depth += num + + def move_pos(self, new_pos): + old_pos = 0 + self.pos + self.pos = new_pos + return old_pos + + +class QuantumRegister: + """ + Collection of Qubit(s) + """ + def __init__(self, num: int = 0, name: str = None): + self.name = name + self.qubits = {i: Qubit(logic_pos=i, reg_name=name) for i in range(num)} + + def __getitem__(self, item): + return self.qubits[item] + + def __len__(self): + return len(self.qubits) + + def __add__(self, other: 'QuantumRegister'): + qreg = QuantumRegister(name=self.name) + qreg.qubits = {**{self.qubits}, **{i + len(self): qubit for i, qubit in other.qubits.item()}} + return QuantumRegister(len(self) + len(other), name=self.name) + + def exchange(self, p1, p2): + pass + + +if __name__ == '__main__': + # q0 = Qubit(0) + # q1 = Qubit(1) + # reg = {q.pos: q for q in [q0, q1]} + # print(reg) + # + # reg[0], reg[1] = reg[1], reg[0] + # reg[0].pos, reg[1].pos = reg[1].pos, reg[0].pos + # + # print(reg) + # print([q0.pos, q1.pos]) + reg = QuantumRegister(4, name='reg') + print(reg[3]) From 464046b9e75b7d0836aed7cbe8e477d39230042e Mon Sep 17 00:00:00 2001 From: chensgit169 Date: Tue, 8 Aug 2023 19:27:52 +0800 Subject: [PATCH 03/11] avoid plotting of unused-qubits Yet decorator implementation may be adopted in the future to provide cleaner coding and more flexible usage. --- src/quafu/visualisation/circuitPlot.py | 87 +++++++++++++++----------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/src/quafu/visualisation/circuitPlot.py b/src/quafu/visualisation/circuitPlot.py index b5d0f18..7b447ac 100644 --- a/src/quafu/visualisation/circuitPlot.py +++ b/src/quafu/visualisation/circuitPlot.py @@ -107,15 +107,16 @@ def __init__(self, qc): self._text_list = [] + # step0: mapping y-coordinate of used-qubits + qubits_used = qc.used_qubits + self.used_qbit_num = len(qubits_used) + self.used_qbit_y = {iq: y for y, iq in enumerate(qubits_used)} + # step1: process gates/instructions self.dorders = np.zeros(qc.num, dtype=int) for gate in qc.gates: assert isinstance(gate, Instruction) self._process_ins(gate) - qubits_used = qc.used_qubits - self.used_qbit_num = len(qubits_used) - # TODO: drop off unused-qubits - # self.used_qbit_num = np.sum(qubits_used) self.depth = np.max(self.dorders) + 1 @@ -123,8 +124,8 @@ def __init__(self, qc): self._proc_measure(self.depth - 1, q) # step2: initialize bit-label - self.q_label = {i: r'$|q_{%d}\rangle$' % i for i in range(qc.num) if i in qubits_used} - self.c_label = {iq: f'c_{ic}' for iq, ic in qc.measures.items() if iq in qubits_used} + self.q_label = {y: r'$|q_{%d}\rangle$' % i for i, y in self.used_qbit_y.items()} + self.c_label = {self.used_qbit_y[iq]: f'c_{ic}' for iq, ic in qc.measures.items()} # step3: figure coordination self.xs = np.arange(-3 / 2, self.depth + 3 / 2) @@ -158,7 +159,6 @@ def __call__(self, title, size=30, ha='center', va='baseline') - print(title) self._text_list.append(title) # initialize a figure @@ -192,6 +192,7 @@ def _process_ins(self, ins: Instruction, append: bool = True): depth = np.max(self.dorders[_which]) paras = ins.paras + if name == 'barrier': self._proc_barrier(depth, ins.pos) elif name == 'measure': @@ -221,22 +222,11 @@ def _circuit_wires(self): """ plot horizontal circuit wires """ - for y in range(self.used_qbit_num): + for pos, y in self.used_qbit_y.items(): x0 = self.xs[0] + 1 x1 = self.xs[-1] - 1 self._h_wire_points.append([[x0, y], [x1, y]]) - def _gate_bbox(self, x, y, fc: str): - """ Single qubit gate box """ - a = self._a - from matplotlib.patches import FancyBboxPatch - bbox = FancyBboxPatch((-a / 2 + x, -a / 2 + y), a, a, # this warning belongs to matplotlib - boxstyle=f'round, pad={0.2 * a}', - edgecolor=DEEPCOLOR, - facecolor=fc, - ) - self._closed_patches.append(bbox) - def _inits_label(self, labels: dict[int: str] = None): """ qubit-labeling """ if labels is None: @@ -268,7 +258,18 @@ def _measured_label(self, labels: dict[int: str] = None): ) self._text_list.append(txt) - def _gate_label(self, s, x, y): + def _gate_bbox(self, x, y, fc: str): + """ Single qubit gate box """ + a = self._a + from matplotlib.patches import FancyBboxPatch + bbox = FancyBboxPatch((-a / 2 + x, -a / 2 + y), a, a, # this warning belongs to matplotlib + boxstyle=f'round, pad={0.2 * a}', + edgecolor=DEEPCOLOR, + facecolor=fc, + ) + self._closed_patches.append(bbox) + + def _gate_label(self, x, y, s): if not s: return None _dy = 0.05 @@ -282,7 +283,7 @@ def _gate_label(self, s, x, y): text.set_path_effects([self._stroke]) self._text_list.append(text) - def _para_label(self, para_txt, x, y): + def _para_label(self, x, y, para_txt): """ label parameters """ if not para_txt: return None @@ -334,8 +335,7 @@ def _measure_label(self, x, y): self._mea_point_patches += [center_bkg, arrow, center] ######################################################################### - # # # # processing-functions: decompose ins into graphical elements # # # - ######################################################################### + # region # # # # processing-functions: decompose ins into graphical elements # # # def _proc_su2(self, id_name, depth, pos, paras): if id_name in ['x', 'y', 'z', 'h', 'id', 's', 't', 'p', 'u']: fc = '#EE7057' @@ -353,32 +353,37 @@ def _proc_su2(self, id_name, depth, pos, paras): else: para_txt = None - self._gate_label(label, depth, pos) - self._para_label(para_txt, depth, pos) - self._gate_bbox(depth, pos, fc) + x = depth + y = self.used_qbit_y[pos] + self._gate_label(x=x, y=y, s=label) + self._para_label(x=x, y=y, para_txt=para_txt) + self._gate_bbox(x=x, y=y, fc=fc) def _proc_ctrl(self, depth, ins: ControlledGate, ctrl_type: bool = True): # control part p0, p1 = np.max(ins.pos), np.min(ins.pos) - self._ctrl_wire_points.append([[depth, p1], [depth, p0]]) + x0, x1 = self.used_qbit_y[p0], self.used_qbit_y[p1] + self._ctrl_wire_points.append([[depth, x1], [depth, x0]]) ctrl_pos = np.array(ins.ctrls) for c in ctrl_pos: - self._ctrl_points.append((depth, c, ctrl_type)) + x = self.used_qbit_y[c] + self._ctrl_points.append((depth, x, ctrl_type)) # target part name = ins.name.lower() if ins.ct_nums == (1, 1, 2) or name in mc_gate_names: tar_name = ins.targ_name.lower()[-1] pos = ins.targs if isinstance(ins.targs, int) else ins.targs[0] + x = self.used_qbit_y[pos] if tar_name == 'x': - self._not_points.append((depth, pos)) + self._not_points.append((depth, x)) else: self._proc_su2(tar_name, depth, pos, None) elif name == 'cswap': - self._swap_points += [[depth, p] for p in ins.targs] + self._swap_points += [[depth, self.used_qbit_y[p]] for p in ins.targs] elif name == 'ccx': - self._not_points.append((depth, ins.targs)) + self._not_points.append((depth, self.used_qbit_y[ins.targs])) else: from quafu.elements.element_gates import ControlledU assert isinstance(ins, ControlledU), f'unknown gate: {name}, {ins.__class__.__name__}' @@ -386,9 +391,10 @@ def _proc_ctrl(self, depth, ins: ControlledGate, ctrl_type: bool = True): def _proc_swap(self, depth, pos, iswap: bool = False): p1, p2 = pos - nodes = [[depth, p] for p in pos] + x1, x2 = self.used_qbit_y[p1], self.used_qbit_y[p2] + nodes = [[depth, x] for x in [x1, x2]] self._swap_points += nodes - self._ctrl_wire_points.append([[depth, p1], [depth, p2]]) + self._ctrl_wire_points.append([[depth, x1], [depth, x2]]) if iswap: self._iswap_points += nodes @@ -397,21 +403,26 @@ def _proc_barrier(self, depth, pos: list): x1 = depth + self._barrier_width for p in pos: - y0 = (p - 1 / 2) - y1 = (p + 1 / 2) + y = self.used_qbit_y[p] + y0 = (y - 1 / 2) + y1 = (y + 1 / 2) nodes = [[x0, y0], [x0, y1], [x1, y1], [x1, y0], [x0, y0]] self._barrier_points.append(nodes) - def _proc_measure(self, depth, pos): + def _proc_measure(self, depth, pos: int): fc = GOLDEN - self._gate_bbox(depth, pos, fc) - self._measure_label(depth, pos) + y = self.used_qbit_y[pos] + x = depth + self._gate_bbox(x, y, fc) + self._measure_label(x, y) # TODO: decide whether to draw double wire for measurement # y = pos + 0.02 # x0 = depth # x1 = self.depth - 1 / 2 # self._h_wire_points.append([[x0, y], [x1, y]]) + # endregion + ######################################################################### ######################################################################### # # # # # # # # # # # # # # rendering functions # # # # # # # # # # # # # From b6d704583bbcc183b9ca645b16e410cfd77bbd47 Mon Sep 17 00:00:00 2001 From: chensgit169 Date: Tue, 8 Aug 2023 19:40:38 +0800 Subject: [PATCH 04/11] avoid plotting of unused-qubits Yet decorator implementation may be adopted in the future to provide cleaner coding and more flexible usage. --- .../circuits/{qreg.py => quantum_register.py} | 15 --------------- 1 file changed, 15 deletions(-) rename src/quafu/circuits/{qreg.py => quantum_register.py} (85%) diff --git a/src/quafu/circuits/qreg.py b/src/quafu/circuits/quantum_register.py similarity index 85% rename from src/quafu/circuits/qreg.py rename to src/quafu/circuits/quantum_register.py index dc8b2ae..cc7d458 100644 --- a/src/quafu/circuits/qreg.py +++ b/src/quafu/circuits/quantum_register.py @@ -63,18 +63,3 @@ def __add__(self, other: 'QuantumRegister'): def exchange(self, p1, p2): pass - - -if __name__ == '__main__': - # q0 = Qubit(0) - # q1 = Qubit(1) - # reg = {q.pos: q for q in [q0, q1]} - # print(reg) - # - # reg[0], reg[1] = reg[1], reg[0] - # reg[0].pos, reg[1].pos = reg[1].pos, reg[0].pos - # - # print(reg) - # print([q0.pos, q1.pos]) - reg = QuantumRegister(4, name='reg') - print(reg[3]) From b96662dda735f6bfbbdf7a58d5c6ba4afa1bcf12 Mon Sep 17 00:00:00 2001 From: chensgit169 Date: Fri, 11 Aug 2023 12:07:24 +0800 Subject: [PATCH 05/11] fix discontinuity bug of cbits in .measure() Also add more checking procedures to make sure uniqueness. --- src/quafu/circuits/quantum_circuit.py | 82 ++++++++++++++++++++------- 1 file changed, 60 insertions(+), 22 deletions(-) diff --git a/src/quafu/circuits/quantum_circuit.py b/src/quafu/circuits/quantum_circuit.py index db4c1f5..8b24fd5 100644 --- a/src/quafu/circuits/quantum_circuit.py +++ b/src/quafu/circuits/quantum_circuit.py @@ -1,30 +1,55 @@ +# (C) Copyright 2023 Beijing Academy of Quantum Information Sciences +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from typing import List + import numpy as np -import quafu.elements.element_gates.clifford -import quafu.elements.element_gates.pauli -from quafu.elements.quantum_element.pulses.quantum_pulse import QuantumPulse -from ..elements.quantum_element import Barrier, Delay, MultiQubitGate, QuantumGate, ControlledGate, \ - SingleQubitGate, XYResonance import quafu.elements.element_gates as qeg +from quafu.elements.quantum_element.pulses.quantum_pulse import QuantumPulse +from ..elements.quantum_element import ( + Barrier, + Delay, + MultiQubitGate, + QuantumGate, + ControlledGate, + SingleQubitGate, + XYResonance, +) +from .quantum_register import QuantumRegister from ..exceptions import CircuitError class QuantumCircuit(object): - def __init__(self, num: int): + def __init__(self, num: int, *args, **kwargs): """ Initialize a QuantumCircuit object Args: num (int): Total qubit number used """ - self.num = num + self.qregs = [QuantumRegister(num)] if num > 0 else [] self.gates = [] self.openqasm = "" self.circuit = [] self.measures = {} self._used_qubits = [] + @property + def num(self): + return np.sum([len(qreg) for qreg in self.qregs]) + @property def used_qubits(self) -> List: self.layered_circuit() @@ -207,7 +232,9 @@ def from_openqasm(self, openqasm: str): operations = operations_qbs[0] if operations == "qreg": qbs = operations_qbs[1] - self.num = int(re.findall("\d+", qbs)[0]) + num = int(re.findall("\d+", qbs)[0]) + reg = QuantumRegister(num) + self.qregs.append(reg) elif operations == "creg": pass elif operations == "measure": @@ -356,7 +383,7 @@ def h(self, pos: int) -> "QuantumCircuit": Args: pos (int): qubit the gate act. """ - gate = quafu.elements.element_gates.clifford.HGate(pos) + gate = qeg.HGate(pos) self.add_gate(gate) return self @@ -399,7 +426,7 @@ def t(self, pos: int) -> "QuantumCircuit": Args: pos (int): qubit the gate act. """ - self.add_gate(quafu.elements.element_gates.clifford.TGate(pos)) + self.add_gate(qeg.TGate(pos)) return self def tdg(self, pos: int) -> "QuantumCircuit": @@ -409,7 +436,7 @@ def tdg(self, pos: int) -> "QuantumCircuit": Args: pos (int): qubit the gate act. """ - self.add_gate(quafu.elements.element_gates.clifford.TdgGate(pos)) + self.add_gate(qeg.TdgGate(pos)) return self def s(self, pos: int) -> "QuantumCircuit": @@ -419,7 +446,7 @@ def s(self, pos: int) -> "QuantumCircuit": Args: pos (int): qubit the gate act. """ - self.add_gate(quafu.elements.element_gates.clifford.SGate(pos)) + self.add_gate(qeg.SGate(pos)) return self def sdg(self, pos: int) -> "QuantumCircuit": @@ -429,7 +456,7 @@ def sdg(self, pos: int) -> "QuantumCircuit": Args: pos (int): qubit the gate act. """ - self.add_gate(quafu.elements.element_gates.clifford.SdgGate(pos)) + self.add_gate(qeg.SdgGate(pos)) return self def sx(self, pos: int) -> "QuantumCircuit": @@ -439,7 +466,7 @@ def sx(self, pos: int) -> "QuantumCircuit": Args: pos (int): qubit the gate act. """ - self.add_gate(quafu.elements.element_gates.pauli.SXGate(pos)) + self.add_gate(qeg.SXGate(pos)) return self def sxdg(self, pos: int) -> "QuantumCircuit": @@ -449,7 +476,7 @@ def sxdg(self, pos: int) -> "QuantumCircuit": Args: pos (int): qubit the gate act. """ - gate = quafu.elements.element_gates.pauli.SXdgGate(pos) + gate = qeg.SXdgGate(pos) self.add_gate(gate) return self @@ -460,7 +487,7 @@ def sy(self, pos: int) -> "QuantumCircuit": Args: pos (int): qubit the gate act. """ - self.add_gate(quafu.elements.element_gates.pauli.SYGate(pos)) + self.add_gate(qeg.SYGate(pos)) return self def sydg(self, pos: int) -> "QuantumCircuit": @@ -470,7 +497,7 @@ def sydg(self, pos: int) -> "QuantumCircuit": Args: pos (int): qubit the gate act. """ - gate = quafu.elements.element_gates.pauli.SYdgGate(pos) + gate = qeg.SYdgGate(pos) self.add_gate(gate) return self @@ -771,16 +798,27 @@ def measure(self, pos: List[int] = None, cbits: List[int] = None) -> None: if pos is None: pos = list(range(self.num)) + e_num = len(self.measures) # existing num of measures + n_num = len(pos) # newly added num of measures + if not set(self.measures.keys()).isdisjoint(set(pos)): + raise ValueError("Measured qubits overlap with existing measurements.") + + if n_num > len(set(pos)): + raise ValueError("Measured qubits not uniquely assigned.") + if cbits: - if not len(cbits) == len(pos): - raise CircuitError("Number of measured bits should equal to the number of classical bits") + if not len(set(cbits)) == len(cbits): + raise ValueError("Classical bits not uniquely assigned.") + if not len(cbits) == n_num: + raise ValueError("Number of measured bits should equal to the number of classical bits") else: - cbits = pos + cbits = list(range(e_num, e_num + n_num)) + + _sorted_indices = sorted(range(n_num), key=lambda k: cbits[k]) + cbits = [_sorted_indices.index(i) + e_num for i in range(n_num)] newly_measures = dict(zip(pos, cbits)) self.measures = {**self.measures, **newly_measures} - if not len(self.measures.values()) == len(set(self.measures.values())): - raise ValueError("Measured bits not uniquely assigned.") def add_pulse(self, pulse: QuantumPulse, From fcf64847cdd4310c59cc1d0d233f8f5f24fa6f33 Mon Sep 17 00:00:00 2001 From: chensgit169 Date: Fri, 11 Aug 2023 12:21:46 +0800 Subject: [PATCH 06/11] temperately commented 'self.register' and relevant --- src/quafu/circuits/quantum_circuit.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/quafu/circuits/quantum_circuit.py b/src/quafu/circuits/quantum_circuit.py index 8b24fd5..e7f905c 100644 --- a/src/quafu/circuits/quantum_circuit.py +++ b/src/quafu/circuits/quantum_circuit.py @@ -39,16 +39,17 @@ def __init__(self, num: int, *args, **kwargs): Args: num (int): Total qubit number used """ - self.qregs = [QuantumRegister(num)] if num > 0 else [] + # self.qregs = [QuantumRegister(num)] if num > 0 else [] + self.num = num self.gates = [] self.openqasm = "" self.circuit = [] self.measures = {} self._used_qubits = [] - @property - def num(self): - return np.sum([len(qreg) for qreg in self.qregs]) + # @property + # def num(self): + # return np.sum([len(qreg) for qreg in self.qregs]) @property def used_qubits(self) -> List: From d800175b30c7e9d073597d4ed6ba505ac3d0a8af Mon Sep 17 00:00:00 2001 From: chensgit169 Date: Tue, 15 Aug 2023 15:14:06 +0800 Subject: [PATCH 07/11] enhance exception handling of user, task, backend 1. add 502 status code exception report in Task.send(), add some TODO 2. add 'None' exception report in Task.retrieve() 3. replace json.load(response.txt) with response.json() 4. replace default user=User() from the very beginning by initializing only when `user` unset 5. added license by the way --- src/quafu/backends/backends.py | 20 ++++++-- src/quafu/tasks/tasks.py | 84 ++++++++++++++++++++++------------ src/quafu/users/userapi.py | 34 +++++++++----- 3 files changed, 95 insertions(+), 43 deletions(-) diff --git a/src/quafu/backends/backends.py b/src/quafu/backends/backends.py index ccb18a6..ef4b831 100644 --- a/src/quafu/backends/backends.py +++ b/src/quafu/backends/backends.py @@ -1,3 +1,17 @@ +# (C) Copyright 2023 Beijing Academy of Quantum Information Sciences +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import requests import json import re @@ -18,9 +32,9 @@ def __init__(self, backend_info: dict): self.qv = backend_info["QV"] # self.task_in_queue = backend_info["task_in_queue"] - def get_chip_info(self, user=User()): - # update api-token, a patch to be deleted in the future - api_token = user._load_account_token() + def get_chip_info(self, user: User = None): + user = User() if user is None else user + api_token = user.api_token data = {"system_name": self.name.lower()} headers = {"api_token": api_token} chip_info = requests.post(url=User.chip_api, data=data, diff --git a/src/quafu/tasks/tasks.py b/src/quafu/tasks/tasks.py index 17ac8ea..8390525 100644 --- a/src/quafu/tasks/tasks.py +++ b/src/quafu/tasks/tasks.py @@ -1,5 +1,19 @@ +# (C) Copyright 2023 Beijing Academy of Quantum Information Sciences +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import copy -import json +import logging from typing import Dict, List, Tuple from urllib import parse @@ -28,10 +42,8 @@ class Task(object): """ - def __init__(self, user=User()): - # update api-token, a patch to be deleted in the future - user._api_token = user._load_account_token() - self.user = user + def __init__(self, user: User = None): + self.user = User() if user is None else user self.shots = 1000 self.tomo = False @@ -141,14 +153,14 @@ def submit(self, def run(self, qc: QuantumCircuit, - measure_base: List = []) -> ExecResult: + measure_base: List = None) -> ExecResult: """Single run for measurement task. Args: qc (QuantumCircuit): Quantum circuit that need to be executed on backend. measure_base (list[str, list[int]]): measure base and its positions. """ - if len(measure_base) == 0: + if measure_base is None: res = self.send(qc) res.measure_base = '' @@ -168,7 +180,8 @@ def send(self, qc: QuantumCircuit, name: str = "", group: str = "", - wait: bool = True) -> ExecResult: + wait: bool = True, + ) -> ExecResult: """ Run the circuit on experimental device. @@ -184,7 +197,7 @@ def send(self, version = get_version() if qc.num > self.backend.qubit_num: raise CircuitError("The qubit number %d is too large for backend %s which has %d qubits" % ( - qc.num, self.backend.name, self.backend.qubit_num)) + qc.num, self.backend.name, self.backend.qubit_num)) self.check_valid_gates(qc) qc.to_openqasm() @@ -198,29 +211,39 @@ def send(self, else: url = User.exec_async_api + logging.debug('quantum circuit validated, sending task...') headers = {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 'api_token': self.user.api_token} data = parse.urlencode(data) data = data.replace("%27", "'") - res = requests.post(url, headers=headers, data=data) - res_dict = json.loads(res.text) - - if res.json()["status"] in [201, 205]: - raise UserError(res_dict["message"]) - elif res.json()["status"] == 5001: - raise CircuitError(res_dict["message"]) - elif res.json()["status"] == 5003: - raise ServerError(res_dict["message"]) - elif res.json()["status"] == 5004: - raise CompileError(res_dict["message"]) + response = requests.post(url, headers=headers, data=data) # type: requests.models.Response + + # TODO: completing status code checks + # assert response.ok + if response.status_code == 502: + logging.critical("Received a 502 Bad Gateway response. Please try again later.\n" + "If there is persistent failure, please report it on our github page.") + raise UserError() else: - task_id = res_dict["task_id"] - - if not (group in self.submit_history): - self.submit_history[group] = [task_id] + res_dict = response.json() + import pprint + pprint.pprint(res_dict) + if response.status_code in [201, 205]: + raise UserError(res_dict["message"]) + elif response.status_code == 5001: + raise CircuitError(res_dict["message"]) + elif response.status_code == 5003: + raise ServerError(res_dict["message"]) + elif response.status_code == 5004: + raise CompileError(res_dict["message"]) else: - self.submit_history[group].append(task_id) + task_id = res_dict["task_id"] + + if not (group in self.submit_history): + self.submit_history[group] = [task_id] + else: + self.submit_history[group].append(task_id) - return ExecResult(res_dict, qc.measures) + return ExecResult(res_dict, qc.measures) def retrieve(self, taskid: str) -> ExecResult: """ @@ -233,16 +256,19 @@ def retrieve(self, taskid: str) -> ExecResult: url = User.exec_recall_api headers = {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 'api_token': self.user.api_token} - res = requests.post(url, headers=headers, data=data) + response = requests.post(url, headers=headers, data=data) - res_dict = json.loads(res.text) + res_dict = response.json() measures = eval(res_dict["measure"]) + if measures is None: + raise Exception("Measure info returned is None. This may be the error under repairing." + " See https://github.com/ScQ-Cloud/pyquafu/issues/50") return ExecResult(res_dict, measures) def retrieve_group(self, group: str, - history: Dict = {}, + history: Dict = None, verbose: bool = True) -> List[ExecResult]: """ Retrieve the results of submited task by group name. diff --git a/src/quafu/users/userapi.py b/src/quafu/users/userapi.py index 6d68108..093469c 100644 --- a/src/quafu/users/userapi.py +++ b/src/quafu/users/userapi.py @@ -1,4 +1,17 @@ -import json +# (C) Copyright 2023 Beijing Academy of Quantum Information Sciences +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import os import requests @@ -80,13 +93,13 @@ def _get_backends_info(self): """ Get available backends information """ - - backends_info = requests.post(url=self.backends_api, headers={"api_token": self.api_token}) - backends_info_dict = json.loads(backends_info.text) - if backends_info_dict["status"] == 201: - raise UserError(backends_info_dict["message"]) + headers = {"api_token": self.api_token} + response = requests.post(url=self.backends_api, headers=headers) + backends_info = response.json() + if response.status_code == 201: + raise UserError(backends_info["message"]) else: - return backends_info_dict["data"] + return backends_info["data"] def get_available_backends(self, print_info=True): """ @@ -97,9 +110,8 @@ def get_available_backends(self, print_info=True): self._available_backends = {info["system_name"]: Backend(info) for info in backends_info} if print_info: - print((" " * 5).join(["system_name".ljust(10), "qubits".ljust(10), "status".ljust(10)])) + print("\t ".join(["system_name".ljust(10), "qubits".ljust(5), "status"])) for backend in self._available_backends.values(): - print((" " * 5).join( - [backend.name.ljust(10), str(backend.qubit_num).ljust(10), backend.status.ljust(10)])) - + print("\t ".join( + [backend.name.ljust(10), str(backend.qubit_num).ljust(5), backend.status])) return self._available_backends From 1fed1a65f2ac19081fb54fc200a2dcabeecfc9c7 Mon Sep 17 00:00:00 2001 From: chensgit169 Date: Tue, 15 Aug 2023 19:22:24 +0800 Subject: [PATCH 08/11] fix measures 'NoneType' problem in retrieve cancel 'measures' as argument if init ExecResult, for it could be extracted directly from transpiled_circuit. --- src/quafu/results/results.py | 27 ++++++++++++++------------- src/quafu/tasks/tasks.py | 9 ++------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/quafu/results/results.py b/src/quafu/results/results.py index 3f3ce69..844bf4e 100644 --- a/src/quafu/results/results.py +++ b/src/quafu/results/results.py @@ -21,18 +21,8 @@ class ExecResult(Result): transpiled_circuit (QuantumCircuit): Quantum circuit transpiled on backend. """ - def __init__(self, input_dict, measures): + def __init__(self, input_dict): status_map = {0: "In Queue", 1: "Running", 2: "Completed", "Canceled": 3, 4: "Failed"} - self.measures = measures - self.task_status = status_map[input_dict["status"]] - self.res = eval(input_dict['res']) - self.counts = OrderedDict(sorted(self.res.items(), key=lambda s: s[0])) - self.logicalq_res = {} - cbits = list(self.measures.values()) - for key, values in self.counts.items(): - newkey = "".join([key[i] for i in cbits]) - self.logicalq_res[newkey] = values - self.taskid = input_dict['task_id'] self.taskname = input_dict['task_name'] self.transpiled_openqasm = input_dict["openqasm"] @@ -40,10 +30,21 @@ def __init__(self, input_dict, measures): self.transpiled_circuit = QuantumCircuit(0) self.transpiled_circuit.from_openqasm(self.transpiled_openqasm) self.measure_base = [] + + self.measures = self.transpiled_circuit.measures + self.task_status = status_map[input_dict["status"]] + self.res = eval(input_dict['res']) + + self.counts = OrderedDict(sorted(self.res.items(), key=lambda s: s[0])) + self.logicalq_res = {} + for bit_str, count in self.counts.items(): + newkey = "".join([bit_str[i] for i in self.measures.values()]) + self.logicalq_res[newkey] = count + total_counts = sum(self.counts.values()) self.probabilities = {} - for key in self.counts: - self.probabilities[key] = self.counts[key] / total_counts + for bit_str in self.counts: + self.probabilities[bit_str] = self.counts[bit_str] / total_counts def calculate_obs(self, pos): """ diff --git a/src/quafu/tasks/tasks.py b/src/quafu/tasks/tasks.py index 8390525..2368bf8 100644 --- a/src/quafu/tasks/tasks.py +++ b/src/quafu/tasks/tasks.py @@ -243,7 +243,7 @@ def send(self, else: self.submit_history[group].append(task_id) - return ExecResult(res_dict, qc.measures) + return ExecResult(res_dict) def retrieve(self, taskid: str) -> ExecResult: """ @@ -259,12 +259,7 @@ def retrieve(self, taskid: str) -> ExecResult: response = requests.post(url, headers=headers, data=data) res_dict = response.json() - measures = eval(res_dict["measure"]) - if measures is None: - raise Exception("Measure info returned is None. This may be the error under repairing." - " See https://github.com/ScQ-Cloud/pyquafu/issues/50") - - return ExecResult(res_dict, measures) + return ExecResult(res_dict) def retrieve_group(self, group: str, From 9c48094e0f931bc38dfcb6dae11175da4ba20b8b Mon Sep 17 00:00:00 2001 From: chensgit169 Date: Thu, 17 Aug 2023 19:08:18 +0800 Subject: [PATCH 09/11] add OracleGate class and its metaclass provide customize_gate as user-function to customize QuantumGate --- src/quafu/circuits/quantum_circuit.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/quafu/circuits/quantum_circuit.py b/src/quafu/circuits/quantum_circuit.py index e7f905c..c87ede1 100644 --- a/src/quafu/circuits/quantum_circuit.py +++ b/src/quafu/circuits/quantum_circuit.py @@ -39,17 +39,16 @@ def __init__(self, num: int, *args, **kwargs): Args: num (int): Total qubit number used """ - # self.qregs = [QuantumRegister(num)] if num > 0 else [] - self.num = num + self.qregs = [QuantumRegister(num)] if num > 0 else [] self.gates = [] self.openqasm = "" self.circuit = [] self.measures = {} self._used_qubits = [] - # @property - # def num(self): - # return np.sum([len(qreg) for qreg in self.qregs]) + @property + def num(self): + return np.sum([len(qreg) for qreg in self.qregs]) @property def used_qubits(self) -> List: @@ -366,6 +365,18 @@ def to_openqasm(self) -> str: self.openqasm = qasm return qasm + def wrap_to_gate(self, name: str): + """ + Wrap the circuit to a subclass of QuantumGate, create by metaclass. + """ + # TODO: error check + from instruction.qu_gate.quantum_gate import customize_gate + customized = customize_gate(cls_name=name.capitalize(), + sd_name=name, + qubit_num=self.qbit_num, + gate_structure=self.gates) + return customized + def id(self, pos: int) -> "QuantumCircuit": """ Identity gate. From 7442786ba98032ba8b6c38dd3a7930c9b73fc720 Mon Sep 17 00:00:00 2001 From: chensgit169 Date: Thu, 17 Aug 2023 19:09:27 +0800 Subject: [PATCH 10/11] add OracleGate class and its metaclass provide customize_gate as user-function to customize QuantumGate --- .../elements/quantum_element/quantum_gate.py | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/quafu/elements/quantum_element/quantum_gate.py b/src/quafu/elements/quantum_element/quantum_gate.py index 199224b..a5653dd 100644 --- a/src/quafu/elements/quantum_element/quantum_gate.py +++ b/src/quafu/elements/quantum_element/quantum_gate.py @@ -1,4 +1,4 @@ -from abc import ABC, abstractmethod +from abc import ABC, abstractmethod, ABCMeta from typing import List, Union, Iterable import numpy as np @@ -207,3 +207,57 @@ def get_targ_matrix(self, reverse_order=False): tensorm = targ_matrix.reshape([2] * 2 * qnum) targ_matrix = np.transpose(tensorm, order).reshape([dim, dim]) return targ_matrix + + +class OracleGate(QuantumGate): + name = None + gate_structure = [] + + def __init__(self, pos, paras, label: str = None): + super().__init__(pos=pos, paras=paras) + self.label = label if label is not None else self.name + + @property + def matrix(self): + # TODO: this should be finished according to usage in simulation + # to avoid store very large matrix + raise NotImplemented + + +class OracleGateMeta(ABCMeta): + """ + Metaclass to create OracleGate class which is its instance. + """ + def __init__(cls, name, bases, attrs): + for attr_name in ['cls_name', 'gate_structure', 'qubit_num']: + assert attr_name in attrs, f"OracleGateMeta: {attr_name} is required." + super().__init__(name, bases, attrs) + cls.name = attrs.__getitem__('cls_name') + cls.gate_structure = attrs.__getitem__('gate_structure') + cls.qubit_num = attrs.__getitem__('qubit_num') + # TODO: check gate_structure and resolve it + + +def customize_gate(cls_name: str, + gate_structure: list, + qubit_num: int, + ): + """ + helper function to create customized gate class + :param cls_name: + :param gate_structure: + :param qubit_num: + :return: + """ + if cls_name in QuantumGate.gate_classes: + raise ValueError(f"Gate class {cls_name} already exists.") + + attrs = {'cls_name': cls_name, + 'gate_structure': gate_structure, # TODO: translate + 'qubit_num': qubit_num, + } + + customized_cls = OracleGateMeta(cls_name, (OracleGate,), attrs) + assert issubclass(customized_cls, OracleGate) + QuantumGate.register_gate(customized_cls) + return customized_cls From ed594623044a52bb0dcf61b91fa39a9f767e88b5 Mon Sep 17 00:00:00 2001 From: chensgit169 Date: Thu, 17 Aug 2023 19:23:26 +0800 Subject: [PATCH 11/11] add OracleGate class and its metaclass Provide customize_gate as user-function to customize QuantumGate and integrate into QuantumCircuit --- src/quafu/circuits/quantum_circuit.py | 9 ++++----- src/quafu/elements/quantum_element/quantum_gate.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/quafu/circuits/quantum_circuit.py b/src/quafu/circuits/quantum_circuit.py index 6e7ccd7..c947713 100644 --- a/src/quafu/circuits/quantum_circuit.py +++ b/src/quafu/circuits/quantum_circuit.py @@ -56,7 +56,7 @@ def parameterized_gates(self): @property def num(self): - return np.sum([len(qreg) for qreg in self.qregs]) + return sum([len(qreg) for qreg in self.qregs]) @property def used_qubits(self) -> List: @@ -428,10 +428,9 @@ def wrap_to_gate(self, name: str): Wrap the circuit to a subclass of QuantumGate, create by metaclass. """ # TODO: error check - from instruction.qu_gate.quantum_gate import customize_gate - customized = customize_gate(cls_name=name.capitalize(), - sd_name=name, - qubit_num=self.qbit_num, + from quafu.elements.quantum_element.quantum_gate import customize_gate + customized = customize_gate(cls_name=name.lower(), + qubit_num=self.num, gate_structure=self.gates) return customized diff --git a/src/quafu/elements/quantum_element/quantum_gate.py b/src/quafu/elements/quantum_element/quantum_gate.py index a5653dd..e41cf22 100644 --- a/src/quafu/elements/quantum_element/quantum_gate.py +++ b/src/quafu/elements/quantum_element/quantum_gate.py @@ -1,3 +1,17 @@ +# (C) Copyright 2023 Beijing Academy of Quantum Information Sciences +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from abc import ABC, abstractmethod, ABCMeta from typing import List, Union, Iterable