@@ -221,3 +221,193 @@ pub fn register_simulator_utils(m: &Bound<'_, PyModule>) -> PyResult<()> {
221221 m. add_class :: < TableauWrapper > ( ) ?;
222222 Ok ( ( ) )
223223}
224+
225+ // --- Shared batch dispatch for simulator bindings ---
226+
227+ use pecos:: core:: QubitId ;
228+ use pecos:: simulators:: { CliffordGateable , MeasurementResult } ;
229+ use pyo3:: types:: { PySet , PyTuple } ;
230+
231+ /// Extract a single qubit index from a Python location.
232+ /// Handles both bare ints and 1-tuples like `(0,)` (the `GateBindingsDict` wraps ints in tuples).
233+ pub fn extract_single_qubit ( location : & Bound < ' _ , PyAny > ) -> PyResult < usize > {
234+ if let Ok ( q) = location. extract :: < usize > ( ) {
235+ return Ok ( q) ;
236+ }
237+ if let Ok ( tuple) = location. downcast :: < PyTuple > ( ) {
238+ if tuple. len ( ) == 1 {
239+ return tuple. get_item ( 0 ) ?. extract :: < usize > ( ) ;
240+ }
241+ }
242+ Err ( PyErr :: new :: < pyo3:: exceptions:: PyTypeError , _ > ( format ! (
243+ "Expected int or 1-tuple for single-qubit location, got {:?}" ,
244+ location. get_type( ) . name( ) ?
245+ ) ) )
246+ }
247+
248+ /// Collect single-qubit locations from a Python set into a Vec of QubitIds.
249+ fn collect_single_qubits ( locations : & Bound < ' _ , PySet > ) -> PyResult < Vec < QubitId > > {
250+ locations
251+ . iter ( )
252+ . map ( |l| Ok ( QubitId ( extract_single_qubit ( & l) ?) ) )
253+ . collect ( )
254+ }
255+
256+ /// Collect single-qubit locations as raw usize values.
257+ fn collect_single_qubit_indices ( locations : & Bound < ' _ , PySet > ) -> PyResult < Vec < usize > > {
258+ locations
259+ . iter ( )
260+ . map ( |l| extract_single_qubit ( & l) )
261+ . collect ( )
262+ }
263+
264+ /// Collect two-qubit pair locations from a Python set.
265+ fn collect_pairs ( locations : & Bound < ' _ , PySet > ) -> PyResult < Vec < ( QubitId , QubitId ) > > {
266+ locations
267+ . iter ( )
268+ . map ( |l| {
269+ let t: ( usize , usize ) = l. extract ( ) ?;
270+ Ok ( ( QubitId ( t. 0 ) , QubitId ( t. 1 ) ) )
271+ } )
272+ . collect ( )
273+ }
274+
275+ /// Build a measurement output dict from qubit indices and results.
276+ fn build_meas_output (
277+ py : Python < ' _ > ,
278+ qubits : & [ usize ] ,
279+ results : Vec < MeasurementResult > ,
280+ ) -> PyResult < Py < PyDict > > {
281+ let output = PyDict :: new ( py) ;
282+ for ( & q, r) in qubits. iter ( ) . zip ( results) {
283+ if r. outcome {
284+ output. set_item ( q, 1u8 ) ?;
285+ }
286+ }
287+ Ok ( output. into ( ) )
288+ }
289+
290+ /// Try to dispatch a gate in batch mode for any `CliffordGateable` simulator.
291+ ///
292+ /// Returns `Some(output_dict)` if the gate was handled, `None` to fall back to
293+ /// per-location dispatch (for parameterized gates, unknown symbols, etc.).
294+ pub fn try_clifford_batch_dispatch < S : CliffordGateable > (
295+ sim : & mut S ,
296+ symbol : & str ,
297+ locations : & Bound < ' _ , PySet > ,
298+ py : Python < ' _ > ,
299+ ) -> PyResult < Option < Py < PyDict > > > {
300+ match symbol {
301+ // Identity
302+ "I" => return Ok ( Some ( PyDict :: new ( py) . into ( ) ) ) ,
303+
304+ // Single-qubit Clifford gates (no return value)
305+ "X" | "Y" | "Z" | "H" | "H1" | "H+z+x" | "H2" | "H-z-x" | "H3" | "H+y-z"
306+ | "H4" | "H-y-z" | "H5" | "H-x+y" | "H6" | "H-x-y" | "F" | "F1" | "Fdg"
307+ | "F1d" | "F1dg" | "F2" | "F2dg" | "F2d" | "F3" | "F3dg" | "F3d" | "F4"
308+ | "F4dg" | "F4d" | "Q" | "SX" | "SqrtX" | "Qd" | "SXdg" | "SqrtXd" | "SqrtXdg"
309+ | "R" | "SY" | "SqrtY" | "Rd" | "SYdg" | "SqrtYd" | "SqrtYdg" | "S" | "SZ"
310+ | "SqrtZ" | "Sd" | "SZdg" | "SqrtZd" | "SqrtZdg" => {
311+ let qubits = collect_single_qubits ( locations) ?;
312+ match symbol {
313+ "X" => { sim. x ( & qubits) ; }
314+ "Y" => { sim. y ( & qubits) ; }
315+ "Z" => { sim. z ( & qubits) ; }
316+ "H" | "H1" | "H+z+x" => { sim. h ( & qubits) ; }
317+ "H2" | "H-z-x" => { sim. h2 ( & qubits) ; }
318+ "H3" | "H+y-z" => { sim. h3 ( & qubits) ; }
319+ "H4" | "H-y-z" => { sim. h4 ( & qubits) ; }
320+ "H5" | "H-x+y" => { sim. h5 ( & qubits) ; }
321+ "H6" | "H-x-y" => { sim. h6 ( & qubits) ; }
322+ "F" | "F1" => { sim. f ( & qubits) ; }
323+ "Fdg" | "F1d" | "F1dg" => { sim. fdg ( & qubits) ; }
324+ "F2" => { sim. f2 ( & qubits) ; }
325+ "F2dg" | "F2d" => { sim. f2dg ( & qubits) ; }
326+ "F3" => { sim. f3 ( & qubits) ; }
327+ "F3dg" | "F3d" => { sim. f3dg ( & qubits) ; }
328+ "F4" => { sim. f4 ( & qubits) ; }
329+ "F4dg" | "F4d" => { sim. f4dg ( & qubits) ; }
330+ "Q" | "SX" | "SqrtX" => { sim. sx ( & qubits) ; }
331+ "Qd" | "SXdg" | "SqrtXd" | "SqrtXdg" => { sim. sxdg ( & qubits) ; }
332+ "R" | "SY" | "SqrtY" => { sim. sy ( & qubits) ; }
333+ "Rd" | "SYdg" | "SqrtYd" | "SqrtYdg" => { sim. sydg ( & qubits) ; }
334+ "S" | "SZ" | "SqrtZ" => { sim. sz ( & qubits) ; }
335+ "Sd" | "SZdg" | "SqrtZd" | "SqrtZdg" => { sim. szdg ( & qubits) ; }
336+ _ => unreachable ! ( ) ,
337+ }
338+ return Ok ( Some ( PyDict :: new ( py) . into ( ) ) ) ;
339+ }
340+
341+ // Preparations (no return value)
342+ "PZ" | "Init" | "Init +Z" | "init |0>" | "leak" | "leak |0>" | "unleak |0>" => {
343+ sim. pz ( & collect_single_qubits ( locations) ?) ;
344+ return Ok ( Some ( PyDict :: new ( py) . into ( ) ) ) ;
345+ }
346+ "PnZ" | "Init -Z" | "init |1>" | "leak |1>" | "unleak |1>" => {
347+ sim. pnz ( & collect_single_qubits ( locations) ?) ;
348+ return Ok ( Some ( PyDict :: new ( py) . into ( ) ) ) ;
349+ }
350+ "PX" | "Init +X" | "init |+>" => {
351+ sim. px ( & collect_single_qubits ( locations) ?) ;
352+ return Ok ( Some ( PyDict :: new ( py) . into ( ) ) ) ;
353+ }
354+ "PnX" | "Init -X" | "init |->" => {
355+ sim. pnx ( & collect_single_qubits ( locations) ?) ;
356+ return Ok ( Some ( PyDict :: new ( py) . into ( ) ) ) ;
357+ }
358+ "PY" | "Init +Y" | "init |+i>" => {
359+ sim. py ( & collect_single_qubits ( locations) ?) ;
360+ return Ok ( Some ( PyDict :: new ( py) . into ( ) ) ) ;
361+ }
362+ "PnY" | "Init -Y" | "init |-i>" => {
363+ sim. pny ( & collect_single_qubits ( locations) ?) ;
364+ return Ok ( Some ( PyDict :: new ( py) . into ( ) ) ) ;
365+ }
366+
367+ // Measurements (return outcomes)
368+ "MZ" | "Measure" | "measure Z" | "Measure +Z" => {
369+ let qubits = collect_single_qubit_indices ( locations) ?;
370+ let qubit_ids: Vec < QubitId > = qubits. iter ( ) . map ( |& q| QubitId ( q) ) . collect ( ) ;
371+ let results = sim. mz ( & qubit_ids) ;
372+ return Ok ( Some ( build_meas_output ( py, & qubits, results) ?) ) ;
373+ }
374+ "MX" | "Measure +X" => {
375+ let qubits = collect_single_qubit_indices ( locations) ?;
376+ let qubit_ids: Vec < QubitId > = qubits. iter ( ) . map ( |& q| QubitId ( q) ) . collect ( ) ;
377+ let results = sim. mx ( & qubit_ids) ;
378+ return Ok ( Some ( build_meas_output ( py, & qubits, results) ?) ) ;
379+ }
380+ "MY" | "Measure +Y" => {
381+ let qubits = collect_single_qubit_indices ( locations) ?;
382+ let qubit_ids: Vec < QubitId > = qubits. iter ( ) . map ( |& q| QubitId ( q) ) . collect ( ) ;
383+ let results = sim. my ( & qubit_ids) ;
384+ return Ok ( Some ( build_meas_output ( py, & qubits, results) ?) ) ;
385+ }
386+
387+ // Two-qubit Clifford gates (no return value)
388+ "CX" | "CNOT" | "CY" | "CZ" | "SZZ" | "SZZdg" | "SXX" | "SXXdg" | "SYY"
389+ | "SYYdg" | "SqrtZZ" | "SqrtZZd" | "SqrtXX" | "SqrtXXd" | "SqrtYY" | "SqrtYYd"
390+ | "SWAP" | "G" | "G2" => {
391+ let pairs = collect_pairs ( locations) ?;
392+ match symbol {
393+ "CX" | "CNOT" => { sim. cx ( & pairs) ; }
394+ "CY" => { sim. cy ( & pairs) ; }
395+ "CZ" => { sim. cz ( & pairs) ; }
396+ "SZZ" | "SqrtZZ" => { sim. szz ( & pairs) ; }
397+ "SZZdg" | "SqrtZZd" => { sim. szzdg ( & pairs) ; }
398+ "SXX" | "SqrtXX" => { sim. sxx ( & pairs) ; }
399+ "SXXdg" | "SqrtXXd" => { sim. sxxdg ( & pairs) ; }
400+ "SYY" | "SqrtYY" => { sim. syy ( & pairs) ; }
401+ "SYYdg" | "SqrtYYd" => { sim. syydg ( & pairs) ; }
402+ "SWAP" => { sim. swap ( & pairs) ; }
403+ "G" | "G2" => { sim. g ( & pairs) ; }
404+ _ => unreachable ! ( ) ,
405+ }
406+ return Ok ( Some ( PyDict :: new ( py) . into ( ) ) ) ;
407+ }
408+
409+ _ => { }
410+ }
411+
412+ Ok ( None )
413+ }
0 commit comments