def run(d, fname): f = open(fname, "w") f.write("name,Orig. total,Orig. CNOT,Orig. T,PyZX total,PyZX CNOT,PyZX T,time\n") for fname in os.listdir(d): print("Processing %s..." % fname) circ = zx.Circuit.load(os.path.join(d, fname)).to_basic_gates() num_gates_before = len(circ.gates) cnot_count_before = circ.twoqubitcount() t_count_before = zx.tcount(circ) print("Original:\t Total %d, CNOT %d, T %d" % (num_gates_before, cnot_count_before, t_count_before)) start = time.perf_counter() # start timer g = circ.to_graph() zx.full_reduce(g,quiet=False) g.normalize() new_circ = zx.extract_circuit(g).to_basic_gates() stop = time.perf_counter() # stop timer num_gates_after = len(new_circ.gates) cnot_count_after = new_circ.twoqubitcount() t_count_after = zx.tcount(new_circ) print("Final:\t Total %d, CNOT %d, T %d\n" % (num_gates_after, cnot_count_after, t_count_after)) f.write("%s,%d,%d,%d,%d,%d,%d,%f\n" % (fname, num_gates_before, cnot_count_before, t_count_before, num_gates_after, cnot_count_after, t_count_after, stop - start))
def test_convert_to_from_pyzx_optimizing_circuit(tequila_circuit, t_reduce): pyzx_circuit = convert_to_pyzx(tequila_circuit) pyzx_graph = pyzx_circuit.to_graph() if t_reduce: pyzx.teleport_reduce(pyzx_graph) pyzx_circuit_opt = pyzx.Circuit.from_graph(pyzx_graph) else: pyzx.full_reduce(pyzx_graph) pyzx_graph.normalize() pyzx_circuit_opt = pyzx.extract_circuit(pyzx_graph.copy()) # compare_tensors returns True if pyzx_circuit and pyzx_circuit_opt # implement the same circuit (up to global phase) assert (pyzx.compare_tensors(pyzx_circuit, pyzx_circuit_opt)) # verify_equality return True if full_reduce() is able to reduce the # composition of the circuits to the identity assert (pyzx_circuit.verify_equality(pyzx_circuit_opt)) converted_circuit = convert_from_pyzx(pyzx_circuit_opt) wfn1 = simulate(tequila_circuit, backend="symbolic") wfn2 = simulate(converted_circuit, backend="symbolic") assert (numpy.isclose(wfn1.inner(wfn2), 1.0))
def run(d, fname): f = open(fname, "w") f.write("name,T,2q,total,time\n") for fname in os.listdir(d): print("Processing %s..." % fname) circ = zx.Circuit.load(os.path.join(d, fname)).to_basic_gates() start = time.perf_counter() g = circ.to_graph() zx.full_reduce(g, quiet=False) g.normalize() new_circ = zx.extract_circuit(g).to_basic_gates() stop = time.perf_counter() total_count = len(new_circ.gates) two_count = new_circ.twoqubitcount() t_count = zx.tcount(new_circ) print("\t Total %d, T %d, CNOT %d\n" % (total_count, t_count, two_count)) f.write("%s,%d,%d,%d,%f\n" % (fname, t_count, two_count, total_count, stop - start)) f.close()
def remove_id(c, g): g_tmp = g.copy() for v in g.vertices(): if basicrules.remove_id(g_tmp, v): try: c_new = zx.extract_circuit(g_tmp.copy()) return True, (c_new, g_tmp) except: # If we can't extract the circuit, restore the graph and keep trying g_tmp = g.copy() return False, (c, g)
def g_score(g): g_tmp = g.copy() # FIXME: VERY EXPENSIVE. only to enable circuit extraction. # A better strategy would be to probailistically full_reduce zx.full_reduce(g_tmp) c = zx.extract_circuit(g_tmp.copy()).to_basic_gates() c = zx.basic_optimization(c) return c_score(c) """
def fuse(c, g): g_tmp = g.copy() for v1 in g.vertices(): for v2 in g.vertices(): if basicrules.fuse(g_tmp, v1, v2): try: c_new = zx.extract_circuit(g_tmp.copy()) return True, (c_new, g_tmp) except: # If we can't extract the circuit, restore the graph and keep trying g_tmp = g.copy() return False, (c, g)
def rand_lc(c, g, reduce_prob=0.1): g_tmp = g.copy() apply_rand_lc(g_tmp) # Note the work around to not always full_reduce the graph itself! g_fr = g_tmp.copy() zx.full_reduce(g_fr) c_new = zx.extract_circuit(g_fr.copy()).to_basic_gates() c_new = zx.basic_optimization(c_new) if random.uniform(0, 1) < reduce_prob: g_tmp = g_fr.copy() return True, (c_new, g_tmp)
def full_reduce(c, g): # FIXME: Should really be using g_tmp here? orig_tcount = c.tcount() orig_2qubitcount = c.twoqubitcount() zx.full_reduce(g) c_opt = zx.extract_circuit(g.copy()) opt_tcount = c_opt.tcount() opt_2qubitcount = c_opt.twoqubitcount() if orig_tcount == opt_tcount and orig_2qubitcount == opt_2qubitcount: return False, (c, g) return True, (c_opt, g)
def strong_comp(c, g): g_tmp = g.copy() # n = g.num_vertices() # g.copy() will have consecutive vertices for v1 in g.vertices(): for v2 in g.vertices(): if basicrules.strong_comp(g_tmp, v1, v2): try: c_new = zx.extract_circuit(g_tmp.copy()) return True, (c_new, g_tmp) except: # If we can't extract the circuit, restore the graph and keep trying g_tmp = g.copy() return False, (c, g)
def optimize(self): """ Optimize the circuit using PyZX full_reduce optimization function """ # transforming the PyZX circuit to a graph graph = self.circuit_zx.to_graph() zx.full_reduce(graph, quiet=True) # Optimizing the graph graph.normalize() # Extracting the circuit from the optimized graph self.circuit_zx = zx.extract_circuit(graph.copy()) return self.circuit_zx
def teleport_reduce(c, g): orig_tcount = c.tcount() orig_2qubitcount = c.twoqubitcount() zx.teleport_reduce(g) try: c_opt = zx.Circuit.from_graph(g) except: c_opt = zx.extract_circuit(g.copy()) opt_tcount = c_opt.tcount() opt_2qubitcount = c_opt.twoqubitcount() if orig_tcount == opt_tcount and orig_2qubitcount == opt_2qubitcount: return False, (c, g) return True, (c_opt, g)
def simp_base(c, g, simp_method): orig_tcount = c.tcount() orig_2qubitcount = c.twoqubitcount() g_tmp = g.copy() simp_method(g_tmp, quiet=True) try: # FIXME: WHY does this fail sometimes? c_opt = zx.extract_circuit(g_tmp.copy()) except: return False, (c, g) opt_tcount = c_opt.tcount() opt_2qubitcount = c_opt.twoqubitcount() if orig_tcount == opt_tcount and orig_2qubitcount == opt_2qubitcount: return False, (c, g) return True, (c_opt, g_tmp)
def pyzx_evaluation(circ: QuantumCircuit) -> QuantumCircuit: """ Evaluation using the pyzx libary. due to compability issues, it needs to be casted from QSkits Circuit to the internal datastrcuture => check if this consumes to much memory :param circ: :return: """ log.warning("Evaluating pyzx") c_graph = qiskit_circuit_to_zx_circuit(circ).to_graph() zx.simplify.full_reduce(c_graph) cir_reduced = zx.extract_circuit( c_graph).split_phase_gates().to_basic_gates() circ_out = zx_circuit_to_qiskit_circuit(cir_reduced) # circ_out = convert_clifford_t(circ_out) assert verify_equality(circ, circ_out) log.warning("done") return circ_out
def _add_op_to_graph(self, operation: zx.gates.Gate, graph: zx.Graph) -> zx.Graph: """Adds an operation to a PyZX graph by converting to circuit form, adding the operation to the circuit, then transitioning back to graph form. Args: operation: the operation to add to the graph. qubits: the qubit(s) on which the operation should be added. Note that for multi-qubit gates, the control gate(s) should be provided at the beginning of the list. graph: the graph to add the operation to. Returns: The overwritten graph with the operation added to it. """ circuit = zx.extract_circuit(graph) circuit.add_gate(operation) returned_graph = circuit.to_graph() return returned_graph
def evolve(self, g, n_mutants, n_generations): self.n_mutants = n_mutants self.n_gens = n_generations self.g_orig = g.copy() to_graph_like(self.g_orig) self.c_orig = zx.extract_circuit(self.g_orig.copy()).to_basic_gates() self.c_orig = zx.basic_optimization(self.c_orig) # self.c_orig = c # self.g_orig = c.to_graph() # to_graph_like(self.g_orig) # self.mutants = [Mutant(c, self.g_orig) for _ in range(self.n_mutants)] self.mutants = [Mutant(self.c_orig, self.g_orig) for _ in range(self.n_mutants)] self.update_scores() best_mutant = min(self.mutants, key=lambda m: m.score) # FIXME: Check if this assignment is by reference or value best_score = best_mutant.score gen_scores = [best_score] best_scores = [best_score] for i in tqdm(range(self.n_gens), desc="Generations", disable=self.quiet): n_unique_mutants = len(list(set([id(m) for m in self.mutants]))) assert(n_unique_mutants == self.n_mutants) self.mutate() # self.update_scores() best_in_gen = min(self.mutants, key=lambda m: m.score) gen_scores.append(best_in_gen.score) # So that if we never improve, we see each generation. Because our actions might all rely on extracting, we may never improve on the original if best_in_gen.score < best_score: best_mutant = deepcopy(best_in_gen) best_score = best_in_gen.score best_scores.append(best_score) if all([m.dead for m in self.mutants]): print("[_optimize] stopping early -- all mutants are dead") break self.select() return best_scores, gen_scores, best_mutant.c_curr
def run(d, fname): f = open(fname, "w") f.write("name,T,2q,total,time\n") for fname in os.listdir(d): print("Processing %s..." % fname) circ = zx.Circuit.load(os.path.join(d, fname)).to_basic_gates() start = time.perf_counter() g = circ.to_graph() zx.full_reduce(g) g.normalize() opt_circ = zx.extract_circuit(g).to_basic_gates() stop = time.perf_counter() elapsed = stop - start if elapsed < TIMEOUT: try: # try to run full_optimize, cancelling after 10 minutes - elapsed with time_limit(TIMEOUT - round(elapsed)): opt_circ = zx.full_optimize(opt_circ) stop = time.perf_counter() except TimeoutException: print("\tfull_optimize is slow; only running full_reduce") else: print("\tfull_optimize is slow; only running full_reduce") total_count = len(opt_circ.gates) two_count = opt_circ.twoqubitcount() t_count = zx.tcount(opt_circ) print("\tTotal %d, T %d, 2-qubit %d\n" % (total_count, t_count, two_count)) f.write("%s,%d,%d,%d,%f\n" % (fname, t_count, two_count, total_count, stop - start)) f.close()
axes[0].set_title(f"GA: {N_MUTANTS} mutants, {N_GENS} generations") axes[0].legend() ## SA part reductions = list() N_TRIALS = 10 N_ITERS = 1000 init_score = 4 * c_tr.twoqubitcount() + c_tr.tcount() for i in tqdm(range(N_TRIALS)): g_anneal, _ = sa.pivot_anneal(g_tr.copy(), iters=N_ITERS, score=sa.c_score) zx.full_reduce(g_anneal) c_anneal = zx.extract_circuit(g_anneal.copy()).to_basic_gates() c_anneal = zx.basic_optimization(c_anneal) trial_score = 4 * c_anneal.twoqubitcount() + c_anneal.tcount() reduction = (init_score - trial_score) / init_score * 100 reductions.append(reduction) # print(f"\n----- trial {i} -----") # print(c_anneal.stats()) """ plt.hist(reductions) plt.xlabel("Reduction") plt.ylabel("Frequency") plt.title(f"SA: {N_TRIALS} trials, {N_ITERS} iters") """
def to_circuit(self): return zx.extract_circuit(self.graph)
print("Can convert from original graph back to circuit") except: print("Can NOT convert from original graph back to circuit") successes = 0 for _ in tqdm(range(1000)): c = zx.generate.CNOT_HAD_PHASE_circuit(qubits=N_QUBITS, depth=DEPTH, clifford=False) g = c.to_graph() zx.full_reduce(g) try: c_opt = zx.Circuit.from_graph(g) successes += 1 except: continue print(f"Number of successes: {successes}") """ # The below tests the graph-likeness utilities for _ in tqdm(range(100), desc="Verifying equality with [to_graph_like]..."): c = zx.generate.CNOT_HAD_PHASE_circuit(qubits=N_QUBITS, depth=DEPTH, clifford=False) g = c.to_graph() g1 = g.copy() to_graph_like(g1) zx.full_reduce(g1) c1 = zx.extract_circuit(g1.copy()) assert (c.verify_equality(c1)) print("[to_graph_like] appears to maintain equality!")
if __name__ == "__main__": N_QUBITS = 10 DEPTH = 300 c = zx.generate.CNOT_HAD_PHASE_circuit(qubits=N_QUBITS, depth=DEPTH, clifford=False) g = c.to_graph() reduce_tracker = zx.full_reduce_tracker(g, quiet=True) reduce_tracker['tcount'].insert(0, c.tcount()) reduce_tracker['twoqubitcount'].insert(0, c.twoqubitcount()) reduce_tracker['total'].insert(0, len(c.gates)) reduce_tracker['edges'].insert(0, c.to_graph().num_edges()) reduce_tracker['cliffordcount'].insert(0, c.cliffordcount()) c_opt = zx.extract_circuit(g) print("-----FULL_REDUCE-----") print(f"T Count: {c.tcount()} -> {c_opt.tcount()}") print(f"2-qubit Count: {c.twoqubitcount()} -> {c_opt.twoqubitcount()}") print(f"Total Count: {len(c.gates)} -> {len(c_opt.gates)}") n_steps = len(reduce_tracker['total']) xs = list(range(n_steps)) fig = plt.figure() ax1 = fig.add_subplot(111) ax1.plot(xs, reduce_tracker['tcount'], color='blue', label='T Count') ax1.plot(xs, reduce_tracker['total'], color='green', label='Total') ax1.plot(xs, reduce_tracker['twoqubitcount'], color='red', label='2-qubit count') ax1.plot(xs, reduce_tracker['edges'], color='purple', label='edges')
def _optimize(self, c): g = c.to_graph() zx.full_reduce(g, quiet=True) c_opt = zx.extract_circuit(g.copy()) # FIXME: maybe don't need g.copy()? return c_opt
DEPTH = 100 c = zx.generate.CNOT_HAD_PHASE_circuit(qubits=N_QUBITS, depth=DEPTH, clifford=False) print("----- initial -----") print(c.stats()) # zx.draw(c) plt.show() plt.close('all') g = c.to_graph() g_fr = g.copy() zx.full_reduce(g_fr) c_fr = zx.extract_circuit(g_fr.copy()).to_basic_gates() c_fr = zx.basic_optimization(c_fr) # note: we don't reset g_fr here because for any future annealing, we'd really optimize after the graph produced by full_reduce, rather than something resulting from extraction print("\n----- full_reduce + basic_optimization -----") print(c_fr.stats()) g_just_tr = g.copy() zx.teleport_reduce(g_just_tr) c_just_tr = zx.Circuit.from_graph(g_just_tr.copy()).to_basic_gates() print("\n----- teleport_reduce -----") print(c_just_tr.stats()) g_tr = g.copy() zx.teleport_reduce(g_tr) c_tr = zx.Circuit.from_graph(g_tr.copy()).to_basic_gates() # c_opt = zx.full_optimize(c_opt)
cs_thresholded = [(f, c) for (f, c) in bench_cs if c.qubits <= QUBIT_THRESHOLD] print(f"{len(bench_cs)} benchmark circuits, {len(cs_thresholded)} of which have at most {QUBIT_THRESHOLD} qubits") reductions = dict() for (f, c) in cs_thresholded[:3]: init_score = c_score(c) g = c.to_graph() g_tmp = g.copy() if METHOD == "FR": g_opt = g_tmp.copy() zx.full_reduce(g_opt) c_opt = zx.extract_circuit(g_opt.copy()).to_basic_gates() c_opt = zx.basic_optimization(c_opt) opt_score = c_score(c_opt) elif METHOD == "TR": g_opt = g_tmp.copy() zx.teleport_reduce(g_opt) c_opt = zx.Circuit.from_graph(g_opt.copy()).to_basic_gates() c_opt = zx.basic_optimization(c_opt) opt_score = c_score(c_opt) # g_opt = c_opt.to_graph() elif METHOD == "qiskit": c_opt = QISKIT_OPT._optimize(c.copy())