1
- from functools import reduce
1
+ from functools import cache , reduce
2
2
3
3
import numpy as np
4
4
from scipy import sparse
@@ -329,7 +329,9 @@ def ECR(symplectic_matrix, control_q, target_q, nqubits):
329
329
return X (symplectic_matrix , control_q , nqubits )
330
330
331
331
332
- def _exponent (x1 , z1 , x2 , z2 ):
332
+ def _exponent (
333
+ x1 : np .ndarray , z1 : np .ndarray , x2 : np .ndarray , z2 : np .ndarray
334
+ ) -> np .ndarray :
333
335
"""Helper function that computes the exponent to which i is raised for the product of the x and z paulis encoded in the symplectic matrix. This is used in _rowsum. The computation is performed parallely over the separated paulis x1[i], z1[i], x2[i] and z2[i].
334
336
335
337
Args:
@@ -341,13 +343,7 @@ def _exponent(x1, z1, x2, z2):
341
343
Returns:
342
344
(np.array): The calculated exponents.
343
345
"""
344
- x1_ = x1 .astype (np .int8 )
345
- x2_ = x2 .astype (np .int8 )
346
- z1_ = z1 .astype (np .int8 )
347
- z2_ = z2 .astype (np .int8 )
348
- return (
349
- 2 * (x1_ * x2_ * (z2_ - z1_ ) + z1_ * z2_ * (x1_ - x2_ )) - x1_ * z2_ + x2_ * z1_
350
- )
346
+ return 2 * (x1 * x2 * (z2 - z1 ) + z1 * z2 * (x1 - x2 )) - x1 * z2 + x2 * z1
351
347
352
348
353
349
def _rowsum (symplectic_matrix , h , i , nqubits , determined = False ):
@@ -362,16 +358,16 @@ def _rowsum(symplectic_matrix, h, i, nqubits, determined=False):
362
358
Returns:
363
359
(np.array): The updated symplectic matrix.
364
360
"""
365
- xi , xh = symplectic_matrix [i , :nqubits ], symplectic_matrix [h , : nqubits ]
366
- zi , zh = symplectic_matrix [i , nqubits : - 1 ], symplectic_matrix [h , nqubits :- 1 ]
361
+ xi , zi = symplectic_matrix [i , :nqubits ], symplectic_matrix [i , nqubits : - 1 ]
362
+ xh , zh = symplectic_matrix [h , : nqubits ], symplectic_matrix [h , nqubits :- 1 ]
367
363
exponents = _exponent (xi , zi , xh , zh )
368
364
ind = (
369
365
2 * symplectic_matrix [h , - 1 ]
370
366
+ 2 * symplectic_matrix [i , - 1 ]
371
367
+ np .sum (exponents , axis = - 1 )
372
368
) % 4 == 0
373
- r = np .ones (h .shape [0 ], dtype = bool )
374
- r [ind ] = False
369
+ r = np .ones (h .shape [0 ], dtype = np . uint8 )
370
+ r [ind ] = 0
375
371
376
372
xi_xh = xi ^ xh
377
373
zi_zh = zi ^ zh
@@ -390,57 +386,108 @@ def _rowsum(symplectic_matrix, h, i, nqubits, determined=False):
390
386
391
387
392
388
def _determined_outcome (state , q , nqubits ):
393
- state [- 1 , :] = False
389
+ state [- 1 , :] = 0
394
390
idx = (state [:nqubits , q ].nonzero ()[0 ] + nqubits ).astype (np .uint )
391
+ state = _pack_for_measurements (state , nqubits )
395
392
state = _rowsum (
396
393
state ,
397
394
2 * nqubits * np .ones (idx .shape , dtype = np .uint ),
398
- idx . astype ( np . uint ) ,
399
- nqubits ,
395
+ idx ,
396
+ _packed_size ( nqubits ) ,
400
397
True ,
401
398
)
402
- return state , np .uint (state [- 1 , - 1 ])
399
+ state = _unpack_for_measurements (state , nqubits )
400
+ return state , state [- 1 , - 1 ]
403
401
404
402
405
403
def _random_outcome (state , p , q , nqubits ):
406
404
p = p [0 ] + nqubits
407
405
tmp = state [p , q ].copy ()
408
- state [p , q ] = False
406
+ state [p , q ] = 0
409
407
h = state [:- 1 , q ].nonzero ()[0 ]
410
408
state [p , q ] = tmp
411
409
if h .shape [0 ] > 0 :
410
+ state = _pack_for_measurements (state , nqubits )
412
411
state = _rowsum (
413
412
state ,
414
413
h .astype (np .uint ),
415
414
p * np .ones (h .shape [0 ], dtype = np .uint ),
416
- nqubits ,
415
+ _packed_size ( nqubits ) ,
417
416
False ,
418
417
)
418
+ state = _unpack_for_measurements (state , nqubits )
419
419
state [p - nqubits , :] = state [p , :]
420
420
outcome = np .random .randint (2 , size = 1 ).item ()
421
- state [p , :] = False
421
+ state [p , :] = 0
422
422
state [p , - 1 ] = outcome
423
- state [p , nqubits + q ] = True
423
+ state [p , nqubits + q ] = 1
424
424
return state , outcome
425
425
426
426
427
- def _get_p (state , q , nqubits ):
428
- return state [nqubits :- 1 , q ].nonzero ()[0 ]
427
+ @cache
428
+ def _dim (nqubits ):
429
+ """Returns the dimension of the symplectic matrix for a given number of qubits."""
430
+ return 2 * nqubits + 1
431
+
432
+
433
+ @cache
434
+ def _packed_size (n ):
435
+ """Returns the size of an array of `n` booleans after packing."""
436
+ return np .ceil (n / 8 ).astype (int )
437
+
438
+
439
+ def _packbits (array , axis ):
440
+ return np .packbits (array , axis = axis )
441
+
442
+
443
+ def _unpackbits (array , axis ):
444
+ return np .unpackbits (array , axis = axis )
445
+
446
+
447
+ def _pack_for_measurements (state , nqubits ):
448
+ """Prepares the state for measurements by packing the rows of the X and Z sections of the symplectic matrix."""
449
+ r , x , z = _get_rxz (state , nqubits )
450
+ x = _packbits (x , axis = 1 )
451
+ z = _packbits (z , axis = 1 )
452
+ return np .hstack ((x , z , r [:, None ]))
453
+
454
+
455
+ @cache
456
+ def _pad_size (n ):
457
+ """Returns the size of the pad added to an array of original dimension `n` after unpacking."""
458
+ return 8 - (n % 8 )
459
+
460
+
461
+ def _unpack_for_measurements (state , nqubits ):
462
+ """Unpacks the symplectc matrix that was packed for measurements."""
463
+ xz = _unpackbits (state [:, :- 1 ], axis = 1 )
464
+ padding_size = _pad_size (nqubits )
465
+ x , z = xz [:, :nqubits ], xz [:, nqubits + padding_size : - padding_size ]
466
+ return np .hstack ((x , z , state [:, - 1 ][:, None ]))
467
+
468
+
469
+ def _init_state_for_measurements (state , nqubits , collapse ):
470
+ if collapse :
471
+ return _unpackbits (state , axis = 0 )[: _dim (nqubits )]
472
+ else :
473
+ return state .copy ()
429
474
430
475
431
476
# valid for a standard basis measurement only
432
477
def M (state , qubits , nqubits , collapse = False ):
433
478
sample = []
434
- state_copy = state if collapse else state . copy ( )
479
+ state = _init_state_for_measurements ( state , nqubits , collapse )
435
480
for q in qubits :
436
- p = _get_p ( state_copy , q , nqubits )
481
+ p = state [ nqubits : - 1 , q ]. nonzero ()[ 0 ]
437
482
# random outcome, affects the state
438
483
if len (p ) > 0 :
439
- state_copy , outcome = _random_outcome (state_copy , p , q , nqubits )
484
+ state , outcome = _random_outcome (state , p , q , nqubits )
440
485
# determined outcome, state unchanged
441
486
else :
442
- state_copy , outcome = _determined_outcome (state_copy , q , nqubits )
487
+ state , outcome = _determined_outcome (state , q , nqubits )
443
488
sample .append (outcome )
489
+ if collapse :
490
+ state = _packbits (state , axis = 0 )
444
491
return sample
445
492
446
493
@@ -455,10 +502,28 @@ def cast(x, dtype=None, copy=False):
455
502
456
503
457
504
def _clifford_pre_execution_reshape (state ):
458
- return state
505
+ """Reshape and packing applied to the symplectic matrix before execution to prepare the state in the form needed by each engine.
506
+
507
+ Args:
508
+ state (np.array): Input state.
509
+
510
+ Returns:
511
+ (np.array) The packed and reshaped state.
512
+ """
513
+ return _packbits (state , axis = 0 )
459
514
460
515
461
- def _clifford_post_execution_reshape (state , nqubits ):
516
+ def _clifford_post_execution_reshape (state , nqubits : int ):
517
+ """Reshape and unpacking applied to the state after execution to retrieve the standard symplectic matrix form.
518
+
519
+ Args:
520
+ state (np.array): Input state.
521
+ nqubits (int): Number of qubits.
522
+
523
+ Returns:
524
+ (np.array) The unpacked and reshaped state.
525
+ """
526
+ state = _unpackbits (state , axis = 0 )[: _dim (nqubits )]
462
527
return state
463
528
464
529
0 commit comments