Skip to content

Commit

Permalink
Fix edge case.
Browse files Browse the repository at this point in the history
IBM backend still provide ugate calibrations in CmdDef and they are loaded in the instmap. If we update target with the instmap, these gates are accidentally registered in the target, and they may be used in the following 1q decomposition. To prevent this, update_from_instruction_schedule_map method is updated.
  • Loading branch information
nkanazawa1989 committed Mar 13, 2023
1 parent 5207b47 commit 939ad61
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 79 deletions.
2 changes: 1 addition & 1 deletion qiskit/providers/models/pulsedefaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def __init__(

for inst in cmd_def:
entry = PulseQobjDef(converter=self.converter, name=inst.name)
entry.define(inst.sequence)
entry.define(inst.sequence, user_provided=False)
self.instruction_schedule_map._add(
instruction_name=inst.name,
qubits=tuple(inst.qubits),
Expand Down
107 changes: 75 additions & 32 deletions qiskit/pulse/calibration_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@ class CalibrationEntry(metaclass=ABCMeta):
"""A metaclass of a calibration entry."""

@abstractmethod
def define(self, definition: Any):
def define(self, definition: Any, user_provided: bool):
"""Attach definition to the calibration entry.
Args:
definition: Definition of this entry.
user_provided: If this entry is defined by user.
"""
pass

Expand All @@ -64,6 +65,12 @@ def get_schedule(self, *args, **kwargs) -> Union[Schedule, ScheduleBlock]:
"""
pass

@property
@abstractmethod
def user_provided(self) -> bool:
"""Return if this entry is user defined."""
pass


class ScheduleDef(CalibrationEntry):
"""In-memory Qiskit Pulse representation.
Expand All @@ -90,6 +97,11 @@ def __init__(self, arguments: Optional[Sequence[str]] = None):

self._definition = None
self._signature = None
self._user_provided = None

@property
def user_provided(self) -> bool:
return self._user_provided

def _parse_argument(self):
"""Generate signature from program and user provided argument names."""
Expand Down Expand Up @@ -120,35 +132,48 @@ def _parse_argument(self):
)
self._signature = signature

def define(self, definition: Union[Schedule, ScheduleBlock]):
def define(
self,
definition: Union[Schedule, ScheduleBlock],
user_provided: bool = True,
):
self._definition = definition
# add metadata
if "publisher" not in definition.metadata:
definition.metadata["publisher"] = CalibrationPublisher.QISKIT
self._parse_argument()
self._user_provided = user_provided

def get_signature(self) -> inspect.Signature:
return self._signature

def get_schedule(self, *args, **kwargs) -> Union[Schedule, ScheduleBlock]:
if not args and not kwargs:
return self._definition
try:
to_bind = self.get_signature().bind_partial(*args, **kwargs)
except TypeError as ex:
raise PulseError("Assigned parameter doesn't match with schedule parameters.") from ex
value_dict = {}
for param in self._definition.parameters:
# Schedule allows partial bind. This results in parameterized Schedule.
out = self._definition
else:
try:
value_dict[param] = to_bind.arguments[param.name]
except KeyError:
pass
return self._definition.assign_parameters(value_dict, inplace=False)
to_bind = self.get_signature().bind_partial(*args, **kwargs)
except TypeError as ex:
raise PulseError(
"Assigned parameter doesn't match with schedule parameters."
) from ex
value_dict = {}
for param in self._definition.parameters:
# Schedule allows partial bind. This results in parameterized Schedule.
try:
value_dict[param] = to_bind.arguments[param.name]
except KeyError:
pass
out = self._definition.assign_parameters(value_dict, inplace=False)
if "publisher" not in out.metadata:
if self.user_provided:
out.metadata["publisher"] = CalibrationPublisher.QISKIT
else:
out.metadata["publisher"] = CalibrationPublisher.BACKEND_PROVIDER
return out

def __eq__(self, other):
# This delegates equality check to Schedule or ScheduleBlock.
return self._definition == other._definition
if hasattr(other, "_definition"):
return self._definition == other._definition
return False

def __str__(self):
out = f"Schedule {self._definition.name}"
Expand All @@ -171,10 +196,20 @@ def __init__(self):
"""Define an empty entry."""
self._definition = None
self._signature = None
self._user_provided = None

@property
def user_provided(self) -> bool:
return self._user_provided

def define(self, definition: Callable):
def define(
self,
definition: Callable,
user_provided: bool = True,
):
self._definition = definition
self._signature = inspect.signature(definition)
self._user_provided = user_provided

def get_signature(self) -> inspect.Signature:
return self._signature
Expand All @@ -186,17 +221,20 @@ def get_schedule(self, *args, **kwargs) -> Union[Schedule, ScheduleBlock]:
to_bind.apply_defaults()
except TypeError as ex:
raise PulseError("Assigned parameter doesn't match with function signature.") from ex

schedule = self._definition(**to_bind.arguments)
# add metadata
if "publisher" not in schedule.metadata:
schedule.metadata["publisher"] = CalibrationPublisher.QISKIT
return schedule
out = self._definition(**to_bind.arguments)
if "publisher" not in out.metadata:
if self.user_provided:
out.metadata["publisher"] = CalibrationPublisher.QISKIT
else:
out.metadata["publisher"] = CalibrationPublisher.BACKEND_PROVIDER
return out

def __eq__(self, other):
# We cannot evaluate function equality without parsing python AST.
# This simply compares wether they are the same object.
return self._definition is other._definition
# This simply compares weather they are the same object.
if hasattr(other, "_definition"):
return self._definition == other._definition
return False

def __str__(self):
params_str = ", ".join(self.get_signature().parameters.keys())
Expand Down Expand Up @@ -237,14 +275,17 @@ def _build_schedule(self):
for qobj_inst in self._source:
for qiskit_inst in self._converter._get_sequences(qobj_inst):
schedule.insert(qobj_inst.t0, qiskit_inst, inplace=True)
schedule.metadata["publisher"] = CalibrationPublisher.BACKEND_PROVIDER

self._definition = schedule
self._parse_argument()

def define(self, definition: List[PulseQobjInstruction]):
def define(
self,
definition: List[PulseQobjInstruction],
user_provided: bool = False,
):
# This doesn't generate signature immediately, because of lazy schedule build.
self._source = definition
self._user_provided = user_provided

def get_signature(self) -> inspect.Signature:
if self._definition is None:
Expand All @@ -261,9 +302,11 @@ def __eq__(self, other):
# If both objects are Qobj just check Qobj equality.
return self._source == other._source
if isinstance(other, ScheduleDef) and self._definition is None:
# To compare with other scheudle def, this also generates schedule object from qobj.
# To compare with other schedule def, this also generates schedule object from qobj.
self._build_schedule()
return self._definition == other._definition
if hasattr(other, "_definition"):
return self._definition == other._definition
return False

def __str__(self):
if self._definition is None:
Expand Down
6 changes: 3 additions & 3 deletions qiskit/pulse/instruction_schedule_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
CalibrationEntry,
ScheduleDef,
CallableDef,
PulseQobjDef,
# for backward compatibility
PulseQobjDef,
CalibrationPublisher,
)
from qiskit.pulse.exceptions import PulseError
Expand Down Expand Up @@ -77,7 +77,7 @@ def has_custom_gate(self) -> bool:
"""Return ``True`` if the map has user provided instruction."""
for qubit_inst in self._map.values():
for entry in qubit_inst.values():
if not isinstance(entry, PulseQobjDef):
if entry.user_provided:
return True
return False

