-
Notifications
You must be signed in to change notification settings - Fork 2.9k
QPY 2.4rc2 fixes #15900
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 2.4rc2 fixes #15900
Changes from 8 commits
c5ce8c9
fc566e2
17011cd
238e5c2
6bbdd14
d30189e
a811a4e
4ca2dae
775b496
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -137,6 +137,10 @@ impl ParameterExpression { | |||||||||||
| qpy_replay(self, &self.name_map, &mut replay); | ||||||||||||
| replay | ||||||||||||
| } | ||||||||||||
| #[inline] | ||||||||||||
| pub fn num_of_symbols(&self) -> usize { | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor style nitpick: we don't usually use "of" -
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 775b496 |
||||||||||||
| self.name_map.len() | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| // This needs to be implemented manually, because PyO3 does not provide built-in | ||||||||||||
| // conversions for the subclasses of ParameterExpression in Python. Specifically | ||||||||||||
|
|
@@ -260,7 +264,12 @@ impl ParameterExpression { | |||||||||||
| pub fn from_qpy( | ||||||||||||
| replay: &[OPReplay], | ||||||||||||
| subs_operations: Option<Vec<(usize, HashMap<Symbol, ParameterExpression>)>>, | ||||||||||||
| additional_symbols: Option<&HashSet<Symbol>>, | ||||||||||||
| ) -> Result<Self, ParameterError> { | ||||||||||||
|
Comment on lines
262
to
266
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you pull this function into the QPY crate as part of this patch, so we can apply the "no panicking" lint rule to it (and fix the subsequent failures)? I'm still very worried about the amount of panics here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know how - moving it to the QPY crate creates circular dependency since |
||||||||||||
| let mut symbols = match additional_symbols { | ||||||||||||
| None => HashSet::new(), | ||||||||||||
| Some(symbol_map) => symbol_map.clone(), | ||||||||||||
| }; | ||||||||||||
|
Comment on lines
+267
to
+270
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you're not using the
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one causes me several problems:
This made
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||
| // the stack contains the latest lhs and rhs values | ||||||||||||
| let mut stack: Vec<ParameterExpression> = Vec::new(); | ||||||||||||
| let subs_operations = subs_operations.unwrap_or_default(); | ||||||||||||
|
|
@@ -313,20 +322,34 @@ impl ParameterExpression { | |||||||||||
| }; | ||||||||||||
| stack.push(result); | ||||||||||||
| //now check whether any substitutions need to be applied at this stage | ||||||||||||
| let mut sub_operations_to_perform = Vec::new(); | ||||||||||||
| // since we go over the operations from last to first, we need to collect all the subs | ||||||||||||
| // for this stage and go over them in reverse order (from first to last, for this stage) | ||||||||||||
| while current_sub_operation > 0 && subs_operations[current_sub_operation - 1].0 == i + 1 | ||||||||||||
| { | ||||||||||||
| sub_operations_to_perform | ||||||||||||
| .push(subs_operations[current_sub_operation - 1].1.clone()); | ||||||||||||
| current_sub_operation -= 1; | ||||||||||||
| } | ||||||||||||
| for sub_operation in sub_operations_to_perform.iter().rev() { | ||||||||||||
| if let Some(exp) = stack.pop() { | ||||||||||||
| let sub_exp = exp.subs(&subs_operations[current_sub_operation - 1].1, false)?; | ||||||||||||
| let sub_exp = exp.subs(sub_operation, true)?; | ||||||||||||
| stack.push(sub_exp); | ||||||||||||
| for key in sub_operation.keys() { | ||||||||||||
| symbols.remove(key); // remove the symbols that were substituted away | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| current_sub_operation -= 1; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // once we're done, just return the last element in the stack | ||||||||||||
| Ok(stack | ||||||||||||
| let mut result = stack | ||||||||||||
| .pop() | ||||||||||||
| .expect("Invalid QPY replay encountered during deserialization: empty OPReplay.")) | ||||||||||||
| .expect("Invalid QPY replay encountered during deserialization: empty OPReplay."); | ||||||||||||
|
|
||||||||||||
| // need to account | ||||||||||||
| result.extend_symbols(symbols); | ||||||||||||
| Ok(result) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| pub fn iter_symbols(&self) -> impl Iterator<Item = &Symbol> + '_ { | ||||||||||||
|
|
@@ -695,6 +718,17 @@ impl ParameterExpression { | |||||||||||
| } | ||||||||||||
| Ok(merged) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /// Extend the symbol table with additional symbols | ||||||||||||
| pub fn extend_symbols<I>(&mut self, symbols: I) | ||||||||||||
| where | ||||||||||||
| I: IntoIterator<Item = Symbol>, | ||||||||||||
| { | ||||||||||||
| for symbol in symbols { | ||||||||||||
| let name = symbol.repr(false); | ||||||||||||
| self.name_map.entry(name).or_insert(symbol); | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this supposed to replace the name map entries that already exist? If so, this is not doing that. It will only insert the ones that are not present. If you wanted to replace the ones that exist as well you should add a call to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, we don't need to replace anything. The goal here is to add symbols which are not present in the reply but are present in the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm worried that we're layering hack on top of hack here. The blast radius of I get that the trouble is coming in because
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that this part of the code is not related to the |
||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /// A parameter expression. | ||||||||||||
|
|
@@ -1394,7 +1428,7 @@ impl PyParameterExpression { | |||||||||||
| fn __setstate__(&mut self, state: (Vec<OPReplay>, Option<ParameterValueType>)) -> PyResult<()> { | ||||||||||||
| // if there a replay, load from the replay | ||||||||||||
| if !state.0.is_empty() { | ||||||||||||
| let from_qpy = ParameterExpression::from_qpy(&state.0, None)?; | ||||||||||||
| let from_qpy = ParameterExpression::from_qpy(&state.0, None, None)?; | ||||||||||||
| self.inner = from_qpy; | ||||||||||||
| // otherwise, load from the ParameterValueType | ||||||||||||
| } else if let Some(value) = state.1 { | ||||||||||||
|
|
@@ -1871,14 +1905,20 @@ pub enum ParameterValueType { | |||||||||||
| VectorElement(PyParameterVectorElement), | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| impl From<Value> for ParameterValueType { | ||||||||||||
| fn from(value: Value) -> Self { | ||||||||||||
| match value { | ||||||||||||
| Value::Int(i) => ParameterValueType::Int(i), | ||||||||||||
| Value::Real(r) => ParameterValueType::Float(r), | ||||||||||||
| Value::Complex(c) => ParameterValueType::Complex(c), | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| impl ParameterValueType { | ||||||||||||
| fn extract_from_expr(expr: &SymbolExpr) -> Option<ParameterValueType> { | ||||||||||||
| if let Some(value) = expr.eval(true) { | ||||||||||||
| match value { | ||||||||||||
| Value::Int(i) => Some(ParameterValueType::Int(i)), | ||||||||||||
| Value::Real(r) => Some(ParameterValueType::Float(r)), | ||||||||||||
| Value::Complex(c) => Some(ParameterValueType::Complex(c)), | ||||||||||||
| } | ||||||||||||
| Some(value.into()) | ||||||||||||
| } else if let SymbolExpr::Symbol(symbol) = expr { | ||||||||||||
| match symbol.index { | ||||||||||||
| None => { | ||||||||||||
|
|
||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,11 +10,13 @@ | |
| // copyright notice, and modified files need to carry a notice indicating | ||
| // that they have been altered from the originals. | ||
| use binrw::Endian; | ||
| use hashbrown::HashSet; | ||
| use pyo3::prelude::*; | ||
| use qiskit_circuit::imports; | ||
| use qiskit_circuit::operations::Param; | ||
| use qiskit_circuit::parameter::parameter_expression::{ | ||
| OPReplay, OpCode, ParameterExpression, ParameterValueType, PyParameter, | ||
| PyParameterVectorElement, | ||
| }; | ||
| use qiskit_circuit::parameter::symbol_expr::Symbol; | ||
| use std::sync::Arc; | ||
|
|
@@ -224,10 +226,53 @@ fn pack_symbol_table_element( | |
| } | ||
| } | ||
|
|
||
| // In case the parameter expression reduces to a constant value/symbol | ||
| // we still want to pack it as an expression to save the symbol table data, | ||
| // even though the symbols are not used. So we turn the constant to an equivalent expression. | ||
| fn pack_parameter_expression_with_empty_replay( | ||
| exp: &ParameterExpression, | ||
| ) -> Result<Vec<formats::ParameterExpressionElementPack>, QpyError> { | ||
| // Try constant value first | ||
| if let Ok(value) = exp.try_to_value(false) { | ||
| let synthetic_op = OPReplay { | ||
| op: OpCode::ADD, | ||
| lhs: Some(value.into()), | ||
| rhs: Some(ParameterValueType::Int(0)), | ||
| }; | ||
| return pack_parameter_expression_element(&synthetic_op); | ||
| } | ||
|
|
||
| // Check if it's a bare parameter or parameter vector element | ||
| if let Ok(symbol) = exp.try_to_symbol_ref() { | ||
| let param_value = match symbol.index { | ||
| None => ParameterValueType::Parameter(PyParameter { | ||
| symbol: symbol.clone(), | ||
| }), | ||
| Some(_) => ParameterValueType::VectorElement(PyParameterVectorElement { | ||
| symbol: symbol.clone(), | ||
| }), | ||
| }; | ||
| let synthetic_op = OPReplay { | ||
| op: OpCode::ADD, | ||
| lhs: Some(param_value), | ||
| rhs: Some(ParameterValueType::Int(0)), | ||
| }; | ||
| return pack_parameter_expression_element(&synthetic_op); | ||
| } | ||
| Err(QpyError::InvalidParameter(format!( | ||
| "Cannot encode parameter expression {:?}", | ||
| exp | ||
| ))) | ||
| } | ||
|
|
||
| fn pack_parameter_expression_elements( | ||
| exp: &ParameterExpression, | ||
| ) -> Result<Vec<formats::ParameterExpressionElementPack>, QpyError> { | ||
| let mut result = Vec::new(); | ||
| let replay = exp.qpy_replay(); | ||
| if replay.is_empty() { | ||
| return pack_parameter_expression_with_empty_replay(exp); | ||
| } | ||
| let mut result: Vec<formats::ParameterExpressionElementPack> = Vec::new(); | ||
| for replay_obj in exp.qpy_replay().iter() { | ||
| let packed_parameter = pack_parameter_expression_element(replay_obj)?; | ||
| result.extend(packed_parameter); | ||
|
|
@@ -453,9 +498,13 @@ pub(crate) fn unpack_parameter_expression( | |
| replay.push(OPReplay { op, lhs, rhs }); | ||
| }; | ||
| } | ||
| ParameterExpression::from_qpy(&replay, Some(sub_operations)).map_err(|_| { | ||
| QpyError::ConversionError("Failure while loading parameter expression".to_string()) | ||
| }) | ||
| let additional_symbols = HashSet::from_iter(param_uuid_map.values().filter_map(|v| match v { | ||
| GenericValue::ParameterExpressionSymbol(s) => Some(s.clone()), | ||
| _ => None, | ||
| })); | ||
| ParameterExpression::from_qpy(&replay, Some(sub_operations), Some(&additional_symbols)).map_err( | ||
| |_| QpyError::ConversionError("Failure while loading parameter expression".to_string()), | ||
| ) | ||
| } | ||
|
|
||
| pub(crate) fn pack_symbol(symbol: &Symbol) -> formats::ParameterSymbolPack { | ||
|
|
@@ -568,13 +617,29 @@ pub(crate) fn unpack_parameter_vector( | |
| }) | ||
| } | ||
|
|
||
| // exp should be a symbol (for parameter/parameter vector element) | ||
| // but more than that: it should not contain other symbols in its symbol table | ||
| // since if, e.g. exp was `0*x+y` and got simplified to `y` we still need | ||
| // to store `x`, so we must treat exp as an expression | ||
| fn expression_as_single_symbol(exp: &ParameterExpression) -> Option<Symbol> { | ||
| if let Ok(symbol) = exp.try_to_symbol() { | ||
| if exp.num_of_symbols() == 1 { | ||
| Some(symbol) | ||
| } else { | ||
| None | ||
| } | ||
| } else { | ||
| None | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to change this, but if it helps in the future, the "one-liner" version of this is something like exp.try_to_symbol().ok().filter(|_| exp.num_of_symbols() == 1)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! Done in 775b496 |
||
| } | ||
|
|
||
| pub(crate) fn pack_param_expression( | ||
| exp: &ParameterExpression, | ||
| qpy_data: &QPYWriteData, | ||
| ) -> Result<formats::GenericDataPack, QpyError> { | ||
| // if the parameter expression is a single symbol, we should treat it like a parameter | ||
| // or a parameter vector, depending on whether the `vector` field exists | ||
| if let Ok(symbol) = exp.try_to_symbol() { | ||
| if let Some(symbol) = expression_as_single_symbol(exp) { | ||
| match symbol.vector { | ||
| None => pack_generic_value(&GenericValue::ParameterExpressionSymbol(symbol), qpy_data), | ||
| Some(_) => pack_generic_value( | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.