OpenQASM 3.0 & High-Throughput ML#
Quex acts as a highly forgiving OpenQASM 3.0 parser and a strictly compliant exporter.
Let’s read a raw parameterized string, attach a simulator, and run a high-speed Variational loop.
import time
import quex as qx
# 1. Parse a messy, lazy OpenQASM string
qasm_str = """
OPENQASM 3.0;
qubit[2] q;
h q[0];
rx(theta) q[1];
cx q[0], q[1];
"""
qc = qx.Circuit.from_qasm(qasm_str)
print("Parsed and normalized Circuit:")
print(qc)
Parsed and normalized Circuit:
q[0]: ───────[H]───────■───
│
q[1]: ───[RX(theta)]───X───
In an ML loop, you don’t want to change the circuit’s inherent state permanently on every step.
You can use parameter_binds inside the run() method to temporarily override the variables at lightning speed,
completely bypassing the string parser and DAG construction.
qc.simulator = qx.NumpySimulator()
iterations = 500
start_time = time.time()
# High-speed Late Binding execution loop
for i in range(iterations):
# Pass overrides directly to the backend
current_state = qc.run(parameter_binds={"theta": i * 0.01})
end_time = time.time()
print(f"Executed {iterations} unique parameter binds in {end_time - start_time:.4f} seconds!")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[2], line 9
5
6 # High-speed Late Binding execution loop
7 for i in range(iterations):
8 # Pass overrides directly to the backend
----> 9 current_state = qc.run(parameter_binds={"theta": i * 0.01})
10
11 end_time = time.time()
12 print(f"Executed {iterations} unique parameter binds in {end_time - start_time:.4f} seconds!")
File ~/work/quex/quex/src/quex/circuit.py:200, in Circuit.run(self, parameter_binds, initial_state)
198 if self._simulator is None:
199 raise RuntimeError("No Simulator attached! Assign qc.simulator = NumpySimulator() first.")
--> 200 return self._simulator.run(circuit=self, parameter_binds=parameter_binds, initial_state=initial_state)
File ~/work/quex/quex/src/quex/backends/numpy_sim.py:264, in NumpySimulator.run(self, circuit, parameter_binds, initial_state)
259 else:
260 # Default to all-zeros |00...0>
261 # Example: A 3-qubit state is shape (2, 2, 2). Only index [0, 0, 0] is 1.0.
262 state = self._allocate_initial_state(num_qubits)
--> 264 state = self._run_ops[self.exec_mode](target_circuit, state, final_binds)
265 if self.exec_mode not in self._run_ops:
266 raise ValueError(f"Unknown exec_mode: '{self.exec_mode}'. Use 'sequential' or 'fused'.")
File ~/work/quex/quex/src/quex/backends/numpy_sim.py:132, in NumpySimulator._run_sequential(self, circuit, state, final_binds)
130 """The original gate-by-gate loop."""
131 for op in circuit.operations:
--> 132 gate_tensor = self._get_backend_tensor(op["gate"], op["params"], len(op["targets"]))
133 # ... tensordot and moveaxis ...
134 # 2. Iterate through the topological operations
135 for op in circuit.operations:
File ~/work/quex/quex/src/quex/backends/numpy_sim.py:109, in NumpySimulator._get_backend_tensor(self, name, params, num_targets)
104 """
105 Bridge method: Fetches the CPU tensor from the module-level function.
106 Subclasses (like CuPy) will override this to intercept the data.
107 """
108 # Just call your top-level function directly
--> 109 return get_gate_tensor(name, params, num_targets)
File ~/work/quex/quex/src/quex/backends/numpy_sim.py:71, in get_gate_tensor(name, params, num_targets)
69 # Dynamic generation for parameterized gates
70 if name in PARAM_GENERATORS:
---> 71 matrix = PARAM_GENERATORS[name](params)
72 return matrix.reshape((2, 2))
73 raise ValueError(f"Gate '{name}' is not supported yet by NumpySimulator.")
File ~/work/quex/quex/src/quex/backends/numpy_sim.py:19, in _gen_rx(params)
17 def _gen_rx(params: list) -> np.ndarray:
18 theta = params[0]
---> 19 return np.array([[np.cos(theta / 2), -1j * np.sin(theta / 2)], [-1j * np.sin(theta / 2), np.cos(theta / 2)]], dtype=np.complex128)
TypeError: unsupported operand type(s) for /: 'str' and 'int'