-
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 all 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 |
|---|---|---|
|
|
@@ -57,6 +57,8 @@ pub enum ParameterError { | |
| NotASymbol, | ||
| #[error("Derivative not supported on expression: {0}")] | ||
| DerivativeNotSupported(String), | ||
| #[error("QPY replay parsing error: {0}")] | ||
| QpyReplayParsingError(String), | ||
| } | ||
|
|
||
| impl From<ParameterError> for PyErr { | ||
|
|
@@ -260,7 +262,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> { | ||
| let mut symbols = match additional_symbols { | ||
| None => HashSet::new(), | ||
| Some(symbol_map) => symbol_map.clone(), | ||
| }; | ||
| // the stack contains the latest lhs and rhs values | ||
| let mut stack: Vec<ParameterExpression> = Vec::new(); | ||
| let subs_operations = subs_operations.unwrap_or_default(); | ||
|
|
@@ -278,24 +285,50 @@ impl ParameterExpression { | |
|
|
||
| // if we need two operands, pop rhs from the stack | ||
| let rhs = if BINARY_OPS.contains(op) { | ||
| Some(stack.pop().expect("Pop from empty stack")) | ||
| Some(stack.pop().ok_or(ParameterError::QpyReplayParsingError( | ||
| "Tried to pop RHS value from empty stack".to_string(), | ||
| ))?) | ||
| } else { | ||
| None | ||
| }; | ||
|
|
||
| // pop lhs from the stack, this we always need | ||
| let lhs = stack.pop().expect("Pop from empty stack"); | ||
| let lhs = stack.pop().ok_or(ParameterError::QpyReplayParsingError( | ||
| "Tried to pop LHS value from empty stack".to_string(), | ||
| ))?; | ||
|
|
||
| // apply the operation and put the result onto the stack for the next replay | ||
| let result: ParameterExpression = match op { | ||
| OpCode::ADD => lhs.add(&rhs.unwrap())?, | ||
| OpCode::MUL => lhs.mul(&rhs.unwrap())?, | ||
| OpCode::SUB => lhs.sub(&rhs.unwrap())?, | ||
| OpCode::RSUB => rhs.unwrap().sub(&lhs)?, | ||
| OpCode::POW => lhs.pow(&rhs.unwrap())?, | ||
| OpCode::RPOW => rhs.unwrap().pow(&lhs)?, | ||
| OpCode::DIV => lhs.div(&rhs.unwrap())?, | ||
| OpCode::RDIV => rhs.unwrap().div(&lhs)?, | ||
| OpCode::ADD => lhs.add(&rhs.ok_or(ParameterError::QpyReplayParsingError( | ||
| "Missing RHS value".to_string(), | ||
| ))?)?, | ||
| OpCode::MUL => lhs.mul(&rhs.ok_or(ParameterError::QpyReplayParsingError( | ||
| "Missing RHS value".to_string(), | ||
| ))?)?, | ||
| OpCode::SUB => lhs.sub(&rhs.ok_or(ParameterError::QpyReplayParsingError( | ||
| "Missing RHS value".to_string(), | ||
| ))?)?, | ||
| OpCode::RSUB => rhs | ||
| .ok_or(ParameterError::QpyReplayParsingError( | ||
| "Missing RHS value".to_string(), | ||
| ))? | ||
| .sub(&lhs)?, | ||
| OpCode::POW => lhs.pow(&rhs.ok_or(ParameterError::QpyReplayParsingError( | ||
| "Missing RHS value".to_string(), | ||
| ))?)?, | ||
| OpCode::RPOW => rhs | ||
| .ok_or(ParameterError::QpyReplayParsingError( | ||
| "Missing RHS value".to_string(), | ||
| ))? | ||
| .pow(&lhs)?, | ||
| OpCode::DIV => lhs.div(&rhs.ok_or(ParameterError::QpyReplayParsingError( | ||
| "Missing RHS value".to_string(), | ||
| ))?)?, | ||
| OpCode::RDIV => rhs | ||
| .ok_or(ParameterError::QpyReplayParsingError( | ||
| "Missing RHS value".to_string(), | ||
| ))? | ||
| .div(&lhs)?, | ||
| OpCode::ABS => lhs.abs(), | ||
| OpCode::SIN => lhs.sin(), | ||
| OpCode::ASIN => lhs.asin(), | ||
|
|
@@ -308,25 +341,41 @@ impl ParameterExpression { | |
| OpCode::EXP => lhs.exp(), | ||
| OpCode::SIGN => lhs.sign(), | ||
| OpCode::GRAD | OpCode::SUBSTITUTE => { | ||
| panic!("GRAD and SUBSTITUTE are not supported.") | ||
| return Err(ParameterError::QpyReplayParsingError( | ||
| "GRAD and SUBSTITUTE are not supported.".to_string(), | ||
| )); | ||
| } | ||
| }; | ||
| 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.")) | ||
| .ok_or(ParameterError::QpyReplayParsingError("Invalid QPY replay encountered during deserialization: empty OPReplay at the end of parsing.".to_string()))?; | ||
|
|
||
| // need to account | ||
| result.extend_symbols(symbols); | ||
| Ok(result) | ||
| } | ||
|
|
||
| pub fn iter_symbols(&self) -> impl Iterator<Item = &Symbol> + '_ { | ||
|
|
@@ -695,6 +744,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 +1454,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 +1931,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 => { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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
ParameterExpression::__setstate__requiresfrom_qpy. However, I tried removing the panics from the function nevertheless in 775b496.