Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
test_float32_performance.py
Go to the documentation of this file.
1 import os
2 import time
3 
4 import numpy as np
5 import pytest
6 
7 from squander import CNOT, U3
8 
9 # Pin to a single core to eliminate CPU migration / frequency variance across runs.
10 if hasattr(os, "sched_setaffinity"):
11  os.sched_setaffinity(0, {0})
12 
13 
14 def _has_avx2():
15  """Return True if the CPU supports AVX2 (needed for efficient float32 SIMD)."""
16  try:
17  with open("/proc/cpuinfo") as f:
18  return any("avx2" in line for line in f)
19  except OSError:
20  return None # non-Linux — can't tell, assume yes
21 
22 
23 QUBIT_NUM = 12
24 COLS = 256
25 WARMUP = 30
26 REPEATS = 40
27 TRIALS = 5
28 MIN_FLOAT32_SPEEDUP = 2.0
29 MIN_CNOT_SPEEDUP = 0.25
30 
31 
32 def _make_cnot():
33  return CNOT(QUBIT_NUM, 0, QUBIT_NUM - 1)
34 
35 
36 def _make_u3():
37  return U3(QUBIT_NUM, 0)
38 
39 
40 def _parameters(gate, dtype):
41  pnum = gate.get_Parameter_Num()
42  if pnum == 0:
43  return np.asarray([], dtype=dtype)
44  return np.linspace(0.1, 0.1 * pnum, pnum, dtype=dtype)
45 
46 
48  rng = np.random.default_rng(20260527)
49  data64 = np.ascontiguousarray(
50  rng.standard_normal((1 << QUBIT_NUM, COLS))
51  + 1j * rng.standard_normal((1 << QUBIT_NUM, COLS)),
52  dtype=np.complex128,
53  )
54  return data64, data64.astype(np.complex64)
55 
56 
57 def _apply(gate, matrix, params, is_f32):
58  if gate.get_Parameter_Num() == 0:
59  gate.apply_to(matrix, parallel=0, is_f32=is_f32)
60  else:
61  gate.apply_to(matrix, parameters=params, parallel=0, is_f32=is_f32)
62 
63 
64 def _time_apply(gate, dtype):
65  matrix64, matrix32 = _make_inputs()
66  is_f32 = dtype == np.float32
67  matrix = matrix32 if is_f32 else matrix64
68  params = _parameters(gate, dtype)
69 
70  for _ in range(WARMUP):
71  _apply(gate, matrix, params, is_f32=is_f32)
72 
73  start = time.process_time()
74  for _ in range(REPEATS):
75  _apply(gate, matrix, params, is_f32=is_f32)
76  return time.process_time() - start
77 
78 
79 @pytest.mark.parametrize(
80  "gate_factory,gate_name,min_speedup",
81  [
82  pytest.param(_make_u3, "U3", MIN_FLOAT32_SPEEDUP, id="U3"),
83  pytest.param(_make_cnot, "CNOT", MIN_CNOT_SPEEDUP, id="CNOT"),
84  ],
85 )
86 def test_float32_apply_to_hot_path_has_expected_speed(gate_factory, gate_name, min_speedup):
87  """Float32 should be a real HPC path, not just accepted at the API boundary."""
88  if _has_avx2() is False:
89  pytest.skip("CPU lacks AVX2 — float32 SIMD path cannot meet speedup threshold")
90 
91  # Burn-in: one full round to warm CPU frequency / caches, then discard.
92  _time_apply(gate_factory(), np.float64)
93  _time_apply(gate_factory(), np.float32)
94 
95  # Quick self-check: if float32 can't even beat float64 after warmup,
96  # the machine is in a degraded state (thermal throttle / powersave governor).
97  t64_check = _time_apply(gate_factory(), np.float64)
98  t32_check = _time_apply(gate_factory(), np.float32)
99  if t32_check >= t64_check:
100  pytest.skip(
101  f"Machine in degraded state — float32 ({t32_check:.3f}s) not faster "
102  f"than float64 ({t64_check:.3f}s)"
103  )
104 
105  timings = []
106  for _ in range(TRIALS):
107  t64 = _time_apply(gate_factory(), np.float64)
108  t32 = _time_apply(gate_factory(), np.float32)
109  timings.append(t64 / t32)
110 
111  # Drop the first (coldest) trial, use min of the rest for a conservative estimate.
112  speedup = float(np.min(timings[1:]))
113  assert speedup >= min_speedup, (
114  f"{gate_name} float32 speedup {speedup:.2f}x is below "
115  f"{min_speedup:.1f}x; timings={timings}"
116  )
def test_float32_apply_to_hot_path_has_expected_speed(gate_factory, gate_name, min_speedup)
def _apply(gate, matrix, params, is_f32)