QPY Rust and Python compatibility fixes#15847
Conversation
…e Python module expects
|
One or more of the following people are relevant to this code:
|
Cryoris
left a comment
There was a problem hiding this comment.
I don't quite understand between which versions this PR fixes compatibility issues. Isn't V17 always loaded by Rust already?
| @@ -163,9 +163,8 @@ fn pack_condition( | |||
| let register_size = bytes.len() as u16; | |||
| let data = formats::ConditionData::Register(bytes); | |||
| // TODO: this may cause loss of data, but we are constrained by the current qpy format | |||
There was a problem hiding this comment.
Is this comment still valid or is it resolved with the 0-default value?
There was a problem hiding this comment.
No, it's unrelated. The potential loss of data is when the value is too large. We do have BigUint support in the module, it's simply that V17 doesn't use it at this point.
| (ParameterType::Parameter, *parameter.symbol.uuid.as_bytes()) | ||
| } | ||
| ParameterValueType::VectorElement(element) => ( | ||
| ParameterType::ParameterVector, |
There was a problem hiding this comment.
I'm a bit surprised the ParameterVector type is not needed for anything -- why did we introduce it then? 🤔 The one additional info that a parameter vector element has is the index, was it connected to that?
There was a problem hiding this comment.
It is needed in general, but in that specific point of the replay there's no real difference between it and Parameter.
| num_ctrl_qubits: 0, // standard instructions have no control qubits | ||
| ctrl_state: 0, | ||
| gate_class_name: control_flow_inst.name().to_string(), // this name is NOT a proper python class name, but we don't instantiate from the python class anymore | ||
| gate_class_name: control_flow_class_name(control_flow_inst.name())?, // need to use the Python class names for backward compatability |
There was a problem hiding this comment.
| gate_class_name: control_flow_class_name(control_flow_inst.name())?, // need to use the Python class names for backward compatability | |
| gate_class_name: control_flow_class_name(control_flow_inst.name())?, // need to use the Python class names for backward compatibility |
| num_ctrl_qubits: 0, // standard instructions have no control qubits | ||
| ctrl_state: 0, | ||
| gate_class_name: control_flow_inst.name().to_string(), // this name is NOT a proper python class name, but we don't instantiate from the python class anymore | ||
| gate_class_name: control_flow_class_name(control_flow_inst.name())?, // need to use the Python class names for backward compatability |
There was a problem hiding this comment.
Small nit: why not just pass the enum variant by reference directly? I would expect the enum match to be faster since we don't end up comparing string values 🤔
The problem is when writing V17 via Rust then reading it via Python. This path wasn't tested enough, as opposed to the other direction (writing in Python, reading in Rust). The main problem is regarding how control flows are handled; in Rust the parameters were written differently and Python didn't know how to handle them. This breaks V17 and so must be fixed. |
Summary
Fixes several incompatibilities between the Python and Rust file formats; in all cases, the Rust code was changed to conform to the Python expected standard
Details and comments
This PR handles several problems detected with the Rust QPY component, most related to problems arising when an older, Python-based version of the QPY module attempts to read files generated with the Rust module. New tests were added to cover all these cases.
ParameterVector in qpy_reply
In Python, the data type of a
ParameterVectorinside the qpy_replay object is stored as Parameter (b'p') and not ParameterVector (b'v'). Rust saved them with separate types, resulting in Python failing to load QPY files generated with Rust when the circuits contained parameter expressions involving ParameterVectors.The fix makes Rust save the data type as
Parameteras well. This should not cause problems, as parameters and parameter vectors are treated exactly the same when unpacking a parameter expression:Note that writing `b'v' here breaks the QPY17 format, meaning that version 2.4.0rc1 does not fully conform to QPY17.
BoxOp duration parameter
In Rust, the
BoxOpduration parameter was stored asGenericDatawhich is either duration or expression. This should be the implementation going forward to QPY18 and beyond, but in Python support for duration in instruction parameters was nonexistant, so a hack was used instead, whereBoxOphad two parameters, one for the numerical value and the other for the string of the duration's units - as well as "expr" when storing an expression. The current Rust code now conforms to this hack.Control flow names
Previously, Rust stored the "new style" control flow names, e.g.
if_else. This makes Python unable to correctly create those gates as it expects the actual class name. A conversion method from the new to the old style was added.SwitchCaseOp handling
The way in which
SwitchCaseOpparameters are stored in Python QPY is obsolete since the introduction of Rust based control flow ops. However, for now we still need to conform to the old method and the code was changed to reflect this. This allows us to dispose of the specialized treatment for this case inunpack_py_instructionsince now we won't read switch instructions via this path.Since the qpy write code of 2.4.0rc1 already used the new (now reverted) style that does not conform to QPY17 (although control flows are not defined in the spec), but we'll probably transition to it on QPY18, the read code now supports both approaches.
Additional bugfix
As part of the new testing, another bug was discovered in the way condition register is handled - if its value is 0, the program would raise an error. This was fixed.