Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix add_calibration bug (backport #9223) #9451

Merged
merged 1 commit into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -4618,10 +4618,33 @@ def add_calibration(
Raises:
Exception: if the gate is of type string and params is None.
"""

def _format(operand):
try:
# Using float/complex value as a dict key is not good idea.
# This makes the mapping quite sensitive to the rounding error.
# However, the mechanism is already tied to the execution model (i.e. pulse gate)
# and we cannot easily update this rule.
# The same logic exists in DAGCircuit.add_calibration.
evaluated = complex(operand)
if np.isreal(evaluated):
evaluated = float(evaluated.real)
if evaluated.is_integer():
evaluated = int(evaluated)
return evaluated
except TypeError:
# Unassigned parameter
return operand

if isinstance(gate, Gate):
self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule
params = gate.params
gate = gate.name
if params is not None:
params = tuple(map(_format, params))
else:
self._calibrations[gate][(tuple(qubits), tuple(params or []))] = schedule
params = tuple()

self._calibrations[gate][(tuple(qubits), params)] = schedule

# Functions only for scheduled circuits
def qubit_duration(self, *qubits: Union[Qubit, int]) -> float:
Expand Down
29 changes: 25 additions & 4 deletions qiskit/dagcircuit/dagcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,33 @@ def add_calibration(self, gate, qubits, schedule, params=None):
Raises:
Exception: if the gate is of type string and params is None.
"""

def _format(operand):
try:
# Using float/complex value as a dict key is not good idea.
# This makes the mapping quite sensitive to the rounding error.
# However, the mechanism is already tied to the execution model (i.e. pulse gate)
# and we cannot easily update this rule.
# The same logic exists in QuantumCircuit.add_calibration.
evaluated = complex(operand)
if np.isreal(evaluated):
evaluated = float(evaluated.real)
if evaluated.is_integer():
evaluated = int(evaluated)
return evaluated
except TypeError:
# Unassigned parameter
return operand

if isinstance(gate, Gate):
self._calibrations[gate.name][
(tuple(qubits), tuple(float(p) for p in gate.params))
] = schedule
params = gate.params
gate = gate.name
if params is not None:
params = tuple(map(_format, params))
else:
self._calibrations[gate][(tuple(qubits), tuple(params or []))] = schedule
params = tuple()

self._calibrations[gate][(tuple(qubits), params)] = schedule

def has_calibration_for(self, node):
"""Return True if the dag has a calibration defined for the node operation. In this
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
fixes:
- |
:meth:`.QuantumCircuit.add_calibrations` and :meth:`.DAGCircuit.add_calibrations`
method have been updated to resolve the mismatch of parameter formatting logic.
So far one of DAGCircuit tried to typecast every parameter into float,
while QuantumCircuit used given parameters as-is.
This has been crashing transpile when the pulse gate to assign
was kept parameterized throughout the all transpile steps.
Both methods now have the identical logic to format the gate parameters.
22 changes: 22 additions & 0 deletions test/python/transpiler/test_pulse_gate_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,28 @@ def test_transpile_with_custom_gate(self):
}
self.assertDictEqual(transpiled_qc.calibrations, ref_calibration)

def test_transpile_with_parameterized_custom_gate(self):
"""Test providing non-basis gate, which is kept parameterized throughout transpile."""
backend = FakeAthens()
backend.defaults().instruction_schedule_map.add(
"my_gate", (0,), self.my_gate_q0, arguments=["P0"]
)

param = circuit.Parameter("new_P0")
qc = circuit.QuantumCircuit(1)
qc.append(circuit.Gate("my_gate", 1, [param]), [0])

transpiled_qc = transpile(qc, backend, basis_gates=["my_gate"], initial_layout=[0])

my_gate_q0_p = self.my_gate_q0.assign_parameters({self.sched_param: param}, inplace=False)

ref_calibration = {
"my_gate": {
((0,), (param,)): my_gate_q0_p,
}
}
self.assertDictEqual(transpiled_qc.calibrations, ref_calibration)

def test_transpile_with_multiple_circuits(self):
"""Test transpile with multiple circuits with custom gate."""
backend = FakeAthens()
Expand Down