81
81
from pytket .unit_id import _TEMP_BIT_NAME
82
82
from pytket .pauli import Pauli , QubitPauliString
83
83
from pytket .architecture import Architecture , FullyConnected
84
- from pytket .utils import QubitPauliOperator , gen_term_sequence_circuit
84
+ from pytket .utils import (
85
+ QubitPauliOperator ,
86
+ gen_term_sequence_circuit ,
87
+ permute_rows_cols_in_unitary ,
88
+ )
85
89
from pytket .passes import AutoRebase
86
90
87
91
if TYPE_CHECKING :
@@ -290,9 +294,26 @@ def _string_to_circuit(
290
294
return circ
291
295
292
296
297
+ def _get_pytket_ctrl_state (bitstring : str , n_bits : int ) -> tuple [bool , ...]:
298
+ "Converts a little endian string '001'=1 (LE) to (1, 0, 0)."
299
+ assert set (bitstring ).issubset ({"0" , "1" })
300
+ padded_bitstring = bitstring .zfill (n_bits )
301
+ pytket_ctrl_state = reversed ([bool (int (b )) for b in padded_bitstring ])
302
+ return tuple (pytket_ctrl_state )
303
+
304
+
305
+ def _all_bits_set (integer : int , n_bits : int ) -> bool :
306
+ return integer .bit_count () == n_bits
307
+
308
+
293
309
def _get_controlled_tket_optype (c_gate : ControlledGate ) -> OpType :
294
310
"""Get a pytket contolled OpType from a qiskit ControlledGate."""
295
- if c_gate .base_class in _known_qiskit_gate :
311
+
312
+ # If the control state is not "all |1>", use QControlBox
313
+ if not _all_bits_set (c_gate .ctrl_state , c_gate .num_ctrl_qubits ):
314
+ return OpType .QControlBox
315
+
316
+ elif c_gate .base_class in _known_qiskit_gate :
296
317
# First we check if the gate is in _known_qiskit_gate
297
318
# this avoids CZ being converted to CnZ
298
319
return _known_qiskit_gate [c_gate .base_class ]
@@ -334,6 +355,49 @@ def _optype_from_qiskit_instruction(instruction: Instruction) -> OpType:
334
355
)
335
356
336
357
358
+ UnitaryBox = Unitary1qBox | Unitary2qBox | Unitary3qBox
359
+
360
+
361
+ def _get_unitary_box (unitary : NDArray [np .complex128 ], num_qubits : int ) -> UnitaryBox :
362
+ match num_qubits :
363
+ case 1 :
364
+ assert unitary .shape == (2 , 2 )
365
+ return Unitary1qBox (unitary )
366
+ case 2 :
367
+ assert unitary .shape == (4 , 4 )
368
+ return Unitary2qBox (unitary )
369
+ case 3 :
370
+ assert unitary .shape == (8 , 8 )
371
+ return Unitary3qBox (unitary )
372
+ case _:
373
+ raise NotImplementedError (
374
+ f"Conversion of { num_qubits } -qubit unitary gates not supported."
375
+ )
376
+
377
+
378
+ def _get_qcontrol_box (c_gate : ControlledGate , params : list [float ]) -> QControlBox :
379
+ qiskit_ctrl_state : str = bin (c_gate .ctrl_state )[2 :]
380
+ pytket_ctrl_state : tuple [bool , ...] = _get_pytket_ctrl_state (
381
+ bitstring = qiskit_ctrl_state , n_bits = c_gate .num_ctrl_qubits
382
+ )
383
+ if isinstance (c_gate .base_gate , UnitaryGate ):
384
+ unitary = c_gate .base_gate .params [0 ]
385
+ # Here we reverse the order of the columns to correct for endianness.
386
+ new_unitary : NDArray [np .complex128 ] = permute_rows_cols_in_unitary (
387
+ matrix = unitary ,
388
+ permutation = tuple (reversed (range (c_gate .base_gate .num_qubits ))),
389
+ )
390
+ base_op : Op = _get_unitary_box (new_unitary , c_gate .base_gate .num_qubits )
391
+ else :
392
+ base_tket_gate : OpType = _known_qiskit_gate [c_gate .base_gate .base_class ]
393
+
394
+ base_op : Op = Op .create (base_tket_gate , params ) # type: ignore
395
+
396
+ return QControlBox (
397
+ base_op , n_controls = c_gate .num_ctrl_qubits , control_state = pytket_ctrl_state
398
+ )
399
+
400
+
337
401
def _add_state_preparation (
338
402
tkc : Circuit , qubits : list [Qubit ], prep : Initialize | StatePreparation
339
403
) -> None :
@@ -432,21 +496,6 @@ def __init__(
432
496
def circuit (self ) -> Circuit :
433
497
return self .tkc
434
498
435
- def add_xs (
436
- self ,
437
- num_ctrl_qubits : Optional [int ],
438
- ctrl_state : Optional [str | int ],
439
- qargs : list ["Qubit" ],
440
- ) -> None :
441
- if ctrl_state is not None :
442
- assert isinstance (num_ctrl_qubits , int )
443
- assert num_ctrl_qubits >= 0
444
- c = int (ctrl_state , 2 ) if isinstance (ctrl_state , str ) else int (ctrl_state )
445
- assert c >= 0 and (c >> num_ctrl_qubits ) == 0
446
- for i in range (num_ctrl_qubits ):
447
- if ((c >> i ) & 1 ) == 0 :
448
- self .tkc .X (self .qbmap [qargs [i ]])
449
-
450
499
def add_qiskit_data (
451
500
self , circuit : QuantumCircuit , data : Optional ["QuantumCircuitData" ] = None
452
501
) -> None :
@@ -465,42 +514,14 @@ def add_qiskit_data(
465
514
circuit = circuit ,
466
515
)
467
516
468
- # Controlled operations may be controlled on values other than all-1. Handle
469
- # this by prepending and appending X gates on the control qubits.
470
- ctrl_state , num_ctrl_qubits = None , None
471
- try :
472
- ctrl_state = instr .ctrl_state
473
- num_ctrl_qubits = instr .num_ctrl_qubits
474
- except AttributeError :
475
- pass
476
- self .add_xs (num_ctrl_qubits , ctrl_state , qargs )
477
-
478
517
optype = None
479
518
if type (instr ) not in (PauliEvolutionGate , UnitaryGate ):
480
519
# Handling of PauliEvolutionGate and UnitaryGate below
481
520
optype = _optype_from_qiskit_instruction (instruction = instr )
482
521
483
522
if optype == OpType .QControlBox :
484
523
params = [param_to_tk (p ) for p in instr .base_gate .params ]
485
- n_base_qubits = instr .base_gate .num_qubits
486
- sub_circ = Circuit (n_base_qubits )
487
- # use base gate name for the CircBox (shows in renderer)
488
- sub_circ .name = instr .base_gate .name .capitalize ()
489
-
490
- if type (instr .base_gate ) is UnitaryGate :
491
- assert len (cargs ) == 0
492
- add_qiskit_unitary_to_tkc (
493
- sub_circ , instr .base_gate , sub_circ .qubits , condition_kwargs
494
- )
495
- else :
496
- base_tket_gate : OpType = _known_qiskit_gate [
497
- instr .base_gate .base_class
498
- ]
499
- sub_circ .add_gate (
500
- base_tket_gate , params , list (range (n_base_qubits ))
501
- )
502
- c_box = CircBox (sub_circ )
503
- q_ctrl_box = QControlBox (c_box , instr .num_ctrl_qubits )
524
+ q_ctrl_box = _get_qcontrol_box (c_gate = instr , params = params )
504
525
self .tkc .add_qcontrolbox (q_ctrl_box , qubits )
505
526
506
527
elif isinstance (instr , (Initialize , StatePreparation )):
@@ -515,8 +536,20 @@ def add_qiskit_data(
515
536
self .tkc .add_circbox (ccbox , qubits )
516
537
517
538
elif type (instr ) is UnitaryGate :
518
- assert len (cargs ) == 0
519
- add_qiskit_unitary_to_tkc (self .tkc , instr , qubits , condition_kwargs )
539
+ unitary = cast (NDArray [np .complex128 ], instr .params [0 ])
540
+ if len (qubits ) == 0 :
541
+ # If the UnitaryGate acts on no qubits, we add a phase.
542
+ self .tkc .add_phase (np .angle (unitary [0 ][0 ]) / np .pi )
543
+ else :
544
+ unitary_box = _get_unitary_box (
545
+ unitary = unitary , num_qubits = instr .num_qubits
546
+ )
547
+ self .tkc .add_gate (
548
+ unitary_box ,
549
+ list (reversed (qubits )),
550
+ ** condition_kwargs ,
551
+ )
552
+
520
553
elif optype == OpType .Barrier :
521
554
self .tkc .add_barrier (qubits )
522
555
elif optype == OpType .CircBox :
@@ -550,42 +583,6 @@ def add_qiskit_data(
550
583
params = [param_to_tk (p ) for p in instr .params ]
551
584
self .tkc .add_gate (optype , params , qubits + bits , ** condition_kwargs ) # type: ignore
552
585
553
- self .add_xs (num_ctrl_qubits , ctrl_state , qargs )
554
-
555
-
556
- def add_qiskit_unitary_to_tkc (
557
- tkc : Circuit ,
558
- u_gate : UnitaryGate ,
559
- qubits : list [Qubit ],
560
- condition_kwargs : dict [str , Any ],
561
- ) -> None :
562
- # Note reversal of qubits, to account for endianness (pytket unitaries
563
- # are ILO-BE == DLO-LE; qiskit unitaries are ILO-LE == DLO-BE).
564
- params = u_gate .params
565
- assert len (params ) == 1
566
- u = cast (np .ndarray , params [0 ])
567
-
568
- n = len (qubits )
569
- if n == 0 :
570
- assert u .shape == (1 , 1 )
571
- tkc .add_phase (np .angle (u [0 ][0 ]) / np .pi )
572
- elif n == 1 :
573
- assert u .shape == (2 , 2 )
574
- u1box = Unitary1qBox (u )
575
- tkc .add_unitary1qbox (u1box , qubits [0 ], ** condition_kwargs )
576
- elif n == 2 :
577
- assert u .shape == (4 , 4 )
578
- u2box = Unitary2qBox (u )
579
- tkc .add_unitary2qbox (u2box , qubits [1 ], qubits [0 ], ** condition_kwargs )
580
- elif n == 3 :
581
- assert u .shape == (8 , 8 )
582
- u3box = Unitary3qBox (u )
583
- tkc .add_unitary3qbox (u3box , qubits [2 ], qubits [1 ], qubits [0 ], ** condition_kwargs )
584
- else :
585
- raise NotImplementedError (
586
- f"Conversion of { n } -qubit unitary gates not supported."
587
- )
588
-
589
586
590
587
def qiskit_to_tk (qcirc : QuantumCircuit , preserve_param_uuid : bool = False ) -> Circuit :
591
588
"""
@@ -616,6 +613,10 @@ def qiskit_to_tk(qcirc: QuantumCircuit, preserve_param_uuid: bool = False) -> Ci
616
613
return builder .circuit ()
617
614
618
615
616
+ def _get_qiskit_control_state (bool_list : list [bool ]) -> str :
617
+ return "" .join (str (int (b )) for b in bool_list )[::- 1 ]
618
+
619
+
619
620
def param_to_tk (p : float | ParameterExpression ) -> sympy .Expr :
620
621
if isinstance (p , ParameterExpression ):
621
622
symexpr = p ._symbol_expr
@@ -698,6 +699,27 @@ def append_tk_command_to_qiskit(
698
699
qiskit_state_prep_box = StatePreparation (statevector_array )
699
700
return qcirc .append (qiskit_state_prep_box , qargs = list (reversed (qargs )))
700
701
702
+ if optype == OpType .QControlBox :
703
+ assert isinstance (op , QControlBox )
704
+ qargs = [qregmap [q .reg_name ][q .index [0 ]] for q in args ]
705
+ pytket_control_state : list [bool ] = op .get_control_state_bits ()
706
+ qiskit_control_state : str = _get_qiskit_control_state (pytket_control_state )
707
+ try :
708
+ gatetype , phase = _known_gate_rev_phase [op .get_op ().type ]
709
+ except KeyError :
710
+ raise NotImplementedError (
711
+ "Conversion of QControlBox with base gate"
712
+ + f"{ op .get_op ()} not supported by tk_to_qiskit."
713
+ )
714
+ params = _get_params (op .get_op (), symb_map )
715
+ operation = gatetype (* params )
716
+ return qcirc .append (
717
+ operation .control (
718
+ num_ctrl_qubits = op .get_n_controls (), ctrl_state = qiskit_control_state
719
+ ),
720
+ qargs = qargs ,
721
+ )
722
+
701
723
if optype == OpType .Barrier :
702
724
if any (q .type == UnitType .bit for q in args ):
703
725
raise NotImplementedError (
@@ -818,7 +840,12 @@ def append_tk_command_to_qiskit(
818
840
_protected_tket_gates = (
819
841
_supported_tket_gates
820
842
| _additional_multi_controlled_gates
821
- | {OpType .Unitary1qBox , OpType .Unitary2qBox , OpType .Unitary3qBox }
843
+ | {
844
+ OpType .Unitary1qBox ,
845
+ OpType .Unitary2qBox ,
846
+ OpType .Unitary3qBox ,
847
+ OpType .QControlBox ,
848
+ }
822
849
| {OpType .CustomGate }
823
850
)
824
851
0 commit comments