Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
wide_circuit_optimization.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 """
3 Created on Fri Jun 26 14:42:56 2020
4 Copyright 2020 Peter Rakyta, Ph.D.
5 
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9 
10  http://www.apache.org/licenses/LICENSE-2.0
11 
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 
18 @author: Peter Rakyta, Ph.D.
19 """
20 
22 
23 import squander.decomposition.qgd_Wide_Circuit_Optimization as Wide_Circuit_Optimization
25  CNOTGateCount, SingleQubitGateCount, TotalRawGateCount, CircuitGateStats,
26 )
27 from squander.gates.qgd_Circuit import qgd_Circuit as Circuit
28 from squander import utils
29 from squander import Qiskit_IO
30 import time, requests, os, zipfile, tempfile, json, numpy as np
31 from pathlib import Path
32 from collections import Counter
33 
34 # IBM Eagle native gate set (QMill benchmark basis)
35 IBM_EAGLE_BASIS = ["cx", "rz", "sx", "x"]
36 
37 
38 def transpile_to_ibm_eagle(qasm_path_or_circ, parameters=None):
39  """Transpile a circuit to IBM Eagle's native gate set and return consistent
40  gate stats via Squander's CircuitGateStats.
41 
42  Args:
43  qasm_path_or_circ: Either a path to a .qasm file, or a Squander Circuit.
44  parameters: Required if passing a Squander Circuit.
45 
46  Returns:
47  dict from CircuitGateStats, using the same decomposition as the main
48  benchmark pipeline.
49  """
50  from qiskit import QuantumCircuit, transpile
51 
52  if isinstance(qasm_path_or_circ, str):
53  qc = QuantumCircuit.from_qasm_file(qasm_path_or_circ)
54  else:
55  qc = Qiskit_IO.get_Qiskit_Circuit(
56  qasm_path_or_circ,
57  np.asarray(parameters if parameters is not None else [], dtype=np.float64),
58  )
59 
60  transpiled = transpile(qc, basis_gates=IBM_EAGLE_BASIS, optimization_level=0)
61  eagle_circ, eagle_params = Qiskit_IO.convert_Qiskit_to_Squander(transpiled)
62  return CircuitGateStats(eagle_circ)
63 
64 
65 if __name__ == "__main__":
66 
67  config = {
68  "strategy": "TreeSearch", # possible values: "TreeSearch", "qiskit", "bqskit", "TabuSearch"
69  "test_subcircuits": False,
70  "test_final_circuit": False,
71  "max_partition_size": 4,
72  "beam": None,
73  "use_osr": True,
74  "use_graph_search": True,
75  "pre-opt-strategy": "TreeSearch", # possible values: "TreeSearch", "qiskit", "bqskit", "TabuSearch"
76  "routing-strategy": "seqpam-ilp", # possible values: "sabre", "light-sabre", "bqskit-sabre", "seqpam-quick", "seqpam-ilp"
77  "tolerance": 1e-5, #1e-5 for use_float and 1e-10 if not are sensible
78  "use_float": True, # whether to use single precision for the optimization (experimental, may cause instability in some cases, but can significantly reduce optimization time and memory usage for large circuits)
79  # **{'use_basin_hopping': True, 'bh_T': 1.1822334624366124, 'bh_stepsize': 0.9020671823381502, 'bh_interval': 165, 'bh_target_accept_rate': 0.7037812116166546, 'bh_stepwise_factor': 0.8254028860713254}
80  }
81 
82  import os
83 
84  files = [os.path.join(Path(__file__).resolve().parent, "bv_n14.qasm")]
85 
86  # JSON results file with resume support
87  RESULTS_FILE = "results.json"
88  import json
89 
90  # Load existing results to skip already-processed circuits
91  existing_results = {}
92  if os.path.exists(RESULTS_FILE):
93  with open(RESULTS_FILE) as f:
94  existing_results = json.load(f)
95  print(f"Loaded {len(existing_results)} existing results; will skip already-processed circuits.")
96 
97  results = existing_results # merge new results into existing
98  for filename in files:
99  fname = os.path.basename(filename)
100  if fname in results:
101  print(f"Skipping already processed: {fname}")
102  continue
103 
104  print(f"executing optimization of circuit: {filename}")
105  #if not filename.endswith("_n140.qasm"): continue
106 
107  # load the circuit from a file
108  circ, parameters, _ = utils.qasm_to_squander_circuit(filename)
109  config["topology"] = (
110  Wide_Circuit_Optimization.qgd_Wide_Circuit_Optimization.linear_topology(
111  circ.get_Qbit_Num()
112  )
113  )
114 
115  # pre-optimization stats
116  init_stats = CircuitGateStats(circ)
117  init_ibm_eagle = transpile_to_ibm_eagle(filename)
118 
119  # run circuit optimization
120  wide_circuit_optimizer = (
121  Wide_Circuit_Optimization.qgd_Wide_Circuit_Optimization({**config})
122  )
123  start_time = time.time()
124  optcirc, optparameters = wide_circuit_optimizer.OptimizeWideCircuit(
125  circ, parameters
126  )
127  elapsed = time.time() - start_time
128 
129  # post-optimization stats
130  opt_stats = CircuitGateStats(optcirc)
131  final_ibm_eagle = transpile_to_ibm_eagle(optcirc, optparameters)
132  opt_time = wide_circuit_optimizer.config.get("optimization_time", None)
133 
134  # routing stats (if routing was needed)
135  a2a_stats = None
136  routed_stats = None
137  a2a_time = routing_time = None
138  if wide_circuit_optimizer.config.get("routed_circuit", None) is not None:
139  a2acirc = wide_circuit_optimizer.config["all_to_all_circuit"]
140  routedcirc = wide_circuit_optimizer.config["routed_circuit"]
141  a2a_stats = CircuitGateStats(a2acirc)
142  routed_stats = CircuitGateStats(routedcirc)
143  a2a_time = wide_circuit_optimizer.config.get("all_to_all_optimization_time", None)
144  routing_time = wide_circuit_optimizer.config.get("routing_time", None)
145 
146  result_entry = {
147  "file": fname,
148  "strategy": config["strategy"],
149  "pre_opt_strategy": config["pre-opt-strategy"],
150  "routing_strategy": config["routing-strategy"],
151  "init": init_stats,
152  "init_ibm_eagle": init_ibm_eagle,
153  "final": opt_stats,
154  "final_ibm_eagle": final_ibm_eagle,
155  "timing": {
156  "a2a": round(a2a_time, 2) if a2a_time else None,
157  "routing": round(routing_time, 2) if routing_time else None,
158  "optimization": round(opt_time, 2) if opt_time else None,
159  "total": round(elapsed, 2),
160  },
161  }
162  if a2a_stats:
163  result_entry["all_to_all"] = a2a_stats
164  if routed_stats:
165  result_entry["routed"] = routed_stats
166 
167  results[fname] = result_entry
168 
169  # save after each circuit so we don't lose progress
170  with open(RESULTS_FILE, "w") as f:
171  json.dump(results, f, indent=2)
172 
173  wide_circuit_optimizer.check_compare_circuits(
174  circ,
175  parameters,
176  optcirc,
177  optparameters,
178  routing=wide_circuit_optimizer.config.get("routed_circuit", None) is not None,
179  forced_test=True,
180  label="example final original-to-output",
181  )
182 
183  print(f" init: {init_stats['cnot_equiv']} CNOT, {init_stats['single_qubit']} 1q, "
184  f"{init_stats['total_raw']} total, {init_stats['qubits']}q")
185  print(f" final: {opt_stats['cnot_equiv']} CNOT, {opt_stats['single_qubit']} 1q, "
186  f"{opt_stats['total_raw']} total")
187  print(f" time: {elapsed:.2f}s")
188  print(f"--- {len(results)}/{len(files)} circuits processed ---")
189 
190  print("--- %s seconds elapsed during optimization ---" % elapsed)
def transpile_to_ibm_eagle(qasm_path_or_circ, parameters=None)