Skip to content

Commit

Permalink
Fix add_calibration bug (#9223)
Browse files Browse the repository at this point in the history
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
nkanazawa1989 and mergify[bot] authored Jan 25, 2023
1 parent f19ae5d commit b7ab0dc
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 6 deletions.
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 @@ -180,6 +180,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

0 comments on commit b7ab0dc

Please sign in to comment.