Skip to content

Commit 569f5df

Browse files
mergify[bot]gadialeliarbeljakelishman
authored
QPY Rust and Python compatibility fixes (#15847) (#15887)
* Change the encoding of qpy_replay ParameterVector elements to what the Python module expects * Add test * Fix another two bugs found by the extended testing * BoxOp parameter handling * Rust writer now writes SwitchCaseOp's params in the python style * Reading of python style SwitchCaseOp in Rust * Make for_loop tuple params be saved as little endian * Fix BoxOp handling of Nulll duration * Reconcile new and old reading of SwitchCaseOp * Make control_flow_class_name use the enum, not the string * Bugfix in parameter vector lookup for QPY<15 * Raise an error when condition value is too large * Fixes according to PR review * Bugfix * Apply suggestions from code review * Fixes according to PR review * Ensure all combinations of read/write run --------- (cherry picked from commit 63ccd7d) Co-authored-by: gadial <gadial@gmail.com> Co-authored-by: Eli Arbel <46826214+eliarbel@users.noreply.github.com> Co-authored-by: Jake Lishman <jake.lishman@ibm.com>
1 parent d3d05bb commit 569f5df

File tree

8 files changed

+331
-148
lines changed

8 files changed

+331
-148
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/qpy/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ qiskit-circuit.workspace = true
2121
qiskit-quantum-info.workspace = true
2222
num-bigint.workspace = true
2323
num-complex.workspace = true
24+
num-traits.workspace = true
2425
bytemuck.workspace = true
2526
binrw.workspace = true
2627
uuid = {version = "1", features = ["v4"]}

crates/qpy/src/circuit_reader.rs

Lines changed: 76 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,12 @@ fn recognize_instruction_type(
187187
} else if ControlFlowType::from_str(name).is_ok()
188188
|| matches!(
189189
name,
190-
// We don't handle old style SwitchCaseOp in rust yet
191-
"IfElseOp" | "WhileLoopOp" | "ForLoopOp" | "BreakLoopOp" | "ContinueLoopOp"
190+
"IfElseOp"
191+
| "WhileLoopOp"
192+
| "ForLoopOp"
193+
| "BreakLoopOp"
194+
| "ContinueLoopOp"
195+
| "SwitchCaseOp"
192196
)
193197
{
194198
InstructionType::ControlFlow
@@ -597,24 +601,52 @@ fn unpack_control_flow(
597601
}
598602
ControlFlowType::SwitchCase => {
599603
let mut instruction_values = get_instruction_values(instruction, qpy_data)?;
600-
if instruction_values.len() < 3 {
601-
return Err(QpyError::MissingData(format!(
602-
"Switch case instruction has {} parameters, expected at least 3 (target, label_spec, cases)",
603-
instruction_values.len()
604-
)));
605-
}
606-
param_values = instruction_values.split_off(3);
607-
let mut iter = instruction_values.into_iter();
608-
let ((target_value, label_spec_value), cases_value) = iter
609-
.next()
610-
.zip(iter.next())
611-
.zip(iter.next())
612-
.ok_or_else(|| {
613-
QpyError::MissingData(
614-
"Switch case instruction missing some of its parameters".to_string(),
615-
)
616-
})?;
617-
let target = match target_value {
604+
let (target_value, case_label_list) = if instruction_values.len() < 3 {
605+
// we follow the python way of storing switch params
606+
// the first param is the target, the next param is the cases specifier
607+
// the cases specifier is a list of pairs (tuples)
608+
// the second element in each pair is the subcircuit for this case
609+
// the first element is the list of the case labels, or a single case label
610+
// or the special default case label
611+
let [target_value, cases, ..] = &instruction_values[..] else {
612+
return Err(QpyError::MissingData(
613+
"Switch case requires at least 2 parameters".to_string(),
614+
));
615+
};
616+
let mut case_label_list = Vec::new();
617+
for case in cases.as_slice().ok_or(QpyError::InvalidInstruction(
618+
"bad parameters for switch statement".to_string(),
619+
))? {
620+
let [case_labels, case_circuit, ..] =
621+
&case.as_slice().ok_or(QpyError::InvalidInstruction(
622+
"bad parameters for switch statement".to_string(),
623+
))?
624+
else {
625+
return Err(QpyError::InvalidInstruction(
626+
"bad parameters for switch statement".to_string(),
627+
));
628+
};
629+
param_values.push(case_circuit.clone());
630+
case_label_list.push(case_labels.clone());
631+
}
632+
(target_value, case_label_list)
633+
} else {
634+
param_values = instruction_values.split_off(3);
635+
let [target_value, label_spec_value, ..] = &instruction_values[..] else {
636+
return Err(QpyError::MissingData(
637+
"Switch case requires at least 3 parameters".to_string(),
638+
));
639+
};
640+
(
641+
target_value,
642+
label_spec_value.to_vec().ok_or_else(|| {
643+
QpyError::InvalidInstruction(
644+
"bad parameters for switch statement".to_string(),
645+
)
646+
})?,
647+
)
648+
};
649+
let target = match target_value.clone() {
618650
GenericValue::Expression(exp) => Ok(SwitchTarget::Expr(exp)),
619651
GenericValue::Register(ParamRegisterValue::Register(reg)) => {
620652
Ok(SwitchTarget::Register(reg))
@@ -627,45 +659,34 @@ fn unpack_control_flow(
627659
)),
628660
}?;
629661

630-
let GenericValue::Tuple(label_spec_tuple_tuple) = label_spec_value else {
631-
return Err(QpyError::InvalidInstruction(
632-
"could not identify switch case label spec".to_string(),
633-
));
634-
};
635-
let label_spec = label_spec_tuple_tuple
636-
.iter()
637-
.map(|label_spec_tuple_tuple_element| -> Result<_, QpyError> {
638-
let GenericValue::Tuple(label_spec_tuple) = label_spec_tuple_tuple_element
639-
else {
640-
return Err(QpyError::InvalidInstruction(
662+
// now split the zipped cases: move the circuits to params, keep the labels for further processing
663+
let mut label_spec = Vec::new();
664+
for case_labels in case_label_list {
665+
// label spec handling
666+
let GenericValue::Tuple(label_spec_element_tuple) = case_labels else {
667+
return Err(QpyError::InvalidInstruction(
668+
"could not identify switch case label spec".to_string(),
669+
));
670+
};
671+
let label_spec_element = label_spec_element_tuple
672+
.iter()
673+
.map(|label_spec_element| match label_spec_element.as_le() {
674+
GenericValue::CaseDefault => Ok(CaseSpecifier::Default),
675+
GenericValue::BigInt(value) => Ok(CaseSpecifier::Uint(value.clone())),
676+
GenericValue::Int64(value) => {
677+
Ok(CaseSpecifier::Uint(BigUint::from(value as u64)))
678+
}
679+
_ => Err(QpyError::InvalidInstruction(
641680
"could not identify switch case label spec".to_string(),
642-
));
643-
};
644-
label_spec_tuple
645-
.iter()
646-
.map(|label_spec_element| match label_spec_element {
647-
GenericValue::CaseDefault => Ok(CaseSpecifier::Default),
648-
GenericValue::BigInt(value) => Ok(CaseSpecifier::Uint(value.clone())),
649-
GenericValue::Int64(value) => {
650-
Ok(CaseSpecifier::Uint(BigUint::from(*value as u64)))
651-
}
652-
_ => Err(QpyError::InvalidInstruction(
653-
"could not identify switch case label spec".to_string(),
654-
)),
655-
})
656-
.collect::<Result<_, QpyError>>()
657-
})
658-
.collect::<Result<_, QpyError>>()?;
659-
let cases = match cases_value {
660-
GenericValue::Int64(value) => Ok(value as u32),
661-
_ => Err(QpyError::InvalidInstruction(
662-
"could not identify switch cases".to_string(),
663-
)),
664-
}?;
681+
)),
682+
})
683+
.collect::<Result<_, QpyError>>()?;
684+
label_spec.push(label_spec_element);
685+
}
665686
ControlFlow::Switch {
666687
target,
667688
label_spec,
668-
cases,
689+
cases: param_values.len() as u32,
669690
}
670691
}
671692
};
@@ -812,24 +833,6 @@ fn unpack_py_instruction(
812833
// we used the params to construct the loop; they should not be retained as params except the subcircuit
813834
instruction_values.retain(|value| matches!(value, GenericValue::Circuit(_)));
814835
}
815-
if name.as_str() == "SwitchCaseOp" {
816-
// switch cases are as the second component of the second parameter
817-
// we keep only the circuits and remove everything else from the params
818-
if let GenericValue::Tuple(cases) = &instruction_values[1] {
819-
instruction_values = cases
820-
.iter()
821-
.map(|case| -> Result<_, QpyError> {
822-
if let GenericValue::Tuple(case_elements) = case {
823-
Ok(case_elements[1].clone())
824-
} else {
825-
Err(QpyError::InvalidInstruction(
826-
"Unable to read switch case op".to_string(),
827-
))
828-
}
829-
})
830-
.collect::<Result<_, QpyError>>()?;
831-
}
832-
}
833836
gate_class.call1(args)?
834837
}
835838
};

0 commit comments

Comments
 (0)