-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
qpy roundtrip produces different floating point rounding for parameter values in different locations #10166
Comments
Thanks Will for detailed investigation. I also noticed that we don't have any QPY test requiring high-precision floats. I agree adding something like try:
bound_symbol_expr = float(bound_symbol_expr)
except TypeError:
pass to the circuit parameter binding is the minimum bugfix. This bug is critical because some jobs (such as calibration job) fail in execution with new IBM Provider. |
One thing I am not sure about is if there is any benefit to keeping the bound parameter on the circuit instruction as a ParameterExpression bound to a float value versus just replacing it with the float (what you suggest). If not, that seems like the simplest fix, I think. It might be enough to check if the number of free parameters is zero (like the code does for the calibrations key) rather than use try/except. Otherwise, one thing to note is that the parameter with a bound scalar mechanism has been working with qiskit-ibmq-provider. I did not see any recent changes with (This is expanding on the first two suggestions I had at the bottom of the issue description). |
Fwiw, a lot of this is duplicated in #9764, and while "cast fully bound It's non-trivial to remove the usage of If there's a simple catch specifically in the |
Oops, I would say that is the same issue! (I am a little surprised @nkanazawa1989 didn't point me to that one since he pointed out this context to me 🙂 ). It's a little depressing that it has been known for six months now without a solution (still makes me wonder if something else changed that made it start happening more frequently). Since it's the same issue, I guess we should close this in favor of #9764? The argument against closing it is that we have some discussion of possible fixes here and all the discussion there is about one path involving symengine that seems to have been abandoned (the reproducer code here is a bit simpler too). While data not roundtripping unmodified with qpy is an issue, the immediate problem here is that parameter assignment gets handled differently for the instruction parameters and the calibrations parameter keys. If the values were modified but modified in the same way, that would at least avoid the most pressing problem. Another option that could be considered is leaving the calibrations parameter keys as assigned parameters as they are in the instruction parameters rather than replacing them with floats. I don't see an immediate problem with that...we could see if any tests fail. |
I don't really remember the context, but at the time I think whatever/whoever was triggering the bug just found a work-around, so it wasn't high priority and neither Naoki nor I had any great ideas for fixing it, so it just got forgotten. That's a good point about there being a discrepancy - embarrassingly, despite having looked into the issue, I hadn't even thought about it. We could definitely try changing it, but I suspect that calibration lookups will then explode because of #9299. It's not clear to me how we might go about fixing that (though again, it's really something we should). The relevant failure in this case is something like from qiskit.circuit import ParameterExpression
a = ParameterExpression({}, "0.125")
b = ParameterExpression({}, "1 / 8.")
assert a == b # success
assert hash(a) == hash(b) # failure
assert a in {b} # failure Another fixing strategy that also seems unpalatable is a breaking API change to Will, would you be able to drive finding a backport-able solution (if at all possible) and fixing the bug? I know that it's high priority, but I don't have bandwidth to take it on. |
Oh, my example about #9299 is actually mistaken here - I didn't construct the |
Yes, changing the key in the calibrations dict would mean that you could not look up a calibration with a bare float any more since that would give a different hash. You would need to do something like Separate from the immediate fix, I wonder if we want to add a
Yes, I can keep trying, but I think we have to come up with something that you don't rule out 😛 Options that are somewhat viable:
Less desirable options:
|
Regarding the old provider versus the new provider: the old provider runs these circuits without crashing, and returns results (probably incorrect results because of this issue's bug - I haven't checked). Whereas the new provider crashes. This explains why the complaints began or became more frequent with the new provider. Code that demonstrates it: from qiskit import QuantumCircuit, pulse, transpile, IBMQ
from qiskit.circuit import Gate, Parameter
from qiskit.pulse.library import Gaussian
from qiskit_ibm_provider import IBMProvider
new_provider = IBMProvider()
new_backend = new_provider.get_backend("backend name")
IBMQ.load_account()
old_provider = IBMQ.get_provider(
hub="hub name", group="group name", project="project name"
)
old_backend = old_provider.backend.same_backend_name
for backend, old_or_new in zip([old_backend, new_backend], ["old, new"]):
amp = Parameter("amp")
circ = QuantumCircuit(1, 1)
custom_gate = Gate("my_custom_gate", 1, [amp])
circ.append(custom_gate, [0])
circ.measure(0, 0)
with pulse.build(backend) as my_schedule:
pulse.play(Gaussian(duration=64, amp=amp, sigma=8), pulse.drive_channel(0))
circ.add_calibration(custom_gate, [0], my_schedule)
circ.assign_parameters([0.3333333333333333], inplace=True)
circ = transpile(circ, backend)
res = backend.run(circ).result()
print(old_or_new, " provider:", res.get_counts(0), "\n") Note that many backends don't work with the old provider anymore. In the case, the |
I don't expect the results to be incorrect with the old provider because it serializes directly to json without going through qpy. In that case, the instruction parameter and the calibrations dict key for the parameter should both be cast to strings in the same way. |
Environment
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:
Here is code to show the discrepancy in parameter value:
What should happen?
The above code should give
True
for the final expression, but it givesFalse
.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 thecalibrations
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)
) bywrite_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.
The text was updated successfully, but these errors were encountered: