Skip to content

qpy roundtrip produces different floating point rounding for parameter values in different locations #10166

@wshanks

Description

@wshanks

Environment

  • Qiskit Terra version: 0.24.0
  • Python version: 3.10
  • Operating system: Fedora Linux 38

What is happening?

At a high level, some jobs using parameterized pulse gates are failing when using parameters with many digits of precision. The error for the jobs is "unsupported instruction" for the parameterized pulse gate.

The cause of the error is that the pulse gate instruction is becoming decoupled from the circuit calibration after the circuit is submitted to be run, in particular during the qpy serialization / deserialization process.

How can we reproduce the issue?

Here is code to build a problematic circuit:

from qiskit import QuantumCircuit, pulse
from qiskit.circuit import Gate, Parameter
from qiskit.pulse.library import Gaussian


amp = Parameter("amp")

circ = QuantumCircuit(1, 1)
custom_gate = Gate("my_custom_gate", 1, [amp])
circ.append(custom_gate, [0])

with pulse.build() as my_schedule:
    pulse.play(Gaussian(duration=64, amp=amp, sigma=8), pulse.DriveChannel(0))

circ.add_calibration(custom_gate, [0], my_schedule)

circ.assign_parameters([0.3333333333333333], inplace=True)

Here is code to show the discrepancy in parameter value:

from io import BytesIO

from qiskit.qpy import dump, load


buff = BytesIO()

dump(circ, buff)
buff.seek(0)
qpy_circ = load(buff)[0]

instr_param_value = float(qpy_circ.data[0].operation.params[0])
cal_param_value = next(iter(next(iter(qpy_circ.calibrations.values()))))[1][0]
instr_param_value == cal_param_value

What should happen?

The above code should give True for the final expression, but it gives False.

Any suggestions?

The way the current system works is that a circuit instruction can have a name, a tuple of qubits it operates on, and a list of parameters (which can be floats). Then in the calibrations attribute of the circuit there is a nested dictionary structure with keys <instruction_name>:<qubit_tuple>:<parameter_tuple> mapping to schedule definitions. When the backend encounters a custom instruction, it tries to look up a schedule definition in the calibrations attribute using these keys, so there must be an exact match. Floats as dictionary keys are not great, and I could see an argument for a different system, but maybe for now we should just make the current system work.

The cause of the discrepancy is that QuantumCircuit.assign_parameters handles the circuit instruction and calibration substitutions differently. For the circuit instruction, assign_parameters binds the float value into a parameter expression:

https://github.com/Qiskit/qiskit-terra/blob/5013fe2239290414f2cfaafae13c6a9c09ddbbda/qiskit/circuit/quantumcircuit.py#L2836-L2842

as that is what assign() does:

https://github.com/Qiskit/qiskit-terra/blob/5013fe2239290414f2cfaafae13c6a9c09ddbbda/qiskit/circuit/parameterexpression.py#L79-L149

For the calibration parameter, assign_parameters calls _assign_calibration_parameters which substitutes scalar parameters with floats:

https://github.com/Qiskit/qiskit-terra/blob/5013fe2239290414f2cfaafae13c6a9c09ddbbda/qiskit/circuit/quantumcircuit.py#L2896-L2900

The reason this discrepancy matters for high precision floats is that ParameterExpression gets serialized using sympy:

https://github.com/Qiskit/qiskit-terra/blob/5013fe2239290414f2cfaafae13c6a9c09ddbbda/qiskit/qpy/binary_io/value.py#L48-L56

while the float in the calibrations key gets written directly as a float (struct.pack("!d", val)) by write_value():

https://github.com/Qiskit/qiskit-terra/blob/5013fe2239290414f2cfaafae13c6a9c09ddbbda/qiskit/qpy/binary_io/circuits.py#L727

One solution could be for the assignment of a float to a circuit instruction parameter to also directly replace it with the float as happens now with the calibration parameter. Other options could include improving the precision of the parameter so that it deserializes to something that matches the deserialized float or doing a more significant rework of how we keep track of parameterized calibrations.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingmod: pulseRelated to the Pulse modulemod: qpyRelated to QPY serializationpriority: high

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions