22
22
import datetime
23
23
import io
24
24
import logging
25
+ import inspect
25
26
26
27
import retworkx as rx
27
28
28
29
from qiskit .circuit .parameter import Parameter
29
30
from qiskit .pulse .instruction_schedule_map import InstructionScheduleMap
30
31
from qiskit .transpiler .coupling import CouplingMap
32
+ from qiskit .transpiler .exceptions import TranspilerError
31
33
from qiskit .transpiler .instruction_durations import InstructionDurations
32
34
from qiskit .transpiler .timing_constraints import TimingConstraints
33
35
@@ -295,7 +297,11 @@ def add_instruction(self, instruction, properties=None, name=None):
295
297
296
298
Args:
297
299
instruction (qiskit.circuit.Instruction): The operation object to add to the map. If it's
298
- paramerterized any value of the parameter can be set
300
+ paramerterized any value of the parameter can be set. Optionally for variable width
301
+ instructions (such as control flow operations such as :class:`~.ForLoop` or
302
+ :class:`~MCXGate`) you can specify the class. If the class is specified than the
303
+ ``name`` argument must be specified. When a class is used the gate is treated as global
304
+ and not having any properties set.
299
305
properties (dict): A dictionary of qarg entries to an
300
306
:class:`~qiskit.transpiler.InstructionProperties` object for that
301
307
instruction implementation on the backend. Properties are optional
@@ -319,19 +325,37 @@ def add_instruction(self, instruction, properties=None, name=None):
319
325
documentation for the :class:`~qiskit.transpiler.Target` class).
320
326
Raises:
321
327
AttributeError: If gate is already in map
328
+ TranspilerError: If an operation class is passed in for ``instruction`` and no name
329
+ is specified or ``properties`` is set.
322
330
"""
331
+ is_class = inspect .isclass (instruction )
332
+ if not is_class :
333
+ instruction_name = name or instruction .name
334
+ else :
335
+ # Invalid to have class input without a name with characters set "" is not a valid name
336
+ if not name :
337
+ raise TranspilerError (
338
+ "A name must be specified when defining a supported global operation by class"
339
+ )
340
+ if properties is not None :
341
+ raise TranspilerError (
342
+ "An instruction added globally by class can't have properties set."
343
+ )
344
+ instruction_name = name
323
345
if properties is None :
324
346
properties = {None : None }
325
- instruction_name = name or instruction .name
326
347
if instruction_name in self ._gate_map :
327
348
raise AttributeError ("Instruction %s is already in the target" % instruction_name )
328
349
self ._gate_name_map [instruction_name ] = instruction
329
- qargs_val = {}
330
- for qarg in properties :
331
- if qarg is not None :
332
- self .num_qubits = max (self .num_qubits , max (qarg ) + 1 )
333
- qargs_val [qarg ] = properties [qarg ]
334
- self ._qarg_gate_map [qarg ].add (instruction_name )
350
+ if is_class :
351
+ qargs_val = {None : None }
352
+ else :
353
+ qargs_val = {}
354
+ for qarg in properties :
355
+ if qarg is not None :
356
+ self .num_qubits = max (self .num_qubits , max (qarg ) + 1 )
357
+ qargs_val [qarg ] = properties [qarg ]
358
+ self ._qarg_gate_map [qarg ].add (instruction_name )
335
359
self ._gate_map [instruction_name ] = qargs_val
336
360
self ._coupling_graph = None
337
361
self ._instruction_durations = None
@@ -494,7 +518,9 @@ def operation_from_name(self, instruction):
494
518
instruction (str): The instruction name to get the
495
519
:class:`~qiskit.circuit.Instruction` instance for
496
520
Returns:
497
- qiskit.circuit.Instruction: The Instruction instance corresponding to the name
521
+ qiskit.circuit.Instruction: The Instruction instance corresponding to the
522
+ name. This also can also be the class for globally defined variable with
523
+ operations.
498
524
"""
499
525
return self ._gate_name_map [instruction ]
500
526
@@ -507,14 +533,18 @@ def operations_for_qargs(self, qargs):
507
533
instructions that apply to qubit 0.
508
534
Returns:
509
535
list: The list of :class:`~qiskit.circuit.Instruction` instances
510
- that apply to the specified qarg.
536
+ that apply to the specified qarg. This may also be a class if
537
+ a variable width operation is globally defined.
511
538
512
539
Raises:
513
540
KeyError: If qargs is not in target
514
541
"""
515
542
if qargs not in self ._qarg_gate_map :
516
543
raise KeyError (f"{ qargs } not in target." )
517
- return [self ._gate_name_map [x ] for x in self ._qarg_gate_map [qargs ]]
544
+ res = [self ._gate_name_map [x ] for x in self ._qarg_gate_map [qargs ]]
545
+ if None in self ._qarg_gate_map :
546
+ res += [self ._gate_name_map [x ] for x in self ._qarg_gate_map [None ]]
547
+ return res
518
548
519
549
def operation_names_for_qargs (self , qargs ):
520
550
"""Get the operation names for a specified qargs tuple
@@ -609,6 +639,20 @@ def check_obj_params(parameters, obj):
609
639
qargs = tuple (qargs )
610
640
if operation_class is not None :
611
641
for op_name , obj in self ._gate_name_map .items ():
642
+ if inspect .isclass (obj ):
643
+ if obj != operation_class :
644
+ continue
645
+ # If no qargs a operation class is supported
646
+ if qargs is None :
647
+ return True
648
+ # If qargs set then validate no duplicates and all indices are valid on device
649
+ elif all (qarg <= self .num_qubits for qarg in qargs ) and len (set (qargs )) == len (
650
+ qargs
651
+ ):
652
+ return True
653
+ else :
654
+ return False
655
+
612
656
if isinstance (obj , operation_class ):
613
657
if parameters is not None :
614
658
if len (parameters ) != len (obj .params ):
@@ -627,6 +671,23 @@ def check_obj_params(parameters, obj):
627
671
if operation_name in self ._gate_map :
628
672
if parameters is not None :
629
673
obj = self ._gate_name_map [operation_name ]
674
+ if inspect .isclass (obj ):
675
+ # The parameters argument was set and the operation_name specified is
676
+ # defined as a globally supported class in the target. This means
677
+ # there is no available validation (including whether the specified
678
+ # operation supports parameters), the returned value will not factor
679
+ # in the argument `parameters`,
680
+
681
+ # If no qargs a operation class is supported
682
+ if qargs is None :
683
+ return True
684
+ # If qargs set then validate no duplicates and all indices are valid on device
685
+ elif all (qarg <= self .num_qubits for qarg in qargs ) and len (set (qargs )) == len (
686
+ qargs
687
+ ):
688
+ return True
689
+ else :
690
+ return False
630
691
if len (parameters ) != len (obj .params ):
631
692
return False
632
693
for index , param in enumerate (parameters ):
@@ -643,9 +704,21 @@ def check_obj_params(parameters, obj):
643
704
if qargs in self ._gate_map [operation_name ]:
644
705
return True
645
706
if self ._gate_map [operation_name ] is None or None in self ._gate_map [operation_name ]:
646
- return self ._gate_name_map [operation_name ].num_qubits == len (qargs ) and all (
647
- x < self .num_qubits for x in qargs
648
- )
707
+ obj = self ._gate_name_map [operation_name ]
708
+ if inspect .isclass (obj ):
709
+ if qargs is None :
710
+ return True
711
+ # If qargs set then validate no duplicates and all indices are valid on device
712
+ elif all (qarg <= self .num_qubits for qarg in qargs ) and len (set (qargs )) == len (
713
+ qargs
714
+ ):
715
+ return True
716
+ else :
717
+ return False
718
+ else :
719
+ return self ._gate_name_map [operation_name ].num_qubits == len (qargs ) and all (
720
+ x < self .num_qubits for x in qargs
721
+ )
649
722
return False
650
723
651
724
@property
@@ -661,7 +734,12 @@ def operations(self):
661
734
@property
662
735
def instructions (self ):
663
736
"""Get the list of tuples ``(:class:`~qiskit.circuit.Instruction`, (qargs))``
664
- for the target"""
737
+ for the target
738
+
739
+ For globally defined variable width operations the tuple will be of the form
740
+ ``(class, None)`` where class is the actual operation class that
741
+ is globally defined.
742
+ """
665
743
return [
666
744
(self ._gate_name_map [op ], qarg ) for op in self ._gate_map for qarg in self ._gate_map [op ]
667
745
]
@@ -711,6 +789,8 @@ def _build_coupling_graph(self):
711
789
self ._coupling_graph .add_nodes_from ([{} for _ in range (self .num_qubits )])
712
790
for gate , qarg_map in self ._gate_map .items ():
713
791
for qarg , properties in qarg_map .items ():
792
+ if qarg is None :
793
+ continue
714
794
if len (qarg ) == 1 :
715
795
self ._coupling_graph [qarg [0 ]] = properties
716
796
elif len (qarg ) == 2 :
@@ -719,6 +799,8 @@ def _build_coupling_graph(self):
719
799
edge_data [gate ] = properties
720
800
except rx .NoEdgeBetweenNodes :
721
801
self ._coupling_graph .add_edge (* qarg , {gate : properties })
802
+ if self ._coupling_graph .num_edges () == 0 and any (x is None for x in self ._qarg_gate_map ):
803
+ self ._coupling_graph = None
722
804
723
805
def build_coupling_map (self , two_q_gate = None ):
724
806
"""Get a :class:`~qiskit.transpiler.CouplingMap` from this target.
@@ -761,9 +843,14 @@ def build_coupling_map(self, two_q_gate=None):
761
843
762
844
if self ._coupling_graph is None :
763
845
self ._build_coupling_graph ()
764
- cmap = CouplingMap ()
765
- cmap .graph = self ._coupling_graph
766
- return cmap
846
+ # if there is no connectivity constraints in the coupling graph treat it as not
847
+ # existing and return
848
+ if self ._coupling_graph is not None :
849
+ cmap = CouplingMap ()
850
+ cmap .graph = self ._coupling_graph
851
+ return cmap
852
+ else :
853
+ return None
767
854
768
855
@property
769
856
def physical_qubits (self ):
0 commit comments