Source code for qlazy.QCirc

# -*- coding: utf-8 -*-
""" Quantum Circuit """
import ctypes
from collections import Counter
from fractions import Fraction
import re
import random
import pickle

import qlazy.config as cfg
from qlazy.util import (is_unitary_gate, is_clifford_gate, is_non_clifford_gate,
                        is_measurement_gate, is_reset_gate,
                        get_qgate_qubit_num, get_qgate_param_num)
from qlazy.QObject import QObject

[docs]def string_to_args(s): # for from_qasm """ convert string to args """ token = s.split(' ') if len(token) == 1: args = token elif len(token) > 1: args = [token[0], ' '.join(token[1:])] else: raise ValueError("can't split string {}.".format(s)) return args
[docs]def init_qc_canvas(qubit_num, cmem_num): # for show method qlen = len(str(qubit_num - 1)) clen = len(str(cmem_num)) qc_canvas = ["q[{:0{digits}d}] -" .format(i, digits=qlen) + "-" * (clen - 1) for i in range(qubit_num)] if cmem_num > 0: qc_canvas.append('c' + ' ' * (qlen+1) + '=/=' + '=' * (clen - 1)) qc_canvas.append(' ' + ' ' * (qlen+1) + ' ' + str(cmem_num) + ' ') return qc_canvas
[docs]def append_qc_canvas(qc_canvas, gates, qubit_num, cmem_num): # for show method if len(gates) == 1: # append single gate g = gates[0] if get_qgate_param_num(g['kind']) == 0: g_label = cfg.GATE_LABEL[g['kind']] + '-' else: g_label = cfg.GATE_LABEL[g['kind']] + '(' + str(g['para'][0]*g['para'][2]) + ')-' if get_qgate_qubit_num(g['kind']) == 1 or is_reset_gate(g['kind']): qc_canvas[g['qid'][0]] += g_label if g['ctrl'] is not None: for j in range(g['qid'][0] + 1, qubit_num): qc_canvas[j] += '|' qc_canvas[qubit_num] += '^' qc_canvas[qubit_num + 1] += (str(g['ctrl']) + ' ') elif get_qgate_qubit_num(g['kind']) == 2: qc_canvas[g['qid'][0]] += '*' qc_canvas[g['qid'][1]] += g_label qid_min = min(g['qid'][0], g['qid'][1]) qid_max = max(g['qid'][0], g['qid'][1]) for i in range(qid_min + 1, qid_max): qc_canvas[i] += '|' if g['ctrl'] is not None: for j in range(qid_max, qubit_num): qc_canvas[j] += '|' qc_canvas[qubit_num] += '^' qc_canvas[qubit_num + 1] += (str(g['ctrl']) + ' ') elif is_measurement_gate(g['kind']): qc_canvas[g['qid'][0]] += g_label qc_canvas[qubit_num + 1] += (str(g['c']) + ' ') for i in range(g['qid'][0] + 1, qubit_num): qc_canvas[i] += '|' qc_canvas[qubit_num] += 'v' else: # append group of 1-qubit gates for g in gates: if get_qgate_param_num(g['kind']) == 0: g_label = cfg.GATE_LABEL[g['kind']] + '-' else: g_label = cfg.GATE_LABEL[g['kind']] + '(' + str(g['para'][0]) + ')-' qc_canvas[g['qid'][0]] += g_label # padding canvas_len = max(map(len, qc_canvas)) for i in range(len(qc_canvas)): if i < qubit_num: qc_canvas[i] += ('-' * (canvas_len - len(qc_canvas[i]))) elif i == qubit_num: qc_canvas[i] += ('=' * (canvas_len - len(qc_canvas[i]))) else: qc_canvas[i] += (' ' * (canvas_len - len(qc_canvas[i])))
[docs]class QCirc(ctypes.Structure, QObject): """ Quantum Circuit Attributes ---------- qubit_num : int qubit number of the quantum state (= log(state_num)). cmem_num : int number of the classical register. gate_num : int number of gates in the quantum circuit. first: object first gate of the quantum circuit. last: object last gate of the quantum circuit. tag_table: object tag table.. """ _fields_ = [ ('qubit_num', ctypes.c_int), ('cmem_num', ctypes.c_int), ('gate_num', ctypes.c_int), ('first', ctypes.c_void_p), ('last', ctypes.c_void_p), ('tag_table', ctypes.c_void_p), ] def __new__(cls, **kwargs): """ Parameters ---------- None Returns ------- qcirc : instance (QCirc) """ obj = qcirc_init() qcirc = ctypes.cast(obj.value, ctypes.POINTER(cls)).contents return qcirc def __str__(self): return self.to_string() def __add__(self, qc): qcirc = self.merge(qc) return qcirc def __iadd__(self, qc): self.merge_mutable(qc) return self def __mul__(self, other): return self.multiple(other) __rmul__ = __mul__ def __eq__(self, qc): ans = self.is_equal(qc) return ans def __ne__(self, qc): ans = not self.is_equal(qc) return ans
[docs] def to_string(self): """ get string of the circuit (qlazy format). Parameters ---------- None Returns ------- qcirc_str : str """ qc = self.clone() qcirc_str = "" while True: kind = qc.kind_first() if kind is None: break (kind, qid, para, c, ctrl, tag) = qc.pop_gate() term_num = get_qgate_qubit_num(kind) if kind in (cfg.MEASURE, cfg.RESET): term_num = 1 para_num = get_qgate_param_num(kind) gate_str = cfg.GATE_STRING[kind] qid_str = " ".join(map(str, [qid[i] for i in range(term_num)])) qid_str.strip() if para_num == 0: para_str = "" else: para_str = "{}".format(para[0]*para[2]) para_str = "(" + para_str+ ")" if c is None: c_str = "" else: c_str = "-> {}".format(c) if ctrl is None: ctrl_str = "" else: ctrl_str = ", ctrl = {}".format(ctrl) if tag is None or tag == "": tag_str = "" else: tag_str = " #" + tag qcirc_str += ("{0:}{2:} {1:} {3:}{4:}{5:}\n" .format(gate_str, qid_str, para_str, c_str, ctrl_str, tag_str)) return qcirc_str.strip()
[docs] @classmethod def from_qasm(cls, string): """ get QCirc instance from OpenQASM 2.0 string. Parameters ---------- None Returns ------- qcirc : instance of QCirc Notes ----- Non-unitary gates (measure, reset) and user customized gates are not supported. Supported gates are 'x', 'y', 'z', 'h', 's', 'sdg', 't', 'tdg', 'cx', 'cz', 'ch', 'rx', 'rz', 'crz'. """ line_list = string.split('\n') # line #0 line_count = 0 args = string_to_args(line_list[line_count]) if args[0] == 'OPENQASM' and args[1] == '2.0;': line_count += 1 else: raise ValueError("line #0 must be 'OPENQASM 2.0;'") # line #1 args = string_to_args(line_list[line_count]) if args[0] == 'include' and args[1] == '"qelib1.inc";': line_count += 1 else: raise ValueError("""line #1 must be 'include "qelib1.inc;"' """) # line #2 (#3) args = string_to_args(line_list[line_count]) if args[0] == 'qreg' and re.match('q', args[1]).group() == 'q': line_count += 1 else: raise ValueError("""line #2 must be 'qreg q[<int>];"' """) # line (#3) #4 ... qcirc = cls() for i in range(line_count, len(line_list)): args = string_to_args(line_list[i]) if args[0] == '': continue if args[0] in ('measure', 'reset'): raise ValueError("sorry, 'measure', 'reset' is not supported.") if args[0] in ('x', 'y', 'z', 'h', 's', 'sdg', 't', 'tdg'): res = re.search(r"q\[|\];", args[1]) if res is not None: q = int(re.sub(r"q\[|\];", "", args[1])) else: raise ValueError("argument '{}' is not valid for '{}' gate." .format(args[1], args[0])) if args[0] == 'x': qcirc.x(q) elif args[0] == 'z': qcirc.z(q) elif args[0] == 'h': qcirc.h(q) elif args[0] == 's': qcirc.s(q) elif args[0] == 'sdg': qcirc.s_dg(q) elif args[0] == 't': qcirc.t(q) elif args[0] == 'tdg': qcirc.t_dg(q) elif args[0] in ('cx', 'cz', 'ch'): res = re.search(r"q\[[0-9]+\],\s*q\[[0-9]+\];", args[1]) if res is not None: qubits = args[1].split(',') q0 = int(re.sub(r"q\[|\]", "", qubits[0])) q1 = int(re.sub(r"q\[|\];", "", qubits[1])) else: raise ValueError("argument '{}' is not valid for '{}' gate." .format(args[1], args[0])) if args[0] == 'cx': qcirc.cx(q0, q1) elif args[0] == 'cz': qcirc.cz(q0, q1) elif args[0] == 'ch': qcirc.ch(q0, q1) elif (re.match('rx', args[0]) is not None or re.match('rz', args[0]) is not None): res = re.search(r"q\[|\];", args[1]) if res is not None: q = int(re.sub(r"q\[|\];", "", args[1])) else: raise ValueError(("argument '{}' is not valid for '{}' gate." .format(args[1], args[0]))) para_str = [s.strip('*').strip('/') for s in re.sub(r".+\(|\)", "", args[0]).split("pi")] if para_str[0] == '0': para = 0. else: denominator = 1. if para_str[0] == '': numerator = 1. else: numerator = float(para_str[0]) if para_str[1] == '': denominator = 1. else: denominator = float(para_str[1]) para = numerator / denominator if re.match('rx', args[0]) is not None: qcirc.rx(q, phase=para) elif re.match('rz', args[0]) is not None: qcirc.rz(q, phase=para) elif re.match('crz', args[0]) is not None: res = re.search(r"q\[[0-9]+\],\s*q\[[0-9]+\];", args[1]) if res is not None: qubits = args[1].split(',') q0 = int(re.sub(r"q\[|\]", "", qubits[0])) q1 = int(re.sub(r"q\[|\];", "", qubits[1])) else: raise ValueError(("argument '{}' is not valid for '{}' gate." .format(args[1], args[0]))) para_str = [s.strip('*').strip('/') for s in re.sub(r".+\(|\)", "", args[0]).split("pi")] if para_str[0] == '0': para = 0. else: denominator = 1. if para_str[0] == '': numerator = 1. else: numerator = float(para_str[0]) if para_str[1] == '': denominator = 1. else: denominator = float(para_str[1]) para = numerator / denominator if re.match('crz', args[0]) is not None: qcirc.crz(q0, q1, phase=para) else: raise ValueError("{} gate is not supported".format(args[0])) return qcirc
[docs] @classmethod def from_qasm_file(cls, file_path): """ get QCirc instance from OpenQASM 2.0 file. Parameters ---------- file_path: str file path name of OpenQASM 2.0 file Returns ------- qcirc : instance of QCirc Notes ----- Non-unitary gates (measure, reset) and user customized gates are not supported. Supported gates are 'x', 'y', 'z', 'h', 's', 'sdg', 't', 'tdg', 'cx', 'cz', 'ch', 'rx', 'rz', 'crz'. """ with open(file_path, mode='r') as f: s = f.read() qcirc = cls.from_qasm(s) return qcirc
[docs] def to_qasm(self): """ get OpenQASM 2.0 string of the circuit. Parameters ---------- None Returns ------- qcirc_str : str """ qc = self.clone() # header and include file qcirc_str = """OPENQASM 2.0;\n""" qcirc_str += """include "qelib1.inc";\n""" # definition of qreg, creg qcirc_str += """qreg q[{}];\n""".format(self.qubit_num) for i in range(self.cmem_num): qcirc_str += """creg c{}[1];\n""".format(i) # description of each gate operation while True: kind = qc.kind_first() if kind is None: break (kind, qid, para, c, ctrl, tag) = qc.pop_gate() term_num = get_qgate_qubit_num(kind) if kind in (cfg.MEASURE, cfg.RESET): term_num = 1 para_num = get_qgate_param_num(kind) para[0] *= para[2] para_frac = [Fraction(str(p)) for p in para] gate_str = cfg.GATE_STRING_QASM[kind] qid_str = ",".join(["q[" + str(qid[i]) + "]" for i in range(term_num)]) qid_str.strip() if para_num == 0: if kind == cfg.CONTROLLED_S: para_str = "(pi/2)" elif kind == cfg.CONTROLLED_S_: para_str = "(-pi/2)" elif kind == cfg.CONTROLLED_T: para_str = "(pi/4)" elif kind == cfg.CONTROLLED_T_: para_str = "(-pi/4)" else: para_str = "" else: para_str_list = [] for i, p in enumerate(para_frac): if i >= para_num: break if p.numerator == 0: para_str_list.append("0") elif p.numerator == 1: para_str_list.append("pi/"+ str(p.denominator)) else: para_str_list.append(str(p.numerator) + "*pi/" + str(p.denominator)) para_str = ",".join(para_str_list) para_str = "(" + para_str+ ")" if c is None: c_str = "" else: c_str = " -> c{}[0]".format(c) if ctrl is None: ctrl_str = "" else: ctrl_str = "if(c{}==1) ".format(ctrl) qcirc_str += ("{4:}{0:}{2:} {1:}{3:};\n" .format(gate_str, qid_str, para_str, c_str, ctrl_str)) return qcirc_str.strip()
[docs] def to_qasm_file(self, file_path): """ write to OpenQASM 2.0 file. Parameters ---------- file_path: str file path name of OpenQASM 2.0 file Returns ------- None """ s = self.to_qasm() with open(file_path, mode='w') as f: f.write(s)
[docs] def show(self, width=100): """ show the circuit Parameters ---------- width : int, default - 100 width for line breaks and display long quantum circuit Returns ------- None """ qc = self.clone() qubit_num = self.qubit_num cmem_num = self.cmem_num gate_num = self.gate_num qc_canvas = init_qc_canvas(qubit_num, cmem_num) gates = [] qids = [] while True: kind = qc.kind_first() if kind is None: break (kind, qid, para, c, ctrl, tag) = qc.pop_gate() gate = {'kind': kind, 'qid': qid, 'para': para, 'c': c, 'ctrl': ctrl} if ((get_qgate_qubit_num(kind) == 1 or is_reset_gate(kind)) and ctrl is None): if qid[0] in qids: append_qc_canvas(qc_canvas, gates, qubit_num, cmem_num) gates = [gate] qids = [qid[0]] else: gates.append(gate) qids.append(qid[0]) else: if gates != []: append_qc_canvas(qc_canvas, gates, qubit_num, cmem_num) gates = [] qids = [] append_qc_canvas(qc_canvas, [gate], qubit_num, cmem_num) if gates != []: append_qc_canvas(qc_canvas, gates, qubit_num, cmem_num) pos_start = 0 pos_end = width while True: if pos_start >= len(qc_canvas[0]): break for line in qc_canvas: print(line[pos_start:pos_end]) print() pos_start += width pos_end +=width
[docs] def clone(self): """ clone quantum circuit. Parameters ---------- None Returns ------- qcirc : instance of QCirc quantum circuit """ obj = qcirc_copy(self) qcirc = ctypes.cast(obj.value, ctypes.POINTER(self.__class__)).contents return qcirc
[docs] def merge(self, qc): """ merge two quantum circuits. Parameters ---------- qc : instance of QCirc quantum circuit (merged) Returns ------- qcirc : instance of QCirc new quantum circuit (merge result) """ obj = qcirc_merge(self, qc) qcirc = ctypes.cast(obj.value, ctypes.POINTER(self.__class__)).contents return qcirc
[docs] def merge_mutable(self, qc): """ merge a quantum circuit with another one. Parameters ---------- qc : instance of QCirc quantum circuit (merged) Returns ------- self : instance of QCirc quantum circuit (merge result) Notes ----- This method changes original quantum circuit. """ qcirc_merge_mutable(self, qc) return self
[docs] def multiple(self, other): """ integer multiple for quantum circuit Parameters ---------- other : instance of QCirc / int quantum circuit / number Returns ------- qc : instance of QCirc quantum circuit after multiplication """ if isinstance(other, int) and other > 0: qc = self.clone() qc_out = self.clone() for i in range(other-1): qc_out = qc_out.merge(qc) else: raise TypeError("Can't multiple QCirc with {} (type:{})".format(other, type(other))) return qc_out
[docs] def is_equal(self, qc): """ eaual or not. Parameters ---------- qc : instance of QCirc quantum circuit (merged) Returns ------- qcirc : instance of QCirc quantum circuit (result) """ ans = qcirc_is_equal(self, qc) return ans
[docs] def kind_first(self): """ get kind of first gate of the circuit. Parameters ---------- None Returns ------- kind_list : int kind of first quantum gate of quantum circuit Note ---- return None if none of gates included """ kind = qcirc_kind_first(self) return kind
[docs] def kind_list(self): """ get list of kinds from the circuit. Parameters ---------- None Returns ------- kinds : list of kind kinds of the quantum circuit Note ---- return None if none of gates included """ qc = self.clone() kind_list = [] while True: kind = qc.kind_first() if kind is None: break kind_list.append(kind) qc.pop_gate() return kind_list
[docs] def get_gates(self): """ get list of gates from the circuit. Parameters ---------- None Returns ------- gates : list of dict gates of the quantum circuit gates = [{'kind':kind, 'qid':qid, 'phase':phase, 'cid':cid, 'ctrl':ctrl}, ...] - kind: gate name - qid: qubit id - phase: phase parameter (non-zero only for rotation gates) - para: [phase, gphase, factor] * gphase means global phase used only when adding control to p gate - cid: classical register id (set only for measurement gate) - ctrl: classical register id to control gate operation """ qc = self.clone() gates = [] while True: kind = qc.kind_first() if kind is None: break (kind, qid, para, c, ctrl, tag) = qc.pop_gate() qid = [q for q in qid if q >= 0] if c is None: cid = None else: cid = [c] gates.append({'kind': cfg.GATE_STRING[kind], 'qid': qid, 'para': para, 'cid': cid, 'ctrl': ctrl}) return gates
[docs] def add_gates(self, gates=None): """ add list of gates to the circuit. Parameters ---------- gates : list of dict gates of the quantum circuit gates = [{'kind':kind, 'qid':qid, 'phase':phase, 'cid':cid, 'ctrl':ctrl}, ...] - kind: gate name - qid: qubit id - phase: phase parameter (non-zero only for rotation gates) - cid: classical register id (set only for measurement gate) - ctrl: classical register id for controlling gate operation Returns ------- self: instance of QCirc circuit after adding gates Notes ----- This method changes original quantum circuit. """ if gates is None: raise ValueError("gates must be specified.") for g in gates: kind = cfg.GATE_KIND[g['kind']] qid = g['qid'] para = g['para'] if g['cid'] is None: c = None else: c = g['cid'][0] ctrl = g['ctrl'] self.append_gate(kind, qid, para, c, ctrl) return self
[docs] def dump(self, file_path): """ dump the circuit Parameters ---------- file_path: str file path of dump file Returns ------- None """ gates = self.get_gates() with open(file_path, mode='wb') as f: pickle.dump(gates, f)
[docs] def save(self, file_path): """ save the circuit Parameters ---------- file_path: str file path of dump file Returns ------- None """ self.dump(file_path)
[docs] @classmethod def load(cls, file_path): """ load the circuit Parameters ---------- file_path: str file path of dump file Returns ------- qcirc: instance of QCirc loaded circuit """ with open(file_path, mode='rb') as f: gates = pickle.load(f) qcirc = cls().add_gates(gates) return qcirc
[docs] def get_stats(self): """ get statistics of the circuit. Parameters ---------- None Returns ------- stats : dict {'qubit_num':qubit_num, 'cmem_num':cmem_num, 'gate_num':gate_num, 'gate_freq':gate_freq} - qubit_num: number of qubits - cmem_num: number of classical bits - gate_num: number of gates - gate_freq: frequency of gates (Counter) """ gate_list = [cfg.GATE_STRING[kind] for kind in self.kind_list()] gate_freq = Counter(gate_list) gatetype_list = [] for kind in self.kind_list(): if is_clifford_gate(kind) is True or is_non_clifford_gate(kind) is True: gatetype_list.append('unitary') if is_clifford_gate(kind) is True: gatetype_list.append('clifford') elif is_non_clifford_gate(kind) is True: gatetype_list.append('non-clifford') elif is_measurement_gate(kind) is True or is_reset_gate(kind) is True: gatetype_list.append('non-unitary') else: raise ValueError("unknown gate kind:{}".format(kind)) gatetype_freq = Counter(gatetype_list) stats = {'qubit_num': self.qubit_num, 'cmem_num':self.cmem_num, 'gate_num': len(gate_list), 'gate_freq': gate_freq, 'gatetype_freq': gatetype_freq} return stats
[docs] @classmethod def generate_random_gates(cls, qubit_num=0, gate_num=0, phase=None, prob=None, **kwargs): """ generate circuit including random gates. Parameters ---------- qubit_num: int number of qubits gate_num: int numbner of gates phase: tupple of float phases selected randomly prob: dict {'x':prob_x, 'z':prob_z, 'h':prob_h, 's':prob_s, 's_dg':prob_s_dg, 't':prob_t, 't_dg':prob_t_dg, 'rx':prob_rx, 'rz':prob_rz, 'cx':prob_cx, 'cz':prob_cz, 'ch':prob_ch, 'crz':prob_crz} - prob_x: probability of x - prob_z: probability of z - prob_h: probability of h - prob_s: probability of s - prob_s_dg: probability of s_dg - prob_t: probability of t - prob_t_dg: probability of t_dg - prob_rx: probability of rx - prob_rz: probability of rz - prob_cx: probability of cx - prob_cz: probability of cz - prob_ch: probability of ch - prob_crz: probability of crz Returns ------- qcirc: instance of QCirc generated circuit Examples -------- >>> qc = QCirc.generate_random_gates(qubit_num=5, gate_num=100, phase_unit=0.25, prob={'h':3, 'cx':7, 'rz':1}) Notes ----- * each probability values are normalized so that the sum of the probabilities is 1.0. * Phase parameters of rotation gates are selected randomly in the element of 'phase'. """ if ((isinstance(qubit_num, int) is not True or isinstance(gate_num, int) is not True or qubit_num < 1 or gate_num < 1)): raise ValueError("qubit_num and/or gate_num must be positive integer.") if phase is not None: if ((isinstance(phase, float) is not True and isinstance(phase, int) is not True and isinstance(phase, tuple) is not True)): raise ValueError("phase value(s) must be int/float of tuple.") total_prob = 0.0 for p in prob.values(): total_prob += p glist = [] plist = [] p = 0.0 for k, v in prob.items(): if v < 0.0: raise ValueError("probability must be positive value.") if k in ('x', 'z', 'h', 's', 's_dg', 't', 't_dg', 'rx', 'rz', 'cx', 'cz', 'ch', 'crz'): glist.append(k) p += v / total_prob plist.append(p) else: raise ValueError("gate '{}' is not supported.".format(k)) if plist[-1] != 1.0: plist[-1] = 1.0 qcirc = cls() TRY_MAX = 10 # for random generation max_q = -1 gate_count = 0 while gate_count < gate_num or max_q < qubit_num - 1: r = random.random() kind = None for i, p in enumerate(plist): if r <= p: kind = cfg.GATE_KIND[glist[i]] break term_num = get_qgate_qubit_num(kind) para_num = get_qgate_param_num(kind) if term_num == 1 and para_num == 0: # 1-qubit gate q0 = random.randint(0, qubit_num - 1) qcirc.append_gate(kind, [q0]) max_q = max(max_q, q0) gate_count += 1 elif term_num == 1 and para_num == 1: # 1-qubit and 1-parameter gate q0 = random.randint(0, qubit_num - 1) if phase is None: p = 0.0 elif isinstance(phase, (float, int)): p = phase else: p = random.choice(phase) qcirc.append_gate(kind, [q0], para=[p, 0., 0.]) max_q = max(max_q, q0) gate_count += 1 elif term_num == 2 and para_num == 0: # 2-qubit gate q0 = random.randint(0, qubit_num - 1) q1 = random.randint(0, qubit_num - 1) cnt = 0 while q0 == q1 and cnt < TRY_MAX: q1 = random.randint(0, qubit_num - 1) cnt += 1 if cnt >= TRY_MAX: raise ValueError(("can't generate qubit id for '{}' gate." .format(cfg.GATE_STRING[kind]))) qcirc.append_gate(kind, [q0, q1]) max_q = max(max_q, q0, q1) gate_count += 1 elif term_num == 2 and para_num == 1: # 2-qubit and 1-parameter gate q0 = random.randint(0, qubit_num - 1) q1 = random.randint(0, qubit_num - 1) cnt = 0 while q0 == q1 and cnt < TRY_MAX: q1 = random.randint(0, qubit_num - 1) cnt += 1 if cnt >= TRY_MAX: raise ValueError(("can't generate qubit id for '{}' gate." .format(cfg.GATE_STRING[kind]))) if phase is None: p = 0.0 elif isinstance(phase, (float, int)): p = phase else: p = random.choice(phase) qcirc.append_gate(kind, [q0, q1], para=[p, 0., 0.]) max_q = max(max_q, q0, q1) gate_count += 1 else: raise ValueError(("gate of term_num={}, param_num={} is not supported" .format(term_num, para_num))) # delete extra gates for _ in range(gate_count - gate_num): qcirc.pop_gate() return qcirc
[docs] def to_pyzx(self): """ get pyzx's Circuit instance. Parameters ---------- None Returns ------- zxqc: instance of pyzx's Circuit quantum circuit Notes ----- Non-unitary gates: measure, reset are not supported. """ from pyzx import Circuit zxqc = Circuit.from_qasm(self.to_qasm()) return zxqc
[docs] @classmethod def from_pyzx(cls, zxqc): """ get pyzx's Circuit instance. Parameters ---------- zxqc: instance of pyzx's Circuit quantum circuit Returns ------- qc: instance of QCirc quantum circuit Notes ----- Non-unitary gates: measure, reset are not supported. """ qc = cls.from_qasm(zxqc.to_qasm()) return qc
[docs] def optimize(self, *args, **kwargs): """ optimize the quantum circuit (using pyzx's full_optimize method). Parameters ---------- None Returns ------- qc: instance of QCirc optimized circuit Notes ----- Non-unitary gates: measure, reset are not supported. """ import pyzx as zx zxqc = self.to_pyzx() zxqc_opt = zx.optimize.full_optimize(zxqc) qc = self.__class__.from_pyzx(zxqc_opt) return qc
[docs] def equivalent(self, qc, *args, **kwargs): """ two quantum circuits are equivalent or not (using pyzx's verify_equality method). Parameters ---------- None Returns ------- ans: bool Notes ----- Non-unitary gates: measure, reset are not supported. """ from pyzx import Circuit zxqc_A = Circuit.from_qasm(self.to_qasm()) zxqc_B = Circuit.from_qasm(qc.to_qasm()) ans = zxqc_A.verify_equality(zxqc_B) return ans
[docs] def pop_gate(self): """ pop first gate of the circuit. Parameters ---------- None Returns ------- gate : tupple of (int, [int,int], [float,float,float], int, int) tupple of (kind, qid, para, c, ctrl) - kind ... kind of gate - qid ... qubit id list - para ... parameters for rotation - c ... classical register ID to store measured data (only for measurement gate) - ctrl ... classical register id to controll the gate Notes ----- This method changes original quantum circuit. """ (kind, qid, para, c, ctrl, tag) = qcirc_pop_gate(self) return (kind, qid, para, c, ctrl, tag)
[docs] def append_gate(self, kind=None, qid=None, para=None, c=None, ctrl=None, tag=None): """ append gate to the end of the circuit. Parameters ---------- kind : int kind of gate qid : list (int) list of qubit id para : list (float), default None list of parameters c : int, default None classical register id to store measured data ctrl : int, default None classical register id to controll the gate Returns ------- None """ # kind must be int if isinstance(kind, int) is not True: raise TypeError("kind must be int.") # qid must be list of int if isinstance(qid, list) is not True: raise TypeError("qid must be list.") for q in qid: if isinstance(q, int) is not True: raise TypeError("qid must be list of int.") # para is None or para must be list of float if para is not None: if isinstance(para, list) is True: for p in para: if isinstance(p, float) is not True: raise TypeError("para must be a list of float.") else: raise TypeError("para must be a list of float.") # c is None or c must be int if c is not None and isinstance(c, int) is not True: raise TypeError("c must be int.") # ctrl is None or ctrl must be int if ctrl is not None and isinstance(ctrl, int) is not True: raise TypeError("ctrl must be int.") # qcirc_append_gate(self, kind, qid, para, c, ctrl) qcirc_append_gate(self, kind, qid, para, c, ctrl, tag)
[docs] def split_unitary_non_unitary(self): """ split two part of the gate. Parameters ---------- None Returns ------- qc_pair : tupple of (QCirc, Qcirc) former part includes only unitary gates and later part includes non-unitary gate (measure or reset) first """ qc_unitary = self.__class__() qc_non_unitary = self.clone() while True: kind_ori = qc_non_unitary.kind_first() if kind_ori is None or kind_ori is cfg.MEASURE or kind_ori is cfg.RESET: break (kind, qid, para, c, ctrl, tag) = qc_non_unitary.pop_gate() qc_unitary.append_gate(kind, qid, para, c, ctrl, tag) qc_pair = (qc_unitary, qc_non_unitary) return qc_pair
[docs] def is_unitary(self): """ the quantum circuit is unitary or not Parameters ---------- None Returns ------- ans : bool True if the quantum circuit unitary, False if otherwise """ ans = True for kind in self.kind_list(): if is_unitary_gate(kind) is False: ans = False break return ans
[docs] def is_clifford(self): """ the quantum circuit is clifford or not Parameters ---------- None Returns ------- ans : bool True if the quantum circuit unitary, False if otherwise """ ans = True for kind in self.kind_list(): if is_clifford_gate(kind) is False: ans = False break return ans
[docs] def all_gates_measurement(self): """ gates of the qcirc are all measurement Parameters ---------- None Returns ------- ans : bool True if all gates are measurement, False if otherwise """ if self.kind_first() is None: return False ans = True qcirc = self.clone() while True: kind = qcirc.kind_first() if kind is None: break (kind, qid, para, c, ctrl, tag) = qcirc.pop_gate() if kind is not cfg.MEASURE: ans = False break return ans
def __del__(self): qcirc_free(self)
[docs] def remap(self, qid=None, cid=None): """ remap qubit id and cmem id of quantum circuit Parameters ---------- qid : list (int) list of qubit id (quantum register) cid : list (int) list of cmem id (classical memory or classical register) Returns ------- qcirc : instance of QCirc new quantum circuit after remapping Examples -------- >>> qc = QCirc().h(0).cx(0,1).measure(qid=[0,1], cid=[0,1]) >>> qc.show() q[0] -H-*-M--- q[1] ---X-|-M- c =/=====v=v= 2 0 1 >>> qc_new1 = qc.remap(qid=[1,0], cid=[1,0]) >>> qc_new1.show() q[0] ---X---M- q[1] -H-*-M-|- c =/=====v=v= 2 1 0 >>> qc_new2 = qc.remap(qid=[2,1], cid=[1,0]) >>> qc_new2.show() q[0] --------- q[1] ---X---M- q[2] -H-*-M-|- c =/=====v=v= 2 1 0 Notes ----- Length of the qid must be equal to qubit_num of the original quantum circut. Length of cid must be equal to cmem_num of the original quantum circut. Elements of the qid and the cid must not be duplicated. """ if qid is None and cid is None: return self.clone() elif qid is None: qid = list(range(self.qubit_num)) elif cid is None: cid = list(range(self.cmem_num)) # check qid if all([isinstance(q, int) and q>=0 for q in qid]): pass else: raise TypeError("qid must be a list of zero or more integer.") if len(qid) != self.qubit_num: raise ValueError("length of qid must be equal to the qubit number of the quantum circuit:{}.".format(self.qubit_num)) if len(set(qid)) != len(qid): raise ValueError("elements of qid must not be duplicated.") # check cid if all([isinstance(c, int) and c>=0 for c in cid]): pass else: raise TypeError("cid must be a list of zero or more integer.") if len(cid) != self.cmem_num: raise ValueError("length of cid must be equal to the cmem number of the quantum circuit:{}.".fomat(self.cmem_num)) if len(set(cid)) != len(cid): raise ValueError("elements of cid must not be duplicated.") qcirc = QCirc() gates = self.get_gates() for g in gates: if g['qid'] is not None: g['qid'] = [qid[q] for i,q in enumerate(g['qid'])] if g['cid'] is not None: g['cid'] = [cid[c] for i,c in enumerate(g['cid'])] if g['ctrl'] is not None: g['ctrl'] = cid[g['ctrl']] qcirc.add_gates(gates) return qcirc
[docs] def set_params(self, params): """ set parameters for each tag Parameters ---------- params : dict tag and phase dictionary ex) {'tag1': phase1, 'tag2': phase2, ...} Returns ------- None Examples -------- >>> qc = QCirc().h(0).rz(0, tag='foo').rx(0, tag='bar') >>> qc.set_params(params={'foo': 0.2, 'bar': 0.4}) >>> print(qc) h 0 rz(0.2) 0 rx(0.4) 0 >>> qc.set_params(params={'foo': 0.3, 'bar': 0.5}) >>> print(qc) h 0 rz(0.3) 0 rx(0.5) 0 """ if not isinstance(params, dict): raise TypeError("params must be dict.") qcirc_set_params(self, params)
[docs] def get_tag_phase(self, tag): """ get parameter (= phase) for the tag Parameters ---------- tag : str tag of phase parameter for parametric quantum circuit. Returns ------- phase : float rotation angle (unit of angle is PI radian) for the tag. Examples -------- >>> qc = QCirc().h(0).rz(0, tag='foo').rx(0, tag='bar') >>> qc.set_params(params={'foo': 0.2, 'bar': 0.4}) >>> print(qc.get_tag_phase('foo')) 0.2 """ if not isinstance(tag, str): raise TypeError("tag must be str.") phase = qcirc_get_tag_phase(self, tag) return phase
[docs] def get_params(self): """ get parameters for each tag Parameters ---------- None Returns ------- params : dict tag and phase dictionary ex) {'tag1': phase1, 'tag2': phase2, ...} Examples -------- >>> qc = QCirc().h(0).rz(0, tag='foo').rx(0, tag='bar') >>> qc.set_params(params={'foo': 0.2, 'bar': 0.4}) >>> print(qc.get_params()) {'foo': 0.2, 'bar': 0.4} """ tag_list = qcirc_get_tag_list(self) if len(tag_list) == 0: params = None else: params = {tag:self.get_tag_phase(tag) for tag in tag_list} return params
[docs] def add_control(self, qctrl=None): """ add control qubit to quantum circuit Parameters ---------- qctrl : int control qubit id Returns ------- qc_out : instance of QCirc quantum circuit after adding control qubit """ gates = self.get_gates() for g in gates: if qctrl in g['qid']: raise ValueError("qctrl={} is not allowed because it is already used.".format(qctrl)) qc = self.clone() qc_out = QCirc() gid = 0 while True: kind = qc.kind_first() if kind is None: break (kind, qid, para, c, ctrl, tag) = qc.pop_gate() self.__add_controll_gate(qc_out, kind, qid, para, c, ctrl, qctrl, tag) gid += 1 return qc_out
def __add_controll_gate(self, qc, kind, qid, para, c, ctrl, qctrl, tag): # 1-qubit gate if kind == cfg.PAULI_X: qc.cx(qctrl, qid[0], ctrl=ctrl) elif kind == cfg.PAULI_Z: qc.cz(qctrl, qid[0], ctrl=ctrl) elif kind == cfg.HADAMARD: qc.ch(qctrl, qid[0], ctrl=ctrl) elif kind == cfg.PHASE_SHIFT_S: qc.cs(qctrl, qid[0], ctrl=ctrl) elif kind == cfg.PHASE_SHIFT_S_: qc.cs_dg(qctrl, qid[0], ctrl=ctrl) elif kind == cfg.PHASE_SHIFT_T: qc.ct(qctrl, qid[0], ctrl=ctrl) elif kind == cfg.PHASE_SHIFT_T_: qc.ct_dg(qctrl, qid[0], ctrl=ctrl) elif kind == cfg.ROTATION_X: qc.crx(qctrl, qid[0], phase=para[0], ctrl=ctrl, tag=tag, fac=para[2]) elif kind == cfg.ROTATION_Z: qc.crz(qctrl, qid[0], phase=para[0], ctrl=ctrl, tag=tag, fac=para[2]) if para[1] != 0.0: # for the p gate qc.rz(qctrl, phase=para[1]) # 2-qubit gate elif kind == cfg.CONTROLLED_X: qc.ccx(qctrl, qid[0], qid[1], ctrl=ctrl) elif kind == cfg.CONTROLLED_Z: qc.h(qid[1], ctrl=ctrl) qc.ccx(qctrl, qid[0], qid[1], ctrl=ctrl) qc.h(qid[1], ctrl=ctrl) elif kind == cfg.CONTROLLED_H: q0, q1 = qid[0], qid[1] qc.cry(qctrl, q1, phase=-0.25, ctrl=ctrl).ccx(qctrl, q0, q1, ctrl=ctrl).crz(qctrl, q1, phase=-0.5, ctrl=ctrl) qc.ccx(qctrl, q0, q1, ctrl=ctrl).crz(qctrl, q1, phase=0.5, ctrl=ctrl).cry(qctrl, q1, phase=0.25, ctrl=ctrl) qc.crz(qctrl, q0, phase=0.5, ctrl=ctrl) elif kind == cfg.CONTROLLED_RZ: qc.crz(qctrl, qid[1], phase=para[0], ctrl=ctrl, tag=tag, fac=0.5*para[2]) qc.ccx(qctrl, qid[0], qid[1], ctrl=ctrl) qc.crz(qctrl, qid[1], phase=para[0], ctrl=ctrl, tag=tag, fac=-0.5*para[2]) qc.ccx(qctrl, qid[0], qid[1], ctrl=ctrl) # non-unitary gate elif kind == cfg.MEASURE: qc.measure(qid=[qid[0]], cid=[c]) elif kind == cfg.RESET: qc.reset(qid=[qid[0]]) else: raise ValueError("not supported quantum gate.") # operate gate
[docs] def operate_gate(self, kind=None, qid=None, cid=None, phase=0.0, gphase=0.0, fac=1.0, tag=None, ctrl=None): """ operate gate Parameters ---------- kind : int kind of the gate qid : list quantum id list cid : list classical register (memory) id list phase : float phase for rotation gate gphase : float global phase for rotation gate fac : float factor of phase value Returns ------- self : instance of QCirc quantum circuit """ if kind == cfg.RESET: for q in qid: qid = [q, -1] self.append_gate(kind=cfg.RESET, qid=qid) elif kind == cfg.MEASURE: if qid is None: raise ValueError("qid must be specified.") if cid is None: raise ValueError("cid must be specified.") if len(qid) != len(cid): raise ValueError("length of qid and cid must be same.") for q, c in zip(qid, cid): qid = [q, -1] self.append_gate(kind=cfg.MEASURE, qid=qid, c=c) elif (kind in (cfg.PAULI_X, cfg.PAULI_Z, cfg.HADAMARD, cfg.PHASE_SHIFT_S, cfg.PHASE_SHIFT_S_, cfg.PHASE_SHIFT_T, cfg.PHASE_SHIFT_T_)): self.append_gate(kind=kind, qid=qid, ctrl=ctrl) elif (kind in (cfg.ROTATION_X, cfg.ROTATION_Z)): para = [phase, 0.0, fac] self.append_gate(kind=kind, qid=qid, para=para, ctrl=ctrl, tag=tag) elif kind == cfg.CONTROLLED_RZ: para = [phase, 0.0, fac] self.append_gate(kind=kind, qid=qid, para=para, ctrl=ctrl, tag=tag) elif (kind in (cfg.CONTROLLED_X, cfg.CONTROLLED_Z, cfg.CONTROLLED_H, )): self.append_gate(kind=kind, qid=qid, ctrl=ctrl) elif kind ==cfg.PAULI_Y: self.append_gate(kind=cfg.PAULI_Z, qid=qid, ctrl=ctrl) self.append_gate(kind=cfg.PAULI_X, qid=qid, ctrl=ctrl) elif kind == cfg.ROOT_PAULI_X: para = [0.5, 0.0, 1.0] self.append_gate(kind=cfg.ROTATION_X, qid=qid, para=para, ctrl=ctrl) elif kind == cfg.ROOT_PAULI_X_: para = [-0.5, 0.0, 1.0] self.append_gate(kind=cfg.ROTATION_X, qid=qid, para=para, ctrl=ctrl) elif kind == cfg.ROTATION_Y: para = [phase, 0.0, fac] self.append_gate(kind=cfg.PHASE_SHIFT_S_, qid=qid, ctrl=ctrl) self.append_gate(kind=cfg.ROTATION_X, qid=qid, para=para, ctrl=ctrl, tag=tag) self.append_gate(kind=cfg.PHASE_SHIFT_S, qid=qid, ctrl=ctrl) elif kind == cfg.PHASE_SHIFT: para = [phase, phase/2.0, fac] self.append_gate(kind=cfg.ROTATION_Z, qid=qid, para=para, ctrl=ctrl, tag=tag) elif kind == cfg.CONTROLLED_Y: self.append_gate(kind=cfg.CONTROLLED_Z, qid=qid, ctrl=ctrl) self.append_gate(kind=cfg.CONTROLLED_X, qid=qid, ctrl=ctrl) self.append_gate(kind=cfg.PHASE_SHIFT_S, qid=qid, ctrl=ctrl) elif kind == cfg.CONTROLLED_XR: q0, q1 = qid[0], qid[1] para = [0.5, 0.0, 1.0] self.append_gate(kind=cfg.HADAMARD, qid=[q1, -1], ctrl=ctrl) self.append_gate(kind=cfg.CONTROLLED_RZ, qid=[q0, q1], para=para, ctrl=ctrl) self.append_gate(kind=cfg.HADAMARD, qid=[q1, -1], ctrl=ctrl) para = [0.25, 0.0, 1.0] self.append_gate(kind=cfg.ROTATION_Z, qid=[q0, q1], para=para, ctrl=ctrl) elif kind == cfg.CONTROLLED_XR_: q0, q1 = qid[0], qid[1] para = [-0.5, 0.0, 1.0] self.append_gate(kind=cfg.HADAMARD, qid=[q1, -1], ctrl=ctrl) self.append_gate(kind=cfg.CONTROLLED_RZ, qid=[q0, q1], para=para, ctrl=ctrl) self.append_gate(kind=cfg.HADAMARD, qid=[q1, -1], ctrl=ctrl) para = [-0.25, 0.0, 1.0] self.append_gate(kind=cfg.ROTATION_Z, qid=[q0, q1], para=para, ctrl=ctrl) elif kind == cfg.CONTROLLED_S: para = [0.5, 0.0, 1.0] self.append_gate(kind=cfg.CONTROLLED_RZ, qid=qid, para=para, ctrl=ctrl) para = [0.25, 0.0, 1.0] self.append_gate(kind=cfg.ROTATION_Z, qid=qid, para=para, ctrl=ctrl) elif kind == cfg.CONTROLLED_S_: para = [-0.5, 0.0, 1.0] self.append_gate(kind=cfg.CONTROLLED_RZ, qid=qid, para=para, ctrl=ctrl) para = [-0.25, 0.0, 1.0] self.append_gate(kind=cfg.ROTATION_Z, qid=qid, para=para, ctrl=ctrl) elif kind == cfg.CONTROLLED_T: para = [0.25, 0.0, 1.0] self.append_gate(kind=cfg.CONTROLLED_RZ, qid=qid, para=para, ctrl=ctrl) para = [0.125, 0.0, 1.0] self.append_gate(kind=cfg.ROTATION_Z, qid=qid, para=para, ctrl=ctrl) elif kind == cfg.CONTROLLED_T_: para = [-0.25, 0.0, 1.0] self.append_gate(kind=cfg.CONTROLLED_RZ, qid=qid, para=para, ctrl=ctrl) para = [-0.125, 0.0, 1.0] self.append_gate(kind=cfg.ROTATION_Z, qid=qid, para=para, ctrl=ctrl) elif kind == cfg.SWAP_QUBITS: q0, q1 = qid[0], qid[1] self.append_gate(kind=cfg.CONTROLLED_X, qid=[q0, q1], ctrl=ctrl) self.append_gate(kind=cfg.CONTROLLED_X, qid=[q1, q0], ctrl=ctrl) self.append_gate(kind=cfg.CONTROLLED_X, qid=[q0, q1], ctrl=ctrl) elif kind == cfg.CONTROLLED_P: para = [phase, 0.0, fac] self.append_gate(kind=cfg.CONTROLLED_RZ, qid=qid, para=para, ctrl=ctrl, tag=tag) para = [phase, 0.0, 0.5*fac] self.append_gate(kind=cfg.ROTATION_Z, qid=qid, para=para, ctrl=ctrl, tag=tag) elif kind == cfg.CONTROLLED_RX: q0, q1 = qid[0], qid[1] para = [phase, 0.0, fac] self.append_gate(kind=cfg.HADAMARD, qid=[q1, -1], ctrl=ctrl) self.append_gate(kind=cfg.CONTROLLED_RZ, qid=[q0, q1], para=para, ctrl=ctrl, tag=tag) self.append_gate(kind=cfg.HADAMARD, qid=[q1, -1], ctrl=ctrl) elif kind == cfg.CONTROLLED_RY: q0, q1 = qid[0], qid[1] # cs_dg gate para = [-0.5, 0.0, 1.0] self.append_gate(kind=cfg.CONTROLLED_RZ, qid=[q0, q1], para=para, ctrl=ctrl) para = [-0.25, 0.0, 1.0] self.append_gate(cfg.ROTATION_Z, qid=[q0, q1], para=para, ctrl=ctrl) para = [phase, 0.0, fac] self.append_gate(cfg.HADAMARD, qid=[q1, -1], ctrl=ctrl) self.append_gate(cfg.CONTROLLED_RZ, qid=[q0, q1], para=para, ctrl=ctrl, tag=tag) self.append_gate(cfg.HADAMARD, qid=[q1, -1], ctrl=ctrl) # cs gate para = [0.5, 0.0, 1.0] self.append_gate(kind=cfg.CONTROLLED_RZ, qid=[q0, q1], para=para, ctrl=ctrl) para = [0.25, 0.0, 1.0] self.append_gate(kind=cfg.ROTATION_Z, qid=[q0, q1], para=para, ctrl=ctrl) elif kind == cfg.ROTATION_XX: q0, q1 = qid[0], qid[1] para=[phase, 0.0, fac] self.append_gate(kind=cfg.HADAMARD, qid=[q0, -1], ctrl=ctrl) self.append_gate(kind=cfg.HADAMARD, qid=[q1, -1], ctrl=ctrl) self.append_gate(kind=cfg.CONTROLLED_X, qid=[q0, q1], ctrl=ctrl) self.append_gate(kind=cfg.ROTATION_Z, qid=[q1, -1], para=para, ctrl=ctrl, tag=tag) self.append_gate(kind=cfg.CONTROLLED_X, qid=[q0, q1], ctrl=ctrl) self.append_gate(kind=cfg.HADAMARD, qid=[q0, -1], ctrl=ctrl) self.append_gate(kind=cfg.HADAMARD, qid=[q1, -1], ctrl=ctrl) elif kind == cfg.ROTATION_YY: q0, q1 = qid[0], qid[1] para = [phase, 0.0, fac] self.append_gate(kind=cfg.ROTATION_X, qid=[q0, -1], para=[0.5, 0.0, 1.0], ctrl=ctrl) self.append_gate(kind=cfg.ROTATION_X, qid=[q1, -1], para=[0.5, 0.0, 1.0], ctrl=ctrl) self.append_gate(kind=cfg.CONTROLLED_X, qid=[q0, q1], ctrl=ctrl) self.append_gate(kind=cfg.ROTATION_Z, qid=[q1, -1], para=para, ctrl=ctrl, tag=tag) self.append_gate(kind=cfg.CONTROLLED_X, qid=[q0, q1], ctrl=ctrl) self.append_gate(kind=cfg.ROTATION_X, qid=[q0, -1], para=[-0.5, 0.0, 1.0], ctrl=ctrl) self.append_gate(kind=cfg.ROTATION_X, qid=[q1, -1], para=[-0.5, 0.0, 1.0], ctrl=ctrl) elif kind == cfg.ROTATION_ZZ: q0, q1 = qid[0], qid[1] para = [phase, 0.0, fac] self.append_gate(kind=cfg.CONTROLLED_X, qid=[q0, q1], ctrl=ctrl) self.append_gate(kind=cfg.ROTATION_Z, qid=[q1, -1], para=para, ctrl=ctrl, tag=tag) self.append_gate(kind=cfg.CONTROLLED_X, qid=[q0, q1], ctrl=ctrl) else: raise ValueError("gate: {} is not supported.".format(cfg.GATE_STRING[kind])) return self
# operate quantum circuit
[docs] def operate_qcirc(self, qcirc, qctrl=None): """ operate quantum circuit Parameters ---------- qcirc : instance of QCirc quantum circuit qctrl : int control qubit id Returns ------- self : instance of QState quantum state after executing the quantum circuit """ if qctrl is None: self.merge_mutable(qcirc) else: qc_qctrl = qcirc.add_control(qctrl=qctrl) self.merge_mutable(qc_qctrl) return self
# c-library for qstate from qlazy.lib.qcirc_c import (qcirc_init, qcirc_copy, qcirc_merge, qcirc_merge_mutable, qcirc_is_equal, qcirc_append_gate, qcirc_kind_first, qcirc_pop_gate, qcirc_set_params, qcirc_get_tag_phase, qcirc_get_tag_list, qcirc_free)