1 from typing
import Set, List, Tuple, Dict, Any, Set
4 from squander
import utils
5 from squander.gates.gates_Wrapper
import Gate
9 from qiskit.transpiler
import PassManager
10 from qiskit.transpiler.passes
import CollectMultiQBlocks
15 Get qubit indices used by a gate 23 return set(gate.get_Involved_Qbits())
26 def get_float_ops(num_qubit, gate_qubits, control_qubits, is_pure=False, io_penalty=48):
28 Compute the number of floating-point operations (FLOPs) required 29 for simulating a quantum gate acting on a set of qubits. 32 num_qubit (int): Total number of qubits in the system. 33 gate_qubits (int): Number of qubits the gate acts on (including controls). 34 control_qubits (int): Number of control qubits for the gate. 35 is_pure (bool, optional): Whether the gate is a "pure" controlled gate 36 (i.e., all controlled gates share the same target qubit). Defaults to False. 39 int: Estimated number of floating-point operations required. 41 g_size = 2 ** (gate_qubits - control_qubits)
43 return 2 ** (num_qubit - (control_qubits
if is_pure
else 0)) * (
44 g_size * (4 + 2) + 2 * (g_size - 1) + io_penalty
50 Compute FLOPs for each partition of gates in a quantum circuit. 53 num_qubit (int): Total number of qubits in the system. 54 gate_to_qubit (dict): Mapping from gate ID to the set of qubits 56 gate_to_tqubit (dict or None): Mapping from gate ID to its target qubit 57 (used for distinguishing control vs. target qubits). If None, 58 control qubits are assumed to be 0. 59 allparts (list[list]): Partitioning of gates, where each part is a 60 collection (e.g., list or set) of gate IDs. 63 list[int]: FLOP counts for each partition in `allparts`. 67 qubits = set.union(*(gate_to_qubit[x]
for x
in part))
68 if gate_to_tqubit
is not None:
69 tqubits = {gate_to_tqubit[x]
for x
in part}
71 len({frozenset(gate_to_qubit[x] - {gate_to_tqubit[x]})
for x
in part})
76 num_qubit, len(qubits), len(qubits) - len(tqubits), is_pure
80 weights.append(
get_float_ops(num_qubit, len(qubits), 0,
False))
85 num_qubit, max_qubits_per_partition, gate_to_qubit, gate_to_tqubit, allparts
88 Compute the total FLOPs across all partitions of a quantum circuit, 89 scaled by the number of qubits outside the maximum partition. 92 num_qubit (int): Total number of qubits in the system. 93 max_qubits_per_partition (int): Maximum number of qubits any partition 95 gate_to_qubit (dict): Mapping from gate ID to the set of qubits 97 gate_to_tqubit (dict or None): Mapping from gate ID to its target qubit. 98 If None, control qubits are assumed to be 0. 99 allparts (list[list]): Partitioning of gates, where each part is a 100 collection of gate IDs. 103 int: Total number of floating-point operations across all partitions. 106 max_qubits_per_partition, gate_to_qubit, gate_to_tqubit, allparts
108 return 2 ** (num_qubit - max_qubits_per_partition) * sum(weights)
112 params: np.ndarray, param_order: List[Tuple[int, int, int]]
115 Call to reorder circuit parameters based on partitioned execution order 119 params ( np.ndarray ) Original parameter array 121 param_order (List[int] ) Tuples specifying new parameter positions: source_idx, dest_idx, param_count 125 Returns with the reordered Reordered parameter array 127 reordered = np.empty_like(params)
129 for s_idx, n_idx, n_params
in param_order:
130 reordered[n_idx : n_idx + n_params] = params[s_idx : s_idx + n_params]
145 Build dependency graphs for circuit gates 151 Gate dict, forward graph, reverse graph, qubit mapping, start set. 153 gate_dict = {i: gate
for i, gate
in enumerate(c.get_Gates())}
154 gate_to_qubit = {i:
get_qubits(g)
for i, g
in gate_dict.items()}
155 g, rg = {i: set()
for i
in gate_dict}, {i: set()
for i
in gate_dict}
157 for gate
in gate_dict:
158 for child
in c.get_Children(gate_dict[gate]):
162 S = {m
for m
in rg
if len(rg[m]) == 0}
164 return gate_dict, g, rg, gate_to_qubit, S
169 Convert Qiskit gate name to SQUANDER name 172 qiskit_name: Qiskit gate name 177 name = qiskit_name.upper()
190 Map gate descriptions to indices for partitioning 193 circ: SQUANDER Circuit 195 preparts: Partition descriptions 198 Partitioned gate indices 203 curr_partition = set()
209 if qubit_groups_only:
211 iter(x
for x
in S
if gate_to_qubit[x] <= preparts[len(parts) - 1]),
None 215 (frozenset(gate_to_qubit[x]), gate_dict[x].get_Name()): x
for x
in S
217 rev_Scomp = {y: x
for x, y
in Scomp.items()}
218 n = next(iter(Scomp.keys() & preparts[len(parts) - 1]),
None)
223 total += len(parts[-1])
224 curr_partition = set()
226 if qubit_groups_only:
228 iter(x
for x
in S
if gate_to_qubit[x] <= preparts[len(parts) - 1]),
232 n = next(iter(Scomp.keys() & preparts[len(parts) - 1]),
None)
236 if not qubit_groups_only:
237 preparts[len(parts) - 1].remove(rev_Scomp[n])
239 curr_partition |= gate_to_qubit[n]
245 assert len(rg[n]) == 0
246 for child
in set(g[n]):
253 total += len(parts[-1])
254 assert total == len(gate_dict)
261 Partition circuit using Qiskit multi-qubit blocks 264 filename: QASM file path 266 max_partition_size: Max qubits per partition 269 Parameters, partitioned circuit, parameter order (source_idx, dest_idx, param_count), partitions 271 circ, parameters, qc = utils.qasm_to_squander_circuit(filename,
True)
274 CollectMultiQBlocks(max_block_size=max_partition_size),
278 assert qc
is not None 280 blocks = pm.property_set[
"block_list"]
285 frozenset(qc.find_bit(x)[0]
for x
in dagop.qargs),
293 assert len(qc.data) == sum(map(len, blocks))
294 from squander.partitioning.kahn
import kahn_partition_preparts
299 return parameters, partitioned_circ, param_order, parts
304 Generate circuit partitions from a QASM file using Qiskit's fusion 305 metadata and Squander's partitioning utilities. 308 1. Parses the QASM file into a Squander circuit. 309 2. Runs the circuit on Qiskit's AerSimulator with gate fusion enabled. 310 3. Extracts the fusion metadata (grouped qubit operations). 311 4. Uses Squander's Kahn-based partitioning to produce circuit partitions. 314 filename (str): Path to the QASM file to be parsed. 315 max_partition_size (int): Maximum number of qubits allowed in each 316 fusion partition (i.e., `fusion_max_qubit` for Qiskit). 320 parameters (list): Parameter list extracted from the Squander circuit. 321 partitioned_circ (object): Partitioned Squander circuit object. 322 param_order (list): Order of parameters corresponding to the partitioned circuit. 323 parts (list[list]): Partitioning of the circuit into gate groups, 324 represented as lists of gate indices. 326 circ, parameters, qc = utils.qasm_to_squander_circuit(filename,
True)
328 assert qc
is not None 330 qc.save_statevector()
331 from qiskit
import transpile
332 from qiskit_aer
import AerSimulator
334 backend = AerSimulator(
335 method=
"statevector",
338 fusion_max_qubit=max_partition_size,
342 tcirc = transpile(qc, backend=backend, optimization_level=0)
343 job = backend.run(tcirc, shots=1)
345 meta = res.results[0].metadata.get(
"fusion", {})
347 frozenset(x[
"qubits"])
for x
in meta[
"output_ops"][:-1]
349 from squander.partitioning.kahn
import kahn_partition_preparts
356 return parameters, partitioned_circ, param_order, parts
361 Partition circuit using BQSKit partitioners 364 filename: QASM file path 366 max_partition_size: Max qubits per partition 368 partitioner: BQSKit Partitioning strategy 371 Parameters, partitioned circuit, parameter order (source_idx, dest_idx, param_count), partitions 374 from bqskit
import Circuit
375 from bqskit.passes.partitioning.quick
import QuickPartitioner
376 from bqskit.passes.partitioning.greedy
import GreedyPartitioner
377 from bqskit.passes.partitioning.cluster
import (
378 ClusteringPartitioner,
380 from bqskit.passes.partitioning.scan
import ScanPartitioner
385 bqskit.ir.gates.constant.cx.CNOTGate:
"CNOT",
386 bqskit.ir.gates.constant.t.TGate:
"T",
387 bqskit.ir.gates.constant.h.HGate:
"H",
388 bqskit.ir.gates.constant.tdg.TdgGate:
"Tdg",
389 bqskit.ir.gates.constant.x.XGate:
"X",
390 bqskit.ir.gates.constant.y.YGate:
"Y",
391 bqskit.ir.gates.constant.z.ZGate:
"Z",
392 bqskit.ir.gates.constant.s.SGate:
"S",
393 bqskit.ir.gates.constant.sdg.SdgGate:
"Sdg",
394 bqskit.ir.gates.constant.r.RGate:
"R", 395 bqskit.ir.gates.constant.sx.SXGate: "SX",
396 bqskit.ir.gates.constant.ch.CHGate:
"CH",
397 bqskit.ir.gates.constant.cz.CZGate:
"CZ",
398 bqskit.ir.gates.parameterized.cry.CRYGate:
"CRY",
399 bqskit.ir.gates.parameterized.u3.U3Gate:
"U3",
400 bqskit.ir.gates.parameterized.rz.RZGate:
"RZ",
401 bqskit.ir.gates.parameterized.ry.RYGate:
"RY",
402 bqskit.ir.gates.parameterized.rx.RXGate:
"RX",
405 if partitioner ==
"Quick":
406 partitioner = QuickPartitioner(block_size=max_partition_size)
407 elif partitioner ==
"Scan":
408 partitioner = ScanPartitioner(block_size=max_partition_size)
409 elif partitioner ==
"Greedy":
410 partitioner = GreedyPartitioner(block_size=max_partition_size)
411 elif partitioner ==
"Cluster":
412 partitioner = ClusteringPartitioner(block_size=max_partition_size)
413 bq_circuit = Circuit.from_file(filename)
414 asyncio.run(partitioner.run(bq_circuit,
None))
416 circ, parameters, qc = utils.qasm_to_squander_circuit(filename,
True)
420 frozenset(curloc.location[x]
for x
in op.location),
421 bqs_to_squander[type(op.gate)],
423 for op
in curloc.gate._circuit.operations()
425 for curloc
in bq_circuit.operations()
427 from squander.partitioning.kahn
import kahn_partition_preparts
432 return parameters, partitioned_circ, param_order, parts
434 except ImportError
as e:
436 f
"bqskit is not installed: bqskit is required for bqskit-{partitioner} partitioning."
def kahn_partition_preparts(c, max_qubit, preparts)
def get_Parameter_Num(self)
Call to get the number of free parameters in the gate structure used for the decomposition.