OpenQASM 3.0 & High-Throughput ML

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'