Expand Down Expand Up @@ -264,7 +264,7 @@ def add(
"Supplied schedule must be one of the Schedule, ScheduleBlock or a "
"callable that outputs a schedule."
)
entry.define(schedule)
entry.define(schedule, user_provided=True)
self._add(instruction, qubits, entry)

def _add(
Expand Down
18 changes: 2 additions & 16 deletions qiskit/transpiler/passes/calibration/pulse_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def supported(self, node_op: CircuitInst, qubits: List) -> bool:
Returns:
Return ``True`` is calibration can be provided.
"""
return self.target.instruction_supported(operation_name=node_op.name, qargs=qubits)
return self.target.calibration_supported(node_op.name, tuple(qubits))

def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, ScheduleBlock]:
"""Gets the calibrated schedule for the given instruction and qubits.
Expand All @@ -95,18 +95,4 @@ def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule,
Raises:
TranspilerError: When node is parameterized and calibration is raw schedule object.
"""
inst_property = self.target[node_op.name][tuple(qubits)]
if not node_op.params:
return inst_property.calibration
try:
# CircuitInstruction doesn't preserve parameter name after parameter binding.
# Thus schedule cannot generate bind dictionary.
# Use CalibraionEntry to utilize inspected signature object.
calibration_entry = inst_property._calibration
return calibration_entry.get_schedule(*node_op.params)
except AttributeError as ex:
raise TranspilerError(
f"Calibraton for {node_op.name} of {qubits} is not a CalibraryEntry instance. "
f"Mapping from parameter values {node_op.params} to parameter objects "
f"in the schedule cannot be identified."
) from ex
return self.target.get_calibration(node_op.name, tuple(qubits), *node_op.params)
Loading

0 comments on commit 939ad61

Please sign in to comment.