2 from unittest
import result
3 from squander.partitioning.partition
import PartitionCircuitQasm
4 from qiskit
import QuantumCircuit
5 from squander.partitioning.tools
import qiskit_to_squander_name, get_qubits, total_float_ops
6 from squander
import utils
14 MAX_GATES_ALLOWED = 1024**2
26 ] + ([
"ilp",
"ilp-fusion",
30 SUPPORTED_GATES = {x
for n
in dir(gate)
for x
in (getattr(gate, n),)
if not n.startswith(
"_")
and issubclass(x, gate.Gate)
and n !=
"Gate"}
31 SUPPORTED_GATES_NAMES = {n
for n
in dir(gate)
if not n.startswith(
"_")
and issubclass(getattr(gate, n), gate.Gate)
and n !=
"Gate"}
35 Run a small symbolic experiment to study âpurityâ and âsparsityâ of control sets. 37 This routine builds a set of helper functions and 1q/2q/3q gate factories with SymPy, 39 1) Checks which qubit subsets act as pure/sparse controls for some canonical gates 40 (e.g., U3 on 1q, CRY on 2q, CCX on 3q). 41 2) Composes short circuits (e.g., U3 on qubit i followed by CRY(0,1)) and reports 42 which control subsets remain pure/sparse after composition. 43 3) Prints the identified âpureâ and âsparsityâ control sets for each experiment. 46 - Endianness is governed by the local variable `little_endian` (default: True). 47 - `apply_to` operates on full operators (2^n à 2^n), not statevectors. 48 - Requires SymPy. The helper `apply_to` also uses `itertools.product`. 55 Results are printed to stdout; the function is intended as a diagnostic/demo. 57 import sympy, functools, operator
58 from sympy.combinatorics
import Permutation
59 theta, phi, lbda, gamma = sympy.Symbol(
"θ"), sympy.Symbol(
"Ï"), sympy.Symbol(
"λ"), sympy.Symbol(
"γ")
60 alpha, theta2 = sympy.Symbol(
"α"), sympy.Symbol(
"θ2")
62 def find_control_qubits(psi, num_qubits):
64 Identify control-qubit sets that make a unitary âpure-controlledâ or âsparse-controlledâ. 66 A subset S of qubits is: 67 - pure-controlled if, when control pattern S is not fully satisfied in basis index j, 68 row j of the unitary equals the corresponding computational basis row (identity row). 69 - sparse-controlled if, when control pattern S is not fully satisfied in j, 70 row j has zeros everywhere except possibly in columns k that satisfy S. 73 psi (sympy.Matrix): 2**num_qubits à 2**num_qubits unitary matrix. 74 num_qubits (int): Total number of qubits. 77 tuple[list[list[int]], list[list[int]]]: 78 pure_controls: list of control index lists (little-endian by default) making psi pure-controlled. 79 sparsity_controls: list of control index lists making psi sparse-controlled. 82 sparsity_controls = []
83 for i
in range(1, 1<<num_qubits):
84 is_pure, is_sparse =
True,
True 85 for j
in range(1<<num_qubits):
87 if not all(psi[j, k] == (1
if j == k
else 0)
for k
in range(1<<num_qubits)):
89 if not all(psi[j, k] == 0
for k
in range(1<<num_qubits)
if i & k == i):
91 if is_pure: pure_controls.append([j
if little_endian
else num_qubits-1-j
for j
in range(num_qubits)
if i & (1<<j)])
92 if is_sparse: sparsity_controls.append([j
if little_endian
else num_qubits-1-j
for j
in range(num_qubits)
if i & (1<<j)])
93 return pure_controls, sparsity_controls
94 def force_euler_pairs(e):
96 a = sympy.Wild(
'a', properties=[
lambda k: k.is_real
is not False])
97 e = e.replace(1 - sympy.exp(sympy.I*a), -2*sympy.I*sympy.exp(sympy.I*a/2)*sympy.sin(a/2))
98 e = e.replace(sympy.exp(sympy.I*a) - 1, 2*sympy.I*sympy.exp(sympy.I*a/2)*sympy.sin(a/2))
100 def quantsimp(x, rewrite=False):
101 x = force_euler_pairs(sympy.sympify(x).rewrite(sympy.exp)
if rewrite
else sympy.sympify(x))
102 return sympy.simplify(sympy.trigsimp(sympy.exptrigsimp(sympy.together(sympy.expand_power_exp(sympy.powsimp(sympy.nsimplify(x, rational=
True), force=
True, deep=
True)))), recursive=
True)).doit()
103 def quantsimprw(x):
return quantsimp(x, rewrite=
True)
104 def apply_to(psi, num_qubits, gate, gate_qubits):
106 Left-apply a k-qubit gate to designated positions inside an n-qubit operator. 108 Embeds the k-qubit `gate` on `gate_qubits` (indices in [0..n-1]) under the 109 current endianness, and returns (gate â I_rest) · psi with the correct qubit 110 wiring. Works for psi as a full 2^nÃ2^n operator (not a statevector). 113 psi (sympy.Matrix): 2**num_qubits à 2**num_qubits operator to transform. 114 num_qubits (int): Total number of qubits n. 115 gate (sympy.Matrix): 2**k à 2**k unitary to embed. 116 gate_qubits (Iterable[int]): The k target qubit indices where `gate` acts. 119 sympy.Matrix: New 2**num_qubits à 2**num_qubits matrix after applying `gate`. 121 pos = [(q
if little_endian
else num_qubits-1-q)
for q
in gate_qubits]
123 deltas, combo = [1<<p
for p
in pos], []
124 for u
in range (1 << k):
127 if x & 1: m ^= deltas[b]
131 rest = [p
for p
in range(num_qubits)
if not p
in pos_set]
132 output = sympy.zeros(1<<num_qubits, 1<<num_qubits)
133 for rest_bits
in itertools.product((0, 1), repeat=len(rest)):
135 for bit, p
in zip(rest_bits, rest):
137 mat, idx = sympy.zeros(0, 1<<num_qubits), []
138 for u
in range(1 << k):
139 idx.append(base ^ combo[u])
140 mat = mat.col_join(psi[idx[u],:])
142 for u
in range(1 << k):
143 output[idx[u],:] = prod[u,:].applyfunc(quantsimp)
145 def compile_gates(num_qubits, gates):
147 Compose a list of embedded gates into a full n-qubit unitary. 150 num_qubits (int): Total number of qubits n. 151 gates (Iterable[tuple[sympy.Matrix, Iterable[int]]]): 152 Sequence of (gate, gate_qubits) pairs. Each `gate` is 2**kÃ2**k, 153 applied to the provided `gate_qubits`. 156 sympy.Matrix: The resulting 2**n à 2**n unitary product. 158 Umtx = sympy.eye(2**num_qubits)
159 for gate
in gates: Umtx =
apply_to(Umtx, num_qubits, *gate)
161 def make_controlled(gate, gate_qubits, gateother=None):
163 Build a 1-control controlled version of `gate` (control is the first qubit by convention). 165 Constructs block-diagonal diag(gateother or I, gate) and applies a permutation so that, 166 under little-endian, the control is the least significant qubit. 169 gate (sympy.Matrix): 2**m à 2**m target unitary. 170 gate_qubits (int): Number of target qubits m (excluding the control qubit). 171 gateother (sympy.Matrix | None): Optional block for the control-off subspace. 172 If None, identity of size 2**m is used. 175 sympy.Matrix: A 2**(m+1) à 2**(m+1) controlled-gate matrix. 177 res = sympy.diag(gateother
if not gateother
is None else sympy.eye(1<<gate_qubits), gate)
178 P = sympy.eye(1<<(gate_qubits+1))[:, [2*x+y
for y
in (0, 1)
for x
in range(1<<gate_qubits)]]
179 return P * res * P.T
if little_endian
else res
180 def make_inverse(g):
return g**-1
181 def make_sqrt(g):
return g**(1/2)
182 def gen_I():
return sympy.eye(2)
183 def gen_Rx(theta):
return sympy.exp(-sympy.I*theta/2*gen_X()).applyfunc(quantsimp)
184 def gen_Ry(theta):
return sympy.exp(-sympy.I*theta/2*gen_Y()).applyfunc(quantsimp)
185 def gen_Rz(phi):
return sympy.exp(-sympy.I*phi/2*gen_Z()).applyfunc(quantsimp)
186 def gen_GP(theta, qbits):
return sympy.exp(theta*sympy.I)*sympy.eye(1<<qbits)
187 def gen_H():
return sympy.
Matrix([[1, 1], [1, -1]])/sympy.sqrt(2)
188 def gen_X():
return sympy.
Matrix([[0, 1], [1, 0]])
189 def gen_Y():
return sympy.
Matrix([[0, -sympy.I], [sympy.I, 0]])
190 def gen_Z():
return sympy.
Matrix([[1, 0], [0, -1]])
191 def gen_S():
return sympy.
Matrix([[1, 0], [0, sympy.I]])
192 def gen_Sdg():
return make_inverse(gen_S())
193 def gen_SX():
return make_sqrt(gen_X()).applyfunc(quantsimp)
194 def gen_CZPowGate(t):
return gen_CP(sympy.pi*t)
195 def gen_fSim(theta, phi):
return compile_gates(2, [(gen_iSWAP_pow(-2*theta/sympy.pi), [0, 1]), (gen_CZPowGate(-phi/sympy.pi), [0, 1])])
196 def gen_SYC():
return gen_fSim(sympy.pi/2, sympy.pi/6)
197 def gen_T():
return sympy.
Matrix([[1, 0], [0, sympy.exp(sympy.I*sympy.pi/4)]])
198 def gen_Tdg():
return make_inverse(gen_T())
199 def gen_P(theta):
return sympy.
Matrix([[1, 0], [0, sympy.exp(sympy.I*theta)]])
200 def gen_U1(theta):
return gen_P(theta)
201 def gen_CH():
return sympy.
Matrix(make_controlled(gen_H(), 1))
202 def gen_CX():
return sympy.
Matrix(make_controlled(gen_X(), 1))
203 def gen_CNOT():
return gen_CX()
204 def gen_CY():
return sympy.
Matrix(make_controlled(gen_Y(), 1))
205 def gen_CZ():
return sympy.
Matrix(make_controlled(gen_Z(), 1))
206 def gen_U(theta, phi, lbda):
return compile_gates(1, [(gen_Rz(phi), [0]), (gen_Ry(theta), [0]), (gen_Rz(lbda), [0]), (gen_GP((phi+lbda)/2, 1), [0])])
207 def gen_U2(phi, lbda):
return gen_U(sympy.pi/2, phi, lbda)
208 def gen_U3(theta, phi, lbda):
return gen_U(theta, phi, lbda)
209 def gen_R(theta, phi):
return gen_U(theta, phi-sympy.pi/2, -phi+sympy.pi/2)
210 def gen_CR(theta, phi):
return make_controlled(gen_R(theta, phi), 1)
211 def gen_CROT(theta, phi):
return make_controlled(gen_R(theta, phi), 1, gen_R(-theta, phi))
212 def gen_CRX(theta):
return make_controlled(gen_Rx(theta), 1)
213 def gen_CRY(theta):
return make_controlled(gen_Ry(theta), 1)
214 def gen_CRZ(theta):
return make_controlled(gen_Rz(theta), 1)
215 def gen_CCX():
return make_controlled(gen_CNOT(), 2)
216 def gen_Toffoli():
return gen_CCX()
217 def gen_CCZ():
return make_controlled(gen_CZ(), 2)
218 def gen_SWAP():
return functools.reduce(operator.add, (compile_gates(2, [(gen(), [0]), (gen(), [1])])
for gen
in (gen_I, gen_X, gen_Y, gen_Z))) / 2
219 def gen_CSWAP():
return make_controlled(gen_SWAP(), 2)
220 def gen_iSWAP():
return gen_Rxy(-sympy.pi)
221 def gen_iSWAP_pow(alpha):
return (gen_iSWAP()**alpha).applyfunc(quantsimprw)
222 def gen_CP(phi):
return make_controlled(gen_P(phi), 1)
224 def gen_CS():
return make_controlled(gen_S(), 1)
225 def gen_CU1(theta):
return gen_CP(theta)
226 def gen_CU(theta, phi, lbda, gamma):
return make_controlled(compile_gates(1, [(gen_U(theta, phi, lbda), [0]), (gen_GP(gamma, 1), [0])]), 1)
227 def gen_CU3(theta, phi, lbda):
return gen_CU(theta, phi, lbda, 0)
228 def gen_Rxx(theta):
return sympy.exp(-sympy.I*theta/2*compile_gates(2, [(gen_X(), [0]), (gen_X(), [1])])).applyfunc(quantsimp)
229 def gen_Ryy(theta):
return sympy.exp(-sympy.I*theta/2*compile_gates(2, [(gen_Y(), [0]), (gen_Y(), [1])])).applyfunc(quantsimp)
230 def gen_Rzz(theta):
return sympy.exp(-sympy.I*theta/2*compile_gates(2, [(gen_Z(), [0]), (gen_Z(), [1])])).applyfunc(quantsimp)
231 def gen_Rxy(phi):
return sympy.exp(-sympy.I*phi/4*(compile_gates(2, [(gen_X(), [0]), (gen_X(), [1])]) + compile_gates(2, [(gen_Y(), [0]), (gen_Y(), [1])]))).applyfunc(quantsimprw)
232 def gen_CZ_decomp():
return compile_gates(2, [(gen_H(), [1]), (gen_CNOT(), [0, 1]), (gen_H(), [1])])
233 def gen_CY_decomp():
return compile_gates(2, [(gen_H(), [1]), (gen_S(), [1]), (gen_CNOT(), [0, 1]), (gen_Sdg(), [1]), (gen_H(), [1])])
234 def gen_CH_decomp():
return compile_gates(2, [(gen_Rz(-sympy.pi/2), [1]), (gen_CNOT(), [0, 1]), (gen_Rz(-sympy.pi/2), [1]), (gen_Ry(sympy.pi/4), [1]), (gen_CNOT(), [0, 1]), (gen_Ry(-sympy.pi/4), [1]), (gen_Rz(sympy.pi), [1]), (gen_P(sympy.pi/2), [0])])
235 def gen_CP_decomp(phi):
return compile_gates(2, [(gen_Rz(phi/2), [0]), (gen_CNOT(), [0, 1]), (gen_Rz(-phi/2), [1]), (gen_CNOT(), [0, 1]), (gen_Rz(phi/2), [1]), (gen_GP(phi/4, 2), [0, 1])])
236 def gen_CRX_decomp(theta):
return compile_gates(2, [(gen_H(), [1]), (gen_Rz(theta/2), [1]), (gen_CNOT(), [0, 1]), (gen_Rz(-theta/2), [1]), (gen_CNOT(), [0, 1]), (gen_H(), [1])])
237 def gen_CRY_decomp(theta):
return compile_gates(2, [(gen_Ry(theta/2), [1]), (gen_CNOT(), [0, 1]), (gen_Ry(-theta/2), [1]), (gen_CNOT(), [0, 1])])
238 def gen_CRZ_decomp(theta):
return compile_gates(2, [(gen_Rz(theta/2), [1]), (gen_CNOT(), [0, 1]), (gen_Rz(-theta/2), [1]), (gen_CNOT(), [0, 1])])
239 def gen_CS_decomp():
return compile_gates(2, [(gen_Rz(sympy.pi/4), [0]), (gen_CNOT(), [0, 1]), (gen_Rz(-sympy.pi/4), [1]), (gen_CNOT(), [0, 1]), (gen_Rz(sympy.pi/4), [1]), (gen_GP(sympy.pi/8, 2), [0, 1])])
240 def gen_CR_decomp(theta, phi):
return compile_gates(2, [(gen_Rz(phi-sympy.pi/2), [1]), (gen_CNOT(), [0, 1]), (gen_Ry(-theta/2), [1]), (gen_CNOT(), [0, 1]), (gen_Ry(theta/2), [1]), (gen_Rz(-phi+sympy.pi/2), [1])])
241 def gen_CROT_decomp(theta, phi):
return compile_gates(2, [(gen_Rz(phi), [1]), (gen_Ry(-sympy.pi/2), [1]), (gen_CNOT(), [0, 1]), (gen_Rz(theta), [1]), (gen_CNOT(), [0, 1]), (gen_Ry(sympy.pi/2), [1]), (gen_Rz(-phi), [1])])
242 def gen_Rxx_decomp(theta):
return compile_gates(2, [(gen_H(), [0]), (gen_H(), [1]), (gen_CNOT(), [0, 1]), (gen_Rz(theta), [1]), (gen_CNOT(), [0, 1]), (gen_H(), [0]), (gen_H(), [1])])
243 def gen_Ryy_decomp(theta):
return compile_gates(2, [(gen_Rx(sympy.pi/2), [0]), (gen_Rx(sympy.pi/2), [1]), (gen_CNOT(), [0, 1]), (gen_Rz(theta), [1]), (gen_CNOT(), [0, 1]), (gen_Rx(-sympy.pi/2), [0]), (gen_Rx(-sympy.pi/2), [1])])
244 def gen_Rzz_decomp(theta):
return compile_gates(2, [(gen_CNOT(), [0, 1]), (gen_Rz(theta), [1]), (gen_CNOT(), [0, 1])])
245 def gen_Rxy_decomp(phi):
return compile_gates(2, [(gen_H(), [0]), (gen_H(), [1]), (gen_CNOT(), [0,1]), (gen_Rz(phi/2), [1]), (gen_CNOT(), [0,1]), (gen_H(), [0]), (gen_H(), [1]), (gen_Sdg(), [0]), (gen_Sdg(), [1]), (gen_H(), [0]), (gen_H(), [1]), (gen_CNOT(), [0,1]), (gen_Rz(phi/2), [1]), (gen_CNOT(), [0,1]), (gen_H(), [0]), (gen_H(), [1]), (gen_S(), [0]), (gen_S(), [1])]).applyfunc(quantsimprw)
246 def gen_CZPowGate_decomp(t):
return gen_CP_decomp(sympy.pi*t)
247 def gen_iSWAP_pow_decomp(alpha):
return compile_gates(2, [(gen_H(), [0]), (gen_H(), [1]), (gen_CNOT(), [0,1]), (gen_Rz(-(alpha*sympy.pi)/2), [1]), (gen_CNOT(), [0,1]), (gen_H(), [0]), (gen_H(), [1]), (gen_Sdg(), [0]), (gen_Sdg(), [1]), (gen_H(), [0]), (gen_H(), [1]), (gen_CNOT(), [0,1]), (gen_Rz(-(alpha*sympy.pi)/2), [1]), (gen_CNOT(), [0,1]), (gen_H(), [0]), (gen_H(), [1]), (gen_S(), [0]), (gen_S(), [1])]).applyfunc(quantsimprw)
248 def gen_fSim_decomp(theta, phi):
return compile_gates(2, [(gen_iSWAP_pow_decomp(-2*theta/sympy.pi), [0, 1]), (gen_CZPowGate_decomp(-phi/sympy.pi), [0, 1])])
251 def gen_SYC_decomp():
return compile_gates(2, [(gen_Rz(-3*sympy.pi/4), [0]), (gen_Rz(sympy.pi/4), [1]), (gen_SX(), [0]), (gen_SX(), [1]), (gen_Rz(-sympy.pi), [0]), (gen_Rz(sympy.pi), [1]), (gen_SX(), [1]), (gen_Rz(5*sympy.pi/2), [1]), (gen_CNOT(), [0, 1]), (gen_SX(), [0]), (gen_Rz(-3*sympy.pi/4), [1]), (gen_SX(), [1]), (gen_Rz(sympy.pi), [1]), (gen_SX(), [1]), (gen_Rz(9*sympy.pi/4), [1]), (gen_CNOT(), [0, 1]), (gen_Rz(sympy.pi/2), [0]), (gen_SX(), [1]), (gen_SX(), [0]), (gen_Rz(sympy.pi/2), [1]), (gen_Rz(11*sympy.pi/12), [0]), (gen_SX(), [1]), (gen_SX(), [0]), (gen_Rz(sympy.pi/2), [1]), (gen_CNOT(), [0, 1]), (gen_SX(), [0]), (gen_Rz(sympy.pi/2), [1]), (gen_Rz(sympy.pi/6), [0]), (gen_SX(), [1]), (gen_Rz(-sympy.pi/3), [1]), (gen_GP(-7*sympy.pi/24, 2), [0, 1])]).rewrite(quantsimprw)
252 def gen_CU_decomp(theta, phi, lbda, gamma):
return compile_gates(2, [(gen_Rz((phi-lbda)/2), [1]), (gen_CNOT(), [0, 1]), (gen_Rz(-(phi+lbda)/2), [1]), (gen_Ry(-theta/2), [1]), (gen_CNOT(), [0, 1]), (gen_Ry(theta/2), [1]), (gen_Rz(lbda), [1]), (gen_P((lbda+phi)/2+gamma), [0])])
253 def gen_CU3_decomp(theta, phi, lbda):
return compile_gates(2, [(gen_Rz((phi-lbda)/2), [1]), (gen_CNOT(), [0, 1]), (gen_Rz(-(phi+lbda)/2), [1]), (gen_Ry(-theta/2), [1]), (gen_CNOT(), [0, 1]), (gen_Ry(theta/2), [1]), (gen_Rz(lbda), [1]), (gen_P((lbda+phi)/2), [0])])
254 def gen_CCZ_decomp():
return compile_gates(3, [(gen_CNOT(), [1, 2]), (gen_Tdg(), [2]), (gen_CNOT(), [0, 2]), (gen_T(), [2]), (gen_CNOT(), [1, 2]), (gen_Tdg(), [2]), (gen_CNOT(), [0, 2]), (gen_T(), [1]), (gen_T(), [2]), (gen_CNOT(), [0, 1]), (gen_T(), [0]), (gen_Tdg(), [1]), (gen_CNOT(), [0, 1])])
255 def gen_CCX_decomp():
return compile_gates(3, [(gen_H(), [2]), (gen_CNOT(), [1, 2]), (gen_Tdg(), [2]), (gen_CNOT(), [0, 2]), (gen_T(), [2]), (gen_CNOT(), [1, 2]), (gen_Tdg(), [2]), (gen_CNOT(), [0, 2]), (gen_T(), [1]), (gen_T(), [2]), (gen_H(), [2]), (gen_CNOT(), [0, 1]), (gen_T(), [0]), (gen_Tdg(), [1]), (gen_CNOT(), [0, 1])])
256 def gen_SWAP_decomp():
return compile_gates(2, [(gen_CNOT(), [0, 1]), (gen_CNOT(), [1, 0]), (gen_CNOT(), [0, 1])])
258 def gen_CSWAP_decomp():
return sympy.simplify(sympy.expand_complex(compile_gates(3, [(gen_S(), [1]), (gen_CNOT(), [2, 1]), (gen_Sdg(), [1]), (gen_SX(), [2]), (gen_T(), [2]), (gen_CNOT(), [0, 2]), (gen_T(), [2]), (gen_CNOT(), [1, 2]), (gen_T(), [1]), (gen_Tdg(), [2]), (gen_CNOT(), [0, 2]), (gen_CNOT(), [0, 1]), (gen_T(), [2]), (gen_T(), [0]), (gen_Tdg(), [1]), (gen_H(), [2]), (gen_CNOT(), [0, 1]), (gen_CNOT(), [2, 1]), (gen_GP(-sympy.pi/4, 3), [0, 1, 2])])))
260 def gen_iSWAP_decomp():
return compile_gates(2, [(gen_S(), [0]), (gen_S(), [1]), (gen_H(), [0]), (gen_CNOT(), [0, 1]), (gen_CNOT(), [1, 0]), (gen_H(), [1])])
261 def gen_SX_test():
return sympy.
Matrix([[1+sympy.I, 1-sympy.I], [1-sympy.I, 1+sympy.I]])/2
262 def gen_Rx_test(theta):
return sympy.
Matrix([[sympy.cos(theta/2), -sympy.I*sympy.sin(theta/2)], [-sympy.I*sympy.sin(theta/2), sympy.cos(theta/2)]])
263 def gen_SYC_test():
return sympy.
Matrix([[1, 0, 0, 0], [0, 0, -sympy.I, 0], [0, -sympy.I, 0, 0], [0, 0, 0, sympy.exp(-sympy.I*sympy.pi/6)]])
267 assert gen_SYC() == gen_SYC_decomp(), (gen_SYC(), gen_SYC_decomp())
268 assert gen_Rx(theta) == gen_Rx_test(theta)
269 assert gen_SX() == gen_SX_test()
270 assert gen_SYC() == gen_SYC_test()
271 assert gen_CZ() == gen_CZ_decomp()
272 assert gen_CY() == gen_CY_decomp()
273 assert gen_CRX(theta) == gen_CRX_decomp(theta)
274 assert gen_CRY(theta) == gen_CRY_decomp(theta)
275 assert gen_CRZ(theta) == gen_CRZ_decomp(theta)
276 assert gen_CCZ() == gen_CCZ_decomp()
277 assert gen_CCX() == gen_CCX_decomp()
278 assert gen_CP(phi) == gen_CP_decomp(phi)
279 assert gen_CS() == gen_CS_decomp()
280 assert gen_CH() == gen_CH_decomp()
281 assert gen_CR(theta, phi) == gen_CR_decomp(theta, phi)
282 assert gen_Rxx(theta) == gen_Rxx_decomp(theta)
283 assert gen_Ryy(theta) == gen_Ryy_decomp(theta)
284 assert gen_Rzz(theta) == gen_Rzz_decomp(theta)
285 assert gen_Rxy(phi) == gen_Rxy_decomp(phi)
286 assert gen_CROT(theta, phi) == gen_CROT_decomp(theta, phi)
287 assert gen_CZPowGate(alpha) == gen_CZPowGate_decomp(alpha)
288 assert gen_iSWAP_pow(alpha) == gen_iSWAP_pow_decomp(alpha), (gen_iSWAP_pow(alpha), gen_iSWAP_pow_decomp(alpha))
289 assert gen_fSim(theta, phi) == gen_fSim_decomp(theta, phi), (gen_fSim(theta, phi), gen_fSim_decomp(theta, phi))
290 assert gen_SYC() == gen_SYC_decomp(), (gen_SYC(), gen_SYC_decomp())
291 assert gen_CU3(theta, phi, lbda) == gen_CU3_decomp(theta, phi, lbda)
292 assert gen_CU(theta, phi, lbda, gamma) == gen_CU_decomp(theta, phi, lbda, gamma)
293 assert gen_SWAP() == gen_SWAP_decomp()
294 assert gen_CSWAP() == gen_CSWAP_decomp()
295 assert gen_iSWAP() == gen_iSWAP_decomp()
297 assert compile_gates(2, [(gen_CNOT(), [1, 0])]) == compile_gates(2, [(gen_H(), [0]), (gen_H(), [1]), (gen_CNOT(), [0, 1]), (gen_H(), [0]), (gen_H(), [1]),])
298 print(find_control_qubits(gen_U3(theta, phi, lbda), 1), find_control_qubits(gen_CRY(theta), 2), find_control_qubits(gen_CCX(), 3))
300 print(f
"U3({i})@CRY(0, 1) pure, sparse control:", find_control_qubits(compile_gates(3, [(gen_U3(theta, phi, lbda), [i]), (gen_CRY(theta2), (0, 1))]), 3))
304 print(f
"CRY({i}, {j})@CRY(0, 1) pure, sparse control:", find_control_qubits(compile_gates(3, [(gen_CRY(theta), [i, j]), (gen_CRY(theta2), (0, 1))]), 3))
310 from projectq.ops
import H, X, Y, Z, S, SqrtX, Sdag, T, Tdag, R, Rx, Ry, Rz, CNOT, CZ
311 def _eval_angle(expr):
313 expr = expr.strip().lower().replace(
"pi", f
"({math.pi})")
314 return eval(expr, {
"__builtins__": {}}, {})
317 _rx = re.compile(
r"rx\(([^)]+)\)\s+q\[(\d+)\];", re.I)
318 _ry = re.compile(
r"ry\(([^)]+)\)\s+q\[(\d+)\];", re.I)
319 _rz = re.compile(
r"rz\(([^)]+)\)\s+q\[(\d+)\];", re.I)
320 _u3 = re.compile(
r"u3\(([^,]+),([^,]+),([^)]+)\)\s+q\[(\d+)\];", re.I)
321 _one = re.compile(
r"(h|x|y|z|s|t|sdg|tdg|sx|r)\s+q\[(\d+)\];", re.I)
322 _cx = re.compile(
r"cx\s+q\[(\d+)\],\s*q\[(\d+)\];", re.I)
323 _cz = re.compile(
r"cz\s+q\[(\d+)\],\s*q\[(\d+)\];", re.I)
324 _meas = re.compile(
r"measure\s+q\[(\d+)\]\s*->\s*([A-Za-z_]\w*)\[(\d+)\];", re.I)
325 _creg = re.compile(
r"creg\s+([A-Za-z_]\w*)\[(\d+)\];", re.I)
326 _qreg = re.compile(
r"qreg\s+q\[(\d+)\];", re.I)
327 with open(filename,
"r") as fh: 328 lines = [ln.strip() for ln
in fh
if ln.strip()
and not ln.startswith(
"//")]
333 nq =
int(m.group(1));
break 334 qureg = eng.allocate_qureg(nq)
335 if not initial_state
is None:
337 eng.backend.set_wavefunction(initial_state.tolist(), qureg)
340 if ln.lower().startswith((
"openqasm",
"include",
"qreg",
"creg",
"barrier",
"measure")):
344 gate, i = m.group(1).lower(),
int(m.group(2))
345 replay.append(({
"h":H,
"x":X,
"y":Y,
"z":Z,
"s":S,
"t":T,
"sdg":Sdag,
"tdg":Tdag,
"sx":SqrtX,
"r":R }[gate], qureg[i])) 348 if m: replay.append((Rx(_eval_angle(m.group(1))), qureg[
int(m.group(2))]));
continue 350 if m: replay.append((Ry(_eval_angle(m.group(1))), qureg[
int(m.group(2))]));
continue 352 if m: replay.append((Rz(_eval_angle(m.group(1))), qureg[
int(m.group(2))]));
continue 355 theta, phi, lam, i = (_eval_angle(m.group(1)), _eval_angle(m.group(2)), _eval_angle(m.group(3)),
int(m.group(4)))
357 replay.append((Rz(phi), qureg[i])); replay.append((Ry(theta), qureg[i])); replay.append((Rz(lam), qureg[i]))
360 if m: replay.append((CNOT, (qureg[
int(m.group(1))], qureg[
int(m.group(2))])));
continue 362 if m: replay.append((CZ, (qureg[
int(m.group(1))], qureg[
int(m.group(2))])));
continue 368 _ALLOWED_NODES = (ast.Expression, ast.BinOp, ast.UnaryOp, ast.Num, ast.Load,
369 ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Pow, ast.USub, ast.UAdd,
370 ast.Name, ast.Constant, ast.Call)
371 _ALLOWED_NAMES = {
"pi": math.pi}
374 def _safe_eval(expr: str) -> float:
375 """Evaluate a simple arithmetic expression with 'pi' safely.""" 377 expr = expr.replace(
"â",
"-").strip()
378 node = ast.parse(expr, mode=
"eval")
379 for n
in ast.walk(node):
380 if not isinstance(n, _ALLOWED_NODES):
381 raise ValueError(f
"Disallowed token in angle expression: {type(n).__name__}")
382 if isinstance(n, ast.Name)
and n.id
not in _ALLOWED_NAMES:
383 raise ValueError(f
"Unknown name in angle expression: {n.id}")
384 if isinstance(n, ast.Call):
385 raise ValueError(
"Function calls not allowed in angle expressions")
386 return float(eval(compile(node,
"<angle>",
"eval"), {
"__builtins__": {}}, _ALLOWED_NAMES))
388 _angle_gate =
r"(u1|u2|u3|rx|ry|rz|p|cp|r|crx|cry|crz)" 390 _param_pat = re.compile(rf
"\b{_angle_gate}\s*\(([^)]+)\)", re.IGNORECASE)
392 def _format_float(x: float) -> str:
395 """Replace symbolic angle expressions (with 'pi') by numeric literals.""" 396 def _repl(m: re.Match) -> str:
400 parts = [p.strip()
for p
in params.split(
",")]
401 new_parts = [_format_float(_safe_eval(p))
for p
in parts]
402 return f
"{gate}(" +
",".join(new_parts) +
")" 403 return _param_pat.sub(_repl, qasm)
406 return state / np.linalg.norm(state)
408 """Checks if two quantum state vectors are equal up to a global phase.""" 414 phase_factor = np.vdot(psi, phi)
415 phase = np.angle(phase_factor)
418 return np.allclose(psi * np.exp(-1j * phase), phi)
421 SVSIM_METHOD_NAMES = [
"SQUANDER",
"Qiskit",
"qulacs",
"Cirq",
"ProjectQ"]
422 files = glob.glob(
"benchmarks/partitioning/test_circuit/*.qasm")
423 print(f
"Total QASM: {len(files)}, max qubits: {max_qubits}")
424 for filename
in files:
425 qc = QuantumCircuit.from_qasm_file(filename)
427 num_gates = len(qc.data)
428 fname = os.path.basename(filename)
429 if num_gates > MAX_GATES_ALLOWED
or not qc_gates_names.issubset(SUPPORTED_GATES_NAMES)
or filename.endswith(
"_qsearch.qasm")
or filename.endswith(
"_squander.qasm"):
430 print(f
"Skipping {fname}; qubits {qc.num_qubits} gates {qc_gates_names} num_gates {num_gates}")
432 qbit_num = qc.num_qubits
433 matrix_size = 1 << qbit_num
434 print(f
"Testing {fname}; qubits {qbit_num} num_gates {num_gates} matrix_size {matrix_size}")
435 if (random_initial_state ) :
436 initial_state_real = np.random.uniform(-1.0,1.0, (matrix_size,) )
437 initial_state_imag = np.random.uniform(-1.0,1.0, (matrix_size,) )
438 initial_state = initial_state_real + initial_state_imag*1j
441 initial_state = np.zeros( (matrix_size), dtype=np.complex128 )
442 initial_state[0] = 1.0 + 0j
444 for method
in SVSIM_METHOD_NAMES:
445 output = [
None,
None]
446 if method ==
"SQUANDER":
448 transformed_state = initial_state.copy()
450 circ.set_min_fusion(14)
452 circ.apply_to(params, transformed_state)
453 output[0] = transformed_state
455 elif method ==
"Qiskit":
456 import qiskit_aer
as Aer
457 from qiskit
import transpile
459 circuit_qiskit = QuantumCircuit.from_qasm_file(filename)
460 init = QuantumCircuit(qbit_num)
461 init.initialize( initial_state )
462 circuit_qiskit = circuit_qiskit.compose(init, front=
True)
463 circuit_qiskit.save_statevector()
464 backend = Aer.AerSimulator(method=
'statevector', fusion_enable=
True, fusion_max_qubit=max_qubits)
465 compiled_circuit = transpile(circuit_qiskit, backend)
467 result = backend.run(compiled_circuit).
result()
468 transformed_state = result.get_statevector(compiled_circuit)
469 output[0] = np.array(transformed_state)
471 elif method ==
"qulacs":
472 from qulacs
import QuantumState, converter
475 state = QuantumState(qbit_num)
476 state.load( initial_state )
477 with open(filename,
'r') as file: 478 circuit_qulacs = converter.convert_QASM_to_qulacs_circuit([preprocess_qasm_angles(x) for x
in file.readlines()
if not x.startswith(
"creg")])
480 circuit_qulacs.update_quantum_state( state )
481 transformed_state = state.get_vector()
482 output[0] = transformed_state
484 elif method ==
"Cirq":
486 from cirq.contrib.qasm_import
import circuit_from_qasm
489 with open(filename,
'r') as file: 490 circuit_cirq = circuit_from_qasm(file.read()) 493 options = qsimcirq.QSimOptions(max_fused_gate_size=max_qubits, cpu_threads=os.cpu_count())
494 qs = qsimcirq.QSimSimulator(options)
495 state = initial_state.astype(np.complex64)
497 result = qs.simulate(circuit_cirq, qubit_order=[cirq.NamedQubit(f
"q_{i}")
for i
in range(qbit_num-1,-1,-1)], initial_state=state)
499 transformed_state = result.final_state_vector
500 output[0] = transformed_state
502 elif method ==
"ProjectQ":
503 from projectq
import MainEngine
504 from projectq.backends
import Simulator
505 from projectq.ops
import Measure, All
507 eng = MainEngine(Simulator(gate_fusion=
True))
510 for op
in replay: op[0] | op[1]
512 _, psi = eng.backend.cheat()
513 transformed_state = np.array(psi, dtype=complex)
514 output[0] = transformed_state
518 tpart = timeit.timeit(f, number=1)
519 trun = timeit.timeit(output[1], number=1)
520 res[method] = (tpart, trun, output[0])
522 overlap = np.abs(np.dot(res[
"SQUANDER"][2], np.conj(res[method][2])))
523 assert np.isclose(overlap, 1.0, atol=1e-3), overlap
525 print({x: res[x][:2]
for x
in res})
529 Benchmark partitioning strategies on QASM circuits 532 max_qubits: Max qubits per partition 534 files = glob.glob(
"benchmarks/partitioning/test_circuit/*.qasm")
535 print(f
"Total QASM: {len(files)}, max qubits: {max_qubits}")
537 for filename
in files:
538 qc = QuantumCircuit.from_qasm_file(filename)
540 num_gates = len(qc.data)
541 fname = os.path.basename(filename)
542 if num_gates > MAX_GATES_ALLOWED
or not qc_gates_names.issubset(SUPPORTED_GATES_NAMES)
or filename.endswith(
"_qsearch.qasm")
or filename.endswith(
"_squander.qasm"):
543 print(f
"Skipping {fname}; qubits {qc.num_qubits} gates {qc_gates_names} num_gates {num_gates}")
545 circ, _ = utils.qasm_to_squander_circuit(filename)
546 gate_dict = {i: gate
for i, gate
in enumerate(circ.get_Gates())}
547 gate_to_qubit = { i:
get_qubits(g)
for i, g
in gate_dict.items() }
548 gate_to_tqubit = { i: g.get_Target_Qbit()
for i, g
in gate_dict.items() }
549 print(fname, qc.num_qubits, num_gates, f
"k={max_qubits}",
550 "Max. float ops:",
total_float_ops(qc.num_qubits, max_qubits, gate_to_qubit,
None, [[i]
for i
in gate_dict]),
551 "Control-aware Max. float ops:",
total_float_ops(qc.num_qubits, max_qubits, gate_to_qubit, gate_to_tqubit, [[i]
for i
in gate_dict]))
553 for method
in METHOD_NAMES:
558 t = timeit.timeit(f, number=1)
559 partitioned_circuit, parameters, L = ls
560 res[method] = len(partitioned_circuit.get_Gates()), t,
total_float_ops(qc.num_qubits, max_qubits, gate_to_qubit,
None, L),
total_float_ops(qc.num_qubits, max_qubits, gate_to_qubit, gate_to_tqubit, L)
561 allfiles[fname] = (qc.num_qubits, num_gates, res)
562 print(fname, allfiles[fname])
564 print(json.dumps(allfiles, indent=2))
565 import matplotlib.pyplot
as plt
566 sorted_items = sorted(allfiles.items(), key=
lambda item: (item[1][0], item[1][1]))
567 circuits_sorted = [name
for name, _
in sorted_items]
568 circuits_labels = [name.replace(
".qasm", f
" ({x[0]}, {x[1]})")
for name, x
in sorted_items]
569 print(
"Circuit Name & Qubit Count & Gate Count & " +
" & ".join(METHOD_NAMES) +
r"\\")
571 ilpbest = [name
for name
in circuits_sorted
if allfiles[name][2][
"ilp"][0] != min(allfiles[name][2][method][0]
for method
in METHOD_NAMES
if method !=
"ilp")]
572 ilptie = [name
for name
in circuits_sorted
if allfiles[name][2][
"ilp"][0] == min(allfiles[name][2][method][0]
for method
in METHOD_NAMES
if method !=
"ilp")]
573 print(
"\n".join(
" & ".join((name.replace(
".qasm",
"").replace(
"_",
r"\_"), str(allfiles[name][0]), str(allfiles[name][1]),
574 *((
r"\textbf{" if allfiles[name][2][method][0] == m
else "") + str(allfiles[name][2][method][0]) + (
r"}" if allfiles[name][2][method][0] == m
else "")
for method
in METHOD_NAMES))) +
r"\\" for name
in ilpbest
for m
in (min(allfiles[name][2][method][0]
for method
in METHOD_NAMES),)))
575 print(
"\n".join(
" & ".join((name.replace(
".qasm",
"").replace(
"_",
r"\_"), str(allfiles[name][0]), str(allfiles[name][1]),
576 *((
r"\textbf{" if allfiles[name][2][method][0] == m
else "") + str(allfiles[name][2][method][0]) + (
r"}" if allfiles[name][2][method][0] == m
else "")
for method
in METHOD_NAMES))) +
r"\\" for name
in ilptie
for m
in (min(allfiles[name][2][method][0]
for method
in METHOD_NAMES),)))
578 print(
"\n".join(
" & ".join((name.replace(
".qasm",
"").replace(
"_",
r"\_"), str(allfiles[name][0]), str(allfiles[name][1]),
579 *((
r"\textbf{" if allfiles[name][2][method][0] == m
else "") + str(allfiles[name][2][method][0]) + (
r"}" if allfiles[name][2][method][0] == m
else "")
for method
in METHOD_NAMES))) +
r"\\" for name
in circuits_sorted
for m
in (min(allfiles[name][2][method][0]
for method
in METHOD_NAMES),)))
580 markers = [
"o",
"*",
"D",
"s",
"+",
"<",
">",
"v",
"^"]
582 title = [
"Total Partitions",
"Runtime Performance",
"Total Float Ops for Fusion",
"Total Float Ops for Control-Aware Fusion"][j]
583 y_label = [
"Partition Count",
"Time in seconds",
"Number of Float Ops",
"Number of Float Ops"][j]
584 for i, strat
in enumerate(METHOD_NAMES):
585 y = [allfiles[name][2][strat][j]
for name
in circuits_sorted]
586 plt.plot(circuits_labels, y, marker=markers[i], label=strat)
587 plt.xlabel(
"Circuit (sorted by qubits, gates)")
588 plt.ylabel(y_label, fontsize=6)
589 plt.title(f
"{y_label} - Maximum k={max_qubits} Qubits per Partition")
590 plt.xticks(rotation=45, ha=
"right")
594 plt.savefig(f
"{title.replace(' ', '_')}-{max_qubits}-max_qubit.svg", format=
"svg", transparent=
True)
597 if __name__ ==
"__main__":
598 for max_qubits
in range(3, 6):
def projectq_import_qasm(filename, eng, initial_state=None)
def apply_to(self, parameters_mtx, state_to_be_transformed)
def test_simulation(max_qubits=4, random_initial_state=True)
def preprocess_qasm_angles
def test_partitions(max_qubits=4)
Double-precision complex matrix (float64).
def state_vector_equivalence(psi, phi)
def normalize_state(state)