2 Copyright 2020 Peter Rakyta, Ph.D. 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 8 http://www.apache.org/licenses/LICENSE-2.0 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 16 You should have received a copy of the GNU General Public License 17 along with this program. If not, see http://www.gnu.org/licenses/. 27 from qiskit
import QuantumCircuit
29 from squander
import Qiskit_IO
30 from squander
import utils
37 for name
in dir(gate):
38 if name.startswith(
"_"):
40 obj = getattr(gate, name)
41 if not inspect.isclass(obj):
44 if issubclass(obj, gate.Gate):
52 QISKIT_EXCLUDED_GATES = {
"SYC",
"CR",
"CROT"}
53 QISKIT_MATRIX_UNSUPPORTED = {
"Gate"} | QISKIT_EXCLUDED_GATES
54 NATIVE_UNSAFE_MATRIX_GATES = {
"Gate"}
55 NATIVE_UNSAFE_APPLY_GATES = {
"Gate"}
56 DERIVATIVE_TEST_EXCLUDED_GATES = set()
63 for gate_name
in ALL_GATE_NAMES:
64 if gate_name
in DERIVATIVE_TEST_EXCLUDED_GATES:
67 if gate_obj.get_Parameter_Num() > 0:
68 names.append(gate_name)
74 for gate_name
in ALL_GATE_NAMES:
75 if gate_name ==
"Gate":
78 if len(gate_obj.get_Involved_Qbits()) >= 2:
79 names.append(gate_name)
84 gate_cls = getattr(gate, gate_name)
86 if gate_name ==
"Gate":
87 return gate_cls(qbit_num)
88 if gate_name
in {
"SWAP",
"RXX",
"RYY",
"RZZ"}:
89 return gate_cls(qbit_num, [0, qbit_num - 1])
90 if gate_name ==
"CCX":
91 return gate_cls(qbit_num, 0, [qbit_num - 2, qbit_num - 1])
92 if gate_name ==
"CSWAP":
93 return gate_cls(qbit_num, [0, 1], [qbit_num - 1])
94 if gate_name ==
"SYC":
95 return gate_cls(qbit_num, 0, qbit_num - 1)
96 if gate_name.startswith(
"C"):
97 return gate_cls(qbit_num, 0, qbit_num - 1)
98 return gate_cls(qbit_num, 0)
102 pnum = gate_obj.get_Parameter_Num()
104 return np.asarray([], dtype=dtype)
105 return np.linspace(0.1, 0.1 * pnum, pnum, dtype=dtype)
109 if gate_obj.get_Parameter_Num() == 0:
110 return np.asarray(gate_obj.get_Matrix())
111 return np.asarray(gate_obj.get_Matrix(parameters))
114 def _apply_gate(gate_obj, state, parameters, is_f32=False, parallel=0):
115 if gate_obj.get_Parameter_Num() == 0:
116 gate_obj.apply_to(state, parallel=parallel, is_f32=is_f32)
118 gate_obj.apply_to(state, parameters=parameters, parallel=parallel, is_f32=is_f32)
122 if gate_obj.get_Parameter_Num() == 0:
123 return np.asarray(gate_obj.get_Matrix(is_f32=is_f32))
124 return np.asarray(gate_obj.get_Matrix(parameters, is_f32=is_f32))
129 if gate_obj.get_Parameter_Num() == 0:
130 gate_obj.apply_to(out, parallel=0, is_f32=is_f32)
132 gate_obj.apply_to(out, parameters=parameters, parallel=0, is_f32=is_f32)
138 if gate_obj.get_Parameter_Num() == 0:
139 gate_obj.apply_to(out, parallel=0, is_f32=is_f32)
141 gate_obj.apply_to(out, parameters=parameters, parallel=0, is_f32=is_f32)
147 if gate_obj.get_Parameter_Num() == 0:
148 gate_obj.apply_from_right(out, is_f32=is_f32)
150 gate_obj.apply_from_right(out, parameters=parameters, is_f32=is_f32)
155 overlap = np.vdot(a.reshape(-1), b.reshape(-1))
156 if np.abs(overlap) > 0:
157 b = b * np.exp(-1j * np.angle(overlap))
158 err = np.linalg.norm(a - b)
159 assert err < tol, f
"matrix mismatch: {err}" 164 control = circuit.num_qubits - 1
166 qiskit_params = list(parameters)
168 if gate_name
not in {
"U1",
"U2",
"CP"}:
169 qiskit_params[0] = 2.0 * qiskit_params[0]
171 if gate_name ==
"U1":
172 circuit.p(*qiskit_params, target)
173 elif gate_name ==
"U2":
174 circuit.u(np.pi / 2.0, qiskit_params[0], qiskit_params[1], target)
175 elif gate_name ==
"U3":
176 circuit.u(*qiskit_params, target)
177 elif gate_name ==
"CNOT":
178 circuit.cx(control, target)
179 elif gate_name ==
"CU":
188 elif gate_name ==
"CCX":
189 circuit.ccx(circuit.num_qubits - 2, circuit.num_qubits - 1, target)
190 elif gate_name ==
"CSWAP":
191 circuit.cswap(circuit.num_qubits - 1, 0, 1)
192 elif gate_name
in {
"SWAP",
"RXX",
"RYY",
"RZZ"}:
193 method = getattr(circuit, gate_name.lower())
195 method(*qiskit_params, 0, circuit.num_qubits - 1)
197 method(0, circuit.num_qubits - 1)
199 method = getattr(circuit, gate_name.lower())
200 if gate_name.startswith(
"C"):
202 method(*qiskit_params, control, target)
207 method(*qiskit_params, target)
214 FORBIDDEN_MULTI_QUBIT_GATES_IN_CNOT_BASIS = {
215 gate_name
for gate_name
in MULTI_QUBIT_GATE_NAMES
if gate_name !=
"CNOT" 221 c_circuit = Circuit(3)
222 assert c_circuit.get_Qbit_Num() == 3
225 c_circuit = Circuit(3)
227 c_circuit.add_CNOT(0, 1)
229 nums = c_circuit.get_Gate_Nums()
230 assert nums.get(
"U3", 0) == 1
231 assert nums.get(
"CNOT", 0) == 1
233 @pytest.mark.parametrize(
"gate_name", ALL_GATE_NAMES)
238 for name, obj
in inspect.getmembers(gate.Gate(4))
239 if callable(obj)
and not name.startswith(
"_")
242 for method_name
in base_methods:
243 assert hasattr(gate_obj, method_name), f
"{gate_name} missing method {method_name}" 247 if gate_name
not in NATIVE_UNSAFE_MATRIX_GATES:
250 assert gate_obj.get_Name()
251 assert isinstance(gate_obj.get_Involved_Qbits(), list)
252 assert gate_obj.get_Parameter_Num() >= 0
253 if matrix
is not None:
254 assert matrix.shape == (1 << 4, 1 << 4)
256 tgt = gate_obj.get_Target_Qbit()
257 gate_obj.set_Target_Qbit(tgt)
259 tgt_list = list(gate_obj.get_Target_Qbits())
261 gate_obj.set_Target_Qbits(tgt_list)
263 ctrl_list = list(gate_obj.get_Control_Qbits())
265 gate_obj.set_Control_Qbits(ctrl_list)
266 gate_obj.set_Control_Qbit(ctrl_list[0])
268 if gate_name
not in NATIVE_UNSAFE_APPLY_GATES
and matrix
is not None:
269 state = np.random.uniform(-1.0, 1.0, (1 << 4,)) + 1j * np.random.uniform(-1.0, 1.0, (1 << 4,))
270 state = state / np.linalg.norm(state)
271 expected = matrix @ state
272 state_out = state.copy()
274 if gate_obj.get_Parameter_Num() == 0:
275 gate_obj.apply_to(state_out)
277 gate_obj.apply_to(state_out, p)
279 assert np.linalg.norm(state_out - expected) < 1e-8
281 @pytest.mark.parametrize(
"gate_name", [name
for name
in ALL_GATE_NAMES
if name !=
"Gate"])
286 from tests.gates.test_gates import _instantiate_gate, _parameters_for_gate 288 gate_name = {gate_name!r} 291 gate_obj = _instantiate_gate(gate_name) 292 p64 = _parameters_for_gate(gate_obj, dtype=np.float64) 293 p32 = p64.astype(np.float32) 295 state64 = np.random.uniform(-1.0, 1.0, (1 << 4,)) + 1j * np.random.uniform(-1.0, 1.0, (1 << 4,)) 296 state64 = state64.astype(np.complex128) 297 state64 = state64 / np.linalg.norm(state64) 299 state32 = state64.astype(np.complex64) 300 state32 = state32 / np.linalg.norm(state32) 302 out64 = state64.copy() 303 out32 = state32.copy() 305 if gate_obj.get_Parameter_Num() == 0: 306 gate_obj.apply_to(out64, parallel=0, is_f32=False) 307 gate_obj.apply_to(out32, parallel=0, is_f32=True) 309 gate_obj.apply_to(out64, parameters=p64, parallel=0, is_f32=False) 310 gate_obj.apply_to(out32, parameters=p32, parallel=0, is_f32=True) 312 err = float(np.linalg.norm(out32 - out64.astype(np.complex64))) 313 print(json.dumps({{"status": "ok", "err": err}})) 314 except Exception as exc: 315 print(json.dumps({{"status": "exception", "message": str(exc)}})) 318 proc = subprocess.run(
319 [sys.executable,
"-c", script],
325 if proc.returncode != 0:
327 f
"Gate {gate_name} crashed in float32/float64 parity subprocess. " 328 f
"returncode={proc.returncode}\nstdout={proc.stdout}\nstderr={proc.stderr}" 331 result = json.loads(proc.stdout.strip().splitlines()[-1])
332 assert result[
"status"] ==
"ok", result
333 assert result[
"err"] < 1e-4, f
"float32/float64 parity mismatch for {gate_name}: {result['err']}" 335 @pytest.mark.parametrize(
"gate_name", [name
for name
in ALL_GATE_NAMES
if name !=
"Gate"])
340 from tests.gates.test_gates import _instantiate_gate, _parameters_for_gate 342 gate_name = {gate_name!r} 345 gate_obj = _instantiate_gate(gate_name) 346 p64 = _parameters_for_gate(gate_obj, dtype=np.float64) 347 p32 = p64.astype(np.float32) 349 if gate_obj.get_Parameter_Num() == 0: 350 m64 = np.asarray(gate_obj.get_Matrix(is_f32=False)) 351 m32 = np.asarray(gate_obj.get_Matrix(is_f32=True)) 353 m64 = np.asarray(gate_obj.get_Matrix(p64, is_f32=False)) 354 m32 = np.asarray(gate_obj.get_Matrix(p32, is_f32=True)) 356 err = float(np.linalg.norm(m32 - m64.astype(np.complex64))) 357 print(json.dumps({{"status": "ok", "err": err, "dtype64": str(m64.dtype), "dtype32": str(m32.dtype)}})) 358 except Exception as exc: 359 print(json.dumps({{"status": "exception", "message": str(exc)}})) 362 proc = subprocess.run(
363 [sys.executable,
"-c", script],
369 if proc.returncode != 0:
371 f
"Gate {gate_name} crashed in get_Matrix float32/float64 parity subprocess. " 372 f
"returncode={proc.returncode}\nstdout={proc.stdout}\nstderr={proc.stderr}" 375 result = json.loads(proc.stdout.strip().splitlines()[-1])
376 assert result[
"status"] ==
"ok", result
377 assert result[
"dtype64"] ==
"complex128", result
378 assert result[
"dtype32"] ==
"complex64", result
379 assert result[
"err"] < 1e-4, f
"float32/float64 get_Matrix parity mismatch for {gate_name}: {result['err']}" 381 @pytest.mark.parametrize(
"gate_name", [name
for name
in ALL_GATE_NAMES
if name !=
"Gate"])
386 from tests.gates.test_gates import _instantiate_gate, _parameters_for_gate 388 gate_name = {gate_name!r} 391 gate_obj = _instantiate_gate(gate_name) 392 p64 = _parameters_for_gate(gate_obj, dtype=np.float64) 393 p32 = p64.astype(np.float32) 395 inp64 = np.random.uniform(-1.0, 1.0, (1 << 4, 1 << 4)) + 1j * np.random.uniform(-1.0, 1.0, (1 << 4, 1 << 4)) 396 inp64 = inp64.astype(np.complex128) 397 inp32 = inp64.astype(np.complex64) 402 if gate_obj.get_Parameter_Num() == 0: 403 gate_obj.apply_from_right(out64, is_f32=False) 404 gate_obj.apply_from_right(out32, is_f32=True) 406 gate_obj.apply_from_right(out64, parameters=p64, is_f32=False) 407 gate_obj.apply_from_right(out32, parameters=p32, is_f32=True) 409 err = float(np.linalg.norm(out32 - out64.astype(np.complex64))) 410 print(json.dumps({{"status": "ok", "err": err}})) 411 except Exception as exc: 412 print(json.dumps({{"status": "exception", "message": str(exc)}})) 415 proc = subprocess.run(
416 [sys.executable,
"-c", script],
422 if proc.returncode != 0:
424 f
"Gate {gate_name} crashed in apply_from_right float32/float64 parity subprocess. " 425 f
"returncode={proc.returncode}\nstdout={proc.stdout}\nstderr={proc.stderr}" 428 result = json.loads(proc.stdout.strip().splitlines()[-1])
429 assert result[
"status"] ==
"ok", result
430 assert result[
"err"] < 1e-4, f
"float32/float64 apply_from_right parity mismatch for {gate_name}: {result['err']}" 432 @pytest.mark.parametrize(
"gate_name", [name
for name
in ALL_GATE_NAMES
if name !=
"Gate"])
437 from tests.gates.test_gates import _instantiate_gate, _parameters_for_gate 439 gate_name = {gate_name!r} 442 gate_obj = _instantiate_gate(gate_name) 443 p64 = _parameters_for_gate(gate_obj, dtype=np.float64) 444 p32 = p64.astype(np.float32) 448 vec = np.random.uniform(-1.0, 1.0, (1 << 4,)) + 1j * np.random.uniform(-1.0, 1.0, (1 << 4,)) 449 vec = vec.astype(np.complex128) 450 vec = vec / np.linalg.norm(vec) 453 inputs32 = [v.astype(np.complex64) for v in inputs64] 455 out64 = [v.copy() for v in inputs64] 456 out32 = [v.copy() for v in inputs32] 458 if gate_obj.get_Parameter_Num() == 0: 459 gate_obj.apply_to_list(out64, parallel=0, is_f32=False) 460 gate_obj.apply_to_list(out32, parallel=0, is_f32=True) 462 gate_obj.apply_to_list(out64, parameters=p64, parallel=0, is_f32=False) 463 gate_obj.apply_to_list(out32, parameters=p32, parallel=0, is_f32=True) 465 errs = [float(np.linalg.norm(a - b.astype(np.complex64))) for a, b in zip(out32, out64)] 466 print(json.dumps({{"status": "ok", "max_err": float(max(errs))}})) 467 except Exception as exc: 468 print(json.dumps({{"status": "exception", "message": str(exc)}})) 471 proc = subprocess.run(
472 [sys.executable,
"-c", script],
478 if proc.returncode != 0:
480 f
"Gate {gate_name} crashed in apply_to_list float32/float64 parity subprocess. " 481 f
"returncode={proc.returncode}\nstdout={proc.stdout}\nstderr={proc.stderr}" 484 result = json.loads(proc.stdout.strip().splitlines()[-1])
485 assert result[
"status"] ==
"ok", result
486 assert result[
"max_err"] < 1e-4, f
"float32/float64 apply_to_list parity mismatch for {gate_name}: {result['max_err']}" 488 @pytest.mark.parametrize(
"gate_name", [name
for name
in ALL_GATE_NAMES
if name !=
"Gate"])
493 from tests.gates.test_gates import _instantiate_gate, _parameters_for_gate, RECT_COLS_MAX 495 gate_name = {gate_name!r} 498 gate_obj = _instantiate_gate(gate_name) 499 p64 = _parameters_for_gate(gate_obj, dtype=np.float64) 500 p32 = p64.astype(np.float32) 502 rng = np.random.default_rng(2026) 504 # apply_to path: 16xN (N=1..32) 505 left64 = np.ascontiguousarray( 506 rng.standard_normal((1 << 4, RECT_COLS_MAX)) 507 + 1j * rng.standard_normal((1 << 4, RECT_COLS_MAX)), 510 left32 = left64.astype(np.complex64) 512 out_full64 = left64.copy() 513 out_full32 = left32.copy() 514 if gate_obj.get_Parameter_Num() == 0: 515 gate_obj.apply_to(out_full64, parallel=0, is_f32=False) 516 gate_obj.apply_to(out_full32, parallel=0, is_f32=True) 518 gate_obj.apply_to(out_full64, parameters=p64, parallel=0, is_f32=False) 519 gate_obj.apply_to(out_full32, parameters=p32, parallel=0, is_f32=True) 521 left_subset_err64 = 0.0 522 left_subset_err32 = 0.0 523 for ncols in range(1, RECT_COLS_MAX + 1): 524 p64_m = np.ascontiguousarray(left64[:, :ncols].copy()) 525 p32_m = np.ascontiguousarray(left32[:, :ncols].copy()) 526 if gate_obj.get_Parameter_Num() == 0: 527 gate_obj.apply_to(p64_m, parallel=0, is_f32=False) 528 gate_obj.apply_to(p32_m, parallel=0, is_f32=True) 530 gate_obj.apply_to(p64_m, parameters=p64, parallel=0, is_f32=False) 531 gate_obj.apply_to(p32_m, parameters=p32, parallel=0, is_f32=True) 532 left_subset_err64 = max(left_subset_err64, float(np.linalg.norm(p64_m - out_full64[:, :ncols]))) 533 left_subset_err32 = max(left_subset_err32, float(np.linalg.norm(p32_m - out_full32[:, :ncols]))) 535 # apply_from_right path: Nx16 (N=1..32), columns must equal matrix_size. 536 right64 = np.ascontiguousarray( 537 rng.standard_normal((RECT_COLS_MAX, 1 << 4)) 538 + 1j * rng.standard_normal((RECT_COLS_MAX, 1 << 4)), 541 right32 = right64.astype(np.complex64) 543 out_right64 = right64.copy() 544 out_right32 = right32.copy() 545 if gate_obj.get_Parameter_Num() == 0: 546 gate_obj.apply_from_right(out_right64, is_f32=False) 547 gate_obj.apply_from_right(out_right32, is_f32=True) 549 gate_obj.apply_from_right(out_right64, parameters=p64, is_f32=False) 550 gate_obj.apply_from_right(out_right32, parameters=p32, is_f32=True) 552 right_subset_err64 = 0.0 553 right_subset_err32 = 0.0 554 for nrows in range(1, RECT_COLS_MAX + 1): 555 p64_m = np.ascontiguousarray(right64[:nrows, :].copy()) 556 p32_m = np.ascontiguousarray(right32[:nrows, :].copy()) 557 if gate_obj.get_Parameter_Num() == 0: 558 gate_obj.apply_from_right(p64_m, is_f32=False) 559 gate_obj.apply_from_right(p32_m, is_f32=True) 561 gate_obj.apply_from_right(p64_m, parameters=p64, is_f32=False) 562 gate_obj.apply_from_right(p32_m, parameters=p32, is_f32=True) 563 right_subset_err64 = max(right_subset_err64, float(np.linalg.norm(p64_m - out_right64[:nrows, :]))) 564 right_subset_err32 = max(right_subset_err32, float(np.linalg.norm(p32_m - out_right32[:nrows, :]))) 566 # apply_to_list path over 16xN for N=1..32. 568 np.ascontiguousarray( 569 rng.standard_normal((1 << 4, ncols)) + 1j * rng.standard_normal((1 << 4, ncols)), 572 for ncols in range(1, RECT_COLS_MAX + 1) 574 list_in32 = [m.astype(np.complex64) for m in list_in64] 575 ref64 = [m.copy() for m in list_in64] 576 ref32 = [m.copy() for m in list_in32] 578 if gate_obj.get_Parameter_Num() == 0: 579 gate_obj.apply_to_list(list_in64, parallel=0, is_f32=False) 580 gate_obj.apply_to_list(list_in32, parallel=0, is_f32=True) 582 gate_obj.apply_to(m, parallel=0, is_f32=False) 584 gate_obj.apply_to(m, parallel=0, is_f32=True) 586 gate_obj.apply_to_list(list_in64, parameters=p64, parallel=0, is_f32=False) 587 gate_obj.apply_to_list(list_in32, parameters=p32, parallel=0, is_f32=True) 589 gate_obj.apply_to(m, parameters=p64, parallel=0, is_f32=False) 591 gate_obj.apply_to(m, parameters=p32, parallel=0, is_f32=True) 593 list_err64 = max(float(np.linalg.norm(a - b)) for a, b in zip(list_in64, ref64)) 594 list_err32 = max(float(np.linalg.norm(a - b)) for a, b in zip(list_in32, ref32)) 598 "left_subset_err64": left_subset_err64, 599 "left_subset_err32": left_subset_err32, 600 "right_subset_err64": right_subset_err64, 601 "right_subset_err32": right_subset_err32, 602 "list_err64": list_err64, 603 "list_err32": list_err32, 605 except Exception as exc: 606 print(json.dumps({{"status": "exception", "message": str(exc)}})) 609 proc = subprocess.run(
610 [sys.executable,
"-c", script],
616 if proc.returncode != 0:
618 f
"Gate {gate_name} crashed in rectangular apply* sweep subprocess. " 619 f
"returncode={proc.returncode}\nstdout={proc.stdout}\nstderr={proc.stderr}" 622 result = json.loads(proc.stdout.strip().splitlines()[-1])
623 assert result[
"status"] ==
"ok", result
624 assert result[
"left_subset_err64"] < 1e-10, result
625 assert result[
"left_subset_err32"] < 5e-5, result
626 assert result[
"right_subset_err64"] < 1e-10, result
627 assert result[
"right_subset_err32"] < 5e-5, result
628 assert result[
"list_err64"] < 1e-10, result
629 assert result[
"list_err32"] < 5e-5, result
631 @pytest.mark.parametrize(
"gate_name", DERIVATIVE_GATE_NAMES)
636 from tests.gates.test_gates import _instantiate_gate, _parameters_for_gate 638 gate_name = {gate_name!r} 641 gate_obj = _instantiate_gate(gate_name) 642 p64 = _parameters_for_gate(gate_obj, dtype=np.float64) 643 p32 = p64.astype(np.float32) 645 state64 = np.random.uniform(-1.0, 1.0, (1 << 4,)) + 1j * np.random.uniform(-1.0, 1.0, (1 << 4,)) 646 state64 = state64.astype(np.complex128) 647 state64 = state64 / np.linalg.norm(state64) 649 state32 = state64.astype(np.complex64) 650 state32 = state32 / np.linalg.norm(state32) 652 d64 = gate_obj.apply_derivate_to(state64.copy(), parameters=p64, parallel=0, is_f32=False) 653 d32 = gate_obj.apply_derivate_to(state32.copy(), parameters=p32, parallel=0, is_f32=True) 655 if not isinstance(d64, list) or not isinstance(d32, list): 656 raise RuntimeError("apply_derivate_to must return a list") 660 if a.dtype != np.complex128: 661 raise RuntimeError("float64 derivative output must be complex128") 662 if a.size != state64.size: 663 raise RuntimeError("float64 derivative output shape mismatch") 667 if a.dtype != np.complex64: 668 raise RuntimeError("float32 derivative output must be complex64") 669 if a.size != state32.size: 670 raise RuntimeError("float32 derivative output shape mismatch") 672 print(json.dumps({{"status": "ok", "n64": len(d64), "n32": len(d32)}})) 673 except Exception as exc: 674 print(json.dumps({{"status": "exception", "message": str(exc)}})) 677 proc = subprocess.run(
678 [sys.executable,
"-c", script],
684 if proc.returncode != 0:
686 f
"Gate {gate_name} crashed in apply_derivate_to wrapper subprocess. " 687 f
"returncode={proc.returncode}\nstdout={proc.stdout}\nstderr={proc.stderr}" 690 result = json.loads(proc.stdout.strip().splitlines()[-1])
691 assert result[
"status"] ==
"ok", result
693 @pytest.mark.parametrize(
"gate_name", DERIVATIVE_GATE_NAMES)
698 from tests.gates.test_gates import _instantiate_gate, _parameters_for_gate 700 gate_name = {gate_name!r} 703 gate_obj = _instantiate_gate(gate_name) 704 p64 = _parameters_for_gate(gate_obj, dtype=np.float64) 705 p32 = p64.astype(np.float32) 707 state64 = np.random.uniform(-1.0, 1.0, (1 << 4,)) + 1j * np.random.uniform(-1.0, 1.0, (1 << 4,)) 708 state64 = state64.astype(np.complex128) 709 state64 = state64 / np.linalg.norm(state64) 711 state32 = state64.astype(np.complex64) 712 state32 = state32 / np.linalg.norm(state32) 714 c64 = gate_obj.apply_to_combined(state64.copy(), parameters=p64, parallel=0, is_f32=False) 715 c32 = gate_obj.apply_to_combined(state32.copy(), parameters=p32, parallel=0, is_f32=True) 717 if not isinstance(c64, list) or not isinstance(c32, list): 718 raise RuntimeError("apply_to_combined must return a list") 720 if len(c64) != len(p64) + 1 or len(c32) != len(p32) + 1: 721 raise RuntimeError("apply_to_combined returned wrong list length") 723 fwd64_ref = state64.copy() 724 gate_obj.apply_to(fwd64_ref, parameters=p64, parallel=0, is_f32=False) 725 fwd32_ref = state32.copy() 726 gate_obj.apply_to(fwd32_ref, parameters=p32, parallel=0, is_f32=True) 728 if np.linalg.norm(np.asarray(c64[0]).reshape(-1) - fwd64_ref.reshape(-1)) > 1e-10: 729 raise RuntimeError("float64 combined forward output mismatch") 730 if np.linalg.norm(np.asarray(c32[0]).astype(np.complex128).reshape(-1) - fwd32_ref.astype(np.complex128).reshape(-1)) > 5e-5: 731 raise RuntimeError("float32 combined forward output mismatch") 733 print(json.dumps({{"status": "ok", "n64": len(c64), "n32": len(c32)}})) 734 except Exception as exc: 735 print(json.dumps({{"status": "exception", "message": str(exc)}})) 738 proc = subprocess.run(
739 [sys.executable,
"-c", script],
745 if proc.returncode != 0:
747 f
"Gate {gate_name} crashed in apply_to_combined wrapper subprocess. " 748 f
"returncode={proc.returncode}\nstdout={proc.stdout}\nstderr={proc.stderr}" 751 result = json.loads(proc.stdout.strip().splitlines()[-1])
752 assert result[
"status"] ==
"ok", result
754 @pytest.mark.parametrize(
756 [name
for name
in ALL_GATE_NAMES
if name
not in QISKIT_MATRIX_UNSUPPORTED],
763 circuit = QuantumCircuit(4)
765 qiskit_matrix = utils.get_unitary_from_qiskit_circuit_operator(circuit)
769 @pytest.mark.parametrize(
771 [name
for name
in ALL_GATE_NAMES
if name
not in (QISKIT_EXCLUDED_GATES | {
"Gate"})],
777 from squander import Qiskit_IO 778 from squander import utils 779 from squander.gates.qgd_Circuit import qgd_Circuit as Circuit 780 from tests.gates.test_gates import _instantiate_gate, _parameters_for_gate 782 gate_name = {gate_name!r} 785 gate_obj = _instantiate_gate(gate_name) 787 circuit.add_Gate(gate_obj) 788 params = _parameters_for_gate(gate_obj) 790 exported = Qiskit_IO.get_Qiskit_Circuit(circuit, params) 792 print(json.dumps({{"status": "unsupported"}})) 794 exported_inv = Qiskit_IO.get_Qiskit_Circuit_inverse(circuit, params) 795 circ_rt, params_rt = Qiskit_IO.convert_Qiskit_to_Squander(exported) 797 m1 = np.asarray(circuit.get_Matrix(params)) 798 m2 = np.asarray(circ_rt.get_Matrix(params_rt)) 800 overlap = np.vdot(m1.reshape(-1), m2.reshape(-1)) 801 if np.abs(overlap) > 0: 802 m2 = m2 * np.exp(-1j * np.angle(overlap)) 803 matrix_err = float(np.linalg.norm(m1 - m2)) 805 forward = np.asarray(utils.get_unitary_from_qiskit_circuit_operator(exported)) 806 inverse = np.asarray(utils.get_unitary_from_qiskit_circuit_operator(exported_inv)) 807 ident = forward @ inverse 808 ident_raw_err = float(np.linalg.norm(ident - np.eye(ident.shape[0], dtype=np.complex128))) 809 phase = np.angle(np.trace(ident)) 810 ident_phase_aligned = ident * np.exp(-1j * phase) 811 ident_phase_err = float(np.linalg.norm(ident_phase_aligned - np.eye(ident.shape[0], dtype=np.complex128))) 815 "matrix_err": matrix_err, 816 "ident_raw_err": ident_raw_err, 817 "ident_phase_err": ident_phase_err, 819 except Exception as exc: 820 print(json.dumps({{"status": "exception", "message": str(exc)}})) 823 proc = subprocess.run(
824 [sys.executable,
"-c", script],
830 if proc.returncode != 0:
832 f
"Gate {gate_name} crashed in qiskit_IO roundtrip subprocess. " 833 f
"returncode={proc.returncode}\nstdout={proc.stdout}\nstderr={proc.stderr}" 836 result = json.loads(proc.stdout.strip().splitlines()[-1])
838 assert result[
"status"] ==
"ok", result
839 assert result[
"matrix_err"] < 1e-6, result
840 assert result[
"ident_raw_err"] < 1e-6
or result[
"ident_phase_err"] < 1e-6, result
842 if result[
"ident_raw_err"] >= 1e-6:
844 f
"{gate_name} is only equivalent up to global phase. " 845 f
"raw={result['ident_raw_err']}, phase_aligned={result['ident_phase_err']}" 848 @pytest.mark.parametrize(
850 [name
for name
in ALL_GATE_NAMES
if name !=
"Gate"],
856 from squander import utils 857 from squander.gates.qgd_Circuit import qgd_Circuit as Circuit 858 from tests.gates.test_gates import _instantiate_gate, _parameters_for_gate 860 gate_name = {gate_name!r} 863 gate_obj = _instantiate_gate(gate_name) 865 circuit.add_Gate(gate_obj) 866 params = _parameters_for_gate(gate_obj, dtype=np.float64) 868 inv_circuit, inv_params = utils.invert_circuit(circuit, params) 871 M = np.eye(n, dtype=np.complex128) 872 Minv = np.eye(n, dtype=np.complex128) 873 circuit.apply_to(params, M) 874 inv_circuit.apply_to(inv_params, Minv) 877 err = float(np.linalg.norm(product - np.eye(n))) 878 print(json.dumps({{"status": "ok", "err": err}})) 879 except Exception as exc: 881 print(json.dumps({{"status": "exception", "message": str(exc), "trace": traceback.format_exc()}})) 884 proc = subprocess.run(
885 [sys.executable,
"-c", script],
891 if proc.returncode != 0:
893 f
"Gate {gate_name} crashed in invert_circuit subprocess. " 894 f
"returncode={proc.returncode}\nstdout={proc.stdout}\nstderr={proc.stderr}" 897 result = json.loads(proc.stdout.strip().splitlines()[-1])
898 assert result[
"status"] ==
"ok", result
899 assert result[
"err"] < 1e-8, f
"invert_circuit error for {gate_name}: {result['err']}" 901 @pytest.mark.parametrize(
"gate_name", DERIVATIVE_GATE_NAMES)
903 """extract_parameters should normalise each parameter via fmod(m*p, m*2pi). 904 Values already inside [0, m*2pi) should be unchanged; values above should wrap.""" 906 pnum = gate_obj.get_Parameter_Num()
912 small_params = np.linspace(0.1, 0.1 * pnum, pnum, dtype=np.float64)
913 large_params = small_params + 10.0 * two_pi
915 small_extracted = gate_obj.Extract_Parameters(small_params)
916 large_extracted = gate_obj.Extract_Parameters(large_params)
918 assert len(small_extracted) == pnum,
"wrong number of extracted parameters" 919 assert len(large_extracted) == pnum
922 np.testing.assert_allclose(
926 err_msg=f
"{gate_name}: extract_parameters did not wrap large parameters to same value as small",
930 for i, p
in enumerate(small_params):
931 assert small_extracted[i] >= 0.0, f
"{gate_name}: extracted param {i} is negative" 933 @pytest.mark.parametrize(
"gate_name", MULTI_QUBIT_GATE_NAMES)
938 params = np.linspace(0.2, 0.2 * circuit.get_Parameter_Num(), circuit.get_Parameter_Num(), dtype=np.float64)
940 cnot_circuit, cnot_params = utils.circuit_to_CNOT_basis(circuit, params)
942 gate_nums = cnot_circuit.get_Gate_Nums()
943 assert gate_nums.get(
"CNOT", 0) > 0
945 for forbidden_gate
in FORBIDDEN_MULTI_QUBIT_GATES_IN_CNOT_BASIS:
946 assert gate_nums.get(forbidden_gate, 0) == 0, (
947 f
"{forbidden_gate} still present after CNOT-basis conversion " 948 f
"for source gate {gate_name}" 951 original_matrix = np.asarray(circuit.get_Matrix(params))
952 transpiled_matrix = np.asarray(cnot_circuit.get_Matrix(cnot_params))
957 """Direct in-process scalar-path checks for gate methods. 959 The subprocess tests above are crash-isolation sweeps. These tests stay in-process 960 so dtype mistakes and scalar dispatch regressions are reported at the failing call. 963 @pytest.fixture(scope=
"class")
965 rng_state = np.random.default_rng(42)
966 state = rng_state.standard_normal(1 << 4) + 1j * rng_state.standard_normal(1 << 4)
967 state = (state / np.linalg.norm(state)).astype(np.complex128)
969 rng_matrix = np.random.default_rng(7)
970 matrix = rng_matrix.standard_normal((1 << 4, 1 << 4)) + 1j * rng_matrix.standard_normal((1 << 4, 1 << 4))
971 return state, matrix.astype(np.complex128)
973 @pytest.mark.parametrize(
"gate_name", [name
for name
in ALL_GATE_NAMES
if name !=
"Gate"])
979 assert matrix.dtype == np.complex128
980 product = matrix.conj().T @ matrix
981 assert np.linalg.norm(product - np.eye(matrix.shape[0])) < 1e-8
983 @pytest.mark.parametrize(
"gate_name", [name
for name
in ALL_GATE_NAMES
if name !=
"Gate"])
987 p32 = p64.astype(np.float32)
992 assert m32.dtype == np.complex64
993 assert np.linalg.norm(m32 - m64.astype(np.complex64)) < F32_TOL
995 @pytest.mark.parametrize(
"gate_name", [name
for name
in ALL_GATE_NAMES
if name !=
"Gate"])
997 state64, _ = scalar_inputs
1000 p32 = p64.astype(np.float32)
1006 assert np.linalg.norm(result64 - matrix @ state64) < 1e-8
1007 assert np.linalg.norm(result32 - result64.astype(np.complex64)) < F32_TOL
1009 @pytest.mark.parametrize(
"gate_name", [name
for name
in ALL_GATE_NAMES
if name !=
"Gate"])
1011 _, matrix64 = scalar_inputs
1014 p32 = p64.astype(np.float32)
1020 assert np.linalg.norm(result64 - gate_matrix @ matrix64) < 1e-8
1021 assert np.linalg.norm(result32 - result64.astype(np.complex64)) < F32_TOL
1023 @pytest.mark.parametrize(
"gate_name", [name
for name
in ALL_GATE_NAMES
if name !=
"Gate"])
1025 _, matrix64 = scalar_inputs
1028 p32 = p64.astype(np.float32)
1034 assert np.linalg.norm(result64 - matrix64 @ gate_matrix) < 1e-8
1035 assert np.linalg.norm(result32 - result64.astype(np.complex64)) < F32_TOL
1039 """Verify that each parametric gate's apply_derivate_to matches a central finite difference.""" 1044 @pytest.mark.parametrize(
"gate_name", DERIVATIVE_GATE_NAMES)
1046 """Analytic derivative must agree with finite difference (float64).""" 1048 pnum = gate_obj.get_Parameter_Num()
1051 dim = np.asarray(gate_obj.get_Matrix(params)).shape[0]
1052 mat = np.eye(dim, dtype=np.complex128)
1053 derivs = gate_obj.apply_derivate_to(mat.copy(), parameters=params, parallel=0, is_f32=
False)
1055 assert len(derivs) == pnum, f
"{gate_name}: got {len(derivs)} derivatives, expected {pnum}" 1058 for k
in range(pnum):
1059 p_plus = params.copy(); p_plus[k] += eps
1060 p_minus = params.copy(); p_minus[k] -= eps
1061 fd = (np.asarray(gate_obj.get_Matrix(p_plus)) - np.asarray(gate_obj.get_Matrix(p_minus))) / (2 * eps)
1062 err = float(np.linalg.norm(np.asarray(derivs[k]) - fd))
1063 assert err < self.
FD_TOL, (
1064 f
"{gate_name} param {k}: analytic vs FD error = {err:.3e} > {self.FD_TOL:.1e}" def test_gate_apply_to_list_float32_float64_parity(self, gate_name)
def test_gate_get_matrix_float32_float64_parity(self, gate_name)
def _apply_to_matrix_with_precision(gate_obj, matrix, parameters, is_f32)
def _assert_matrices_close(a, b, tol=1e-8)
def test_gate_apply_derivate_wrapper_smoke(self, gate_name)
def test_get_matrix_scalar_f64_unitary(self, gate_name)
def _apply_gate(gate_obj, state, parameters, is_f32=False, parallel=0)
def _get_matrix_with_precision(gate_obj, parameters, is_f32)
def _discover_gate_names()
def test_squander_invert_circuit(self, gate_name)
def test_get_matrix_scalar_f32_parity(self, gate_name)
def test_gate_apply_to_combined_wrapper_smoke(self, gate_name)
def test_extract_parameters_wraps_out_of_range(self, gate_name)
def test_apply_from_right_scalar_matches_matrix_and_f32_parity(self, gate_name, scalar_inputs)
def _apply_to_state_with_precision(gate_obj, state, parameters, is_f32)
def test_apply_to_state_scalar_matches_matrix_and_f32_parity(self, gate_name, scalar_inputs)
def test_apply_to_matrix_scalar_matches_matrix_and_f32_parity(self, gate_name, scalar_inputs)
def _add_gate_to_qiskit(circuit, gate_name, parameters)
def _discover_parameterized_gate_names()
def _discover_multi_qubit_gate_names()
def test_gate_apply_to_float32_float64_parity(self, gate_name)
def test_gate_constructor_and_base_methods(self, gate_name)
def _apply_from_right_with_precision(gate_obj, matrix, parameters, is_f32)
def test_gate_matrix_matches_qiskit(self, gate_name)
def _instantiate_gate(gate_name, qbit_num=4)
string method
[create decomposition class] creating a class to decompose the unitary
def _gate_matrix(gate_obj, parameters)
def test_gate_derivative_fd_f64(self, gate_name)
def test_qiskit_io_roundtrip_per_gate(self, gate_name)
def _parameters_for_gate(gate_obj, dtype=np.float64)
def test_circuit_to_cnot_basis_removes_non_cnot_multi_qubit_gates(self, gate_name)
def test_gate_apply_rectangular_sweep_paths(self, gate_name)
def test_operation_block_add_operations(self)
def test_operation_block_creation(self)
def test_gate_apply_from_right_float32_float64_parity(self, gate_name)