Skip to content

Commit d8713ff

Browse files
committed
Handle more complex situations
1 parent 93db998 commit d8713ff

3 files changed

Lines changed: 106 additions & 10 deletions

File tree

crates/circuit/src/parameter/parameter_expression.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ impl ParameterExpression {
137137
qpy_replay(self, &self.name_map, &mut replay);
138138
replay
139139
}
140+
141+
pub fn num_of_symbols(&self) -> usize {
142+
self.name_map.len()
143+
}
140144
}
141145
// This needs to be implemented manually, because PyO3 does not provide built-in
142146
// conversions for the subclasses of ParameterExpression in Python. Specifically
@@ -695,6 +699,17 @@ impl ParameterExpression {
695699
}
696700
Ok(merged)
697701
}
702+
703+
/// Extend the symbol table with additional symbols
704+
pub fn extend_symbols<I>(&mut self, symbols: I)
705+
where
706+
I: IntoIterator<Item = Symbol>,
707+
{
708+
for symbol in symbols {
709+
let name = symbol.repr(false);
710+
self.name_map.entry(name).or_insert(symbol);
711+
}
712+
}
698713
}
699714

700715
/// A parameter expression.
@@ -1871,14 +1886,20 @@ pub enum ParameterValueType {
18711886
VectorElement(PyParameterVectorElement),
18721887
}
18731888

1889+
impl From<Value> for ParameterValueType {
1890+
fn from(value: Value) -> Self {
1891+
match value {
1892+
Value::Int(i) => ParameterValueType::Int(i),
1893+
Value::Real(r) => ParameterValueType::Float(r),
1894+
Value::Complex(c) => ParameterValueType::Complex(c),
1895+
}
1896+
}
1897+
}
1898+
18741899
impl ParameterValueType {
18751900
fn extract_from_expr(expr: &SymbolExpr) -> Option<ParameterValueType> {
18761901
if let Some(value) = expr.eval(true) {
1877-
match value {
1878-
Value::Int(i) => Some(ParameterValueType::Int(i)),
1879-
Value::Real(r) => Some(ParameterValueType::Float(r)),
1880-
Value::Complex(c) => Some(ParameterValueType::Complex(c)),
1881-
}
1902+
Some(value.into())
18821903
} else if let SymbolExpr::Symbol(symbol) = expr {
18831904
match symbol.index {
18841905
None => {

crates/qpy/src/params.rs

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use qiskit_circuit::imports;
1515
use qiskit_circuit::operations::Param;
1616
use qiskit_circuit::parameter::parameter_expression::{
1717
OPReplay, OpCode, ParameterExpression, ParameterValueType, PyParameter,
18+
PyParameterVectorElement,
1819
};
1920
use qiskit_circuit::parameter::symbol_expr::{Symbol, SymbolExpr, Value};
2021
use std::sync::Arc;
@@ -224,10 +225,61 @@ fn pack_symbol_table_element(
224225
}
225226
}
226227

228+
// In case the parameter expression reduces to a constant value/symbol
229+
// we still want to pack it as an expression to save the symbol table data,
230+
// even though the symbols are not used. So we turn the constant to an equivalent expression.
231+
fn pack_parameter_expression_with_empty_replay(
232+
exp: &ParameterExpression,
233+
) -> Result<Vec<formats::ParameterExpressionElementPack>, QpyError> {
234+
// Try constant value first
235+
if let Ok(value) = exp.try_to_value(false) {
236+
let synthetic_op = OPReplay {
237+
op: OpCode::ADD,
238+
lhs: Some(value.into()),
239+
rhs: Some(ParameterValueType::Int(0)),
240+
};
241+
return pack_parameter_expression_element(&synthetic_op);
242+
}
243+
244+
// Check if it's a bare parameter or parameter vector element
245+
if let Ok(symbol) = exp.try_to_symbol_ref() {
246+
let param_value = match symbol.index {
247+
None => ParameterValueType::Parameter(PyParameter {
248+
symbol: symbol.clone(),
249+
}),
250+
Some(_) => ParameterValueType::VectorElement(PyParameterVectorElement {
251+
symbol: symbol.clone(),
252+
}),
253+
};
254+
let synthetic_op = OPReplay {
255+
op: OpCode::ADD,
256+
lhs: Some(param_value),
257+
rhs: Some(ParameterValueType::Int(0)),
258+
};
259+
return pack_parameter_expression_element(&synthetic_op);
260+
}
261+
Err(QpyError::InvalidParameter(format!(
262+
"Cannot encode parameter expression {:?}",
263+
exp
264+
)))
265+
}
266+
267+
// fn pack_parameter_expression_with_empty_replay(exp: &ParameterExpression) -> Result<Vec<formats::ParameterExpressionElementPack>, QpyError> {
268+
// if let Ok(value) = exp.try_to_value(false) {
269+
// return Ok(pack_parameter_expression_element(&constant_value_op_replay(value))?;);
270+
// } else {
271+
// return Err(QpyError::InvalidParameter(format!("Cannot encode parameter expression {:?}", exp)));
272+
// }
273+
// }
274+
227275
fn pack_parameter_expression_elements(
228276
exp: &ParameterExpression,
229277
) -> Result<Vec<formats::ParameterExpressionElementPack>, QpyError> {
230-
let mut result = Vec::new();
278+
let replay = exp.qpy_replay();
279+
if replay.is_empty() {
280+
return pack_parameter_expression_with_empty_replay(exp);
281+
}
282+
let mut result: Vec<formats::ParameterExpressionElementPack> = Vec::new();
231283
for replay_obj in exp.qpy_replay().iter() {
232284
let packed_parameter = pack_parameter_expression_element(replay_obj)?;
233285
result.extend(packed_parameter);
@@ -470,9 +522,15 @@ pub(crate) fn unpack_parameter_expression(
470522
replay.push(OPReplay { op, lhs, rhs });
471523
};
472524
}
473-
ParameterExpression::from_qpy(&replay, Some(sub_operations)).map_err(|_| {
525+
let mut exp = ParameterExpression::from_qpy(&replay, Some(sub_operations)).map_err(|_| {
474526
QpyError::ConversionError("Failure while loading parameter expression".to_string())
475-
})
527+
})?;
528+
// add parameters not present in the replay but part of the original expression (in case they were optimized away)
529+
exp.extend_symbols(param_uuid_map.values().filter_map(|v| match v {
530+
GenericValue::ParameterExpressionSymbol(s) => Some(s.clone()),
531+
_ => None,
532+
}));
533+
Ok(exp)
476534
}
477535

478536
pub(crate) fn pack_symbol(symbol: &Symbol) -> formats::ParameterSymbolPack {
@@ -585,13 +643,29 @@ pub(crate) fn unpack_parameter_vector(
585643
})
586644
}
587645

646+
// exp should be a symbol (for parameter/parameter vector element)
647+
// but more than that: it should no contain other symbols in its symbol table
648+
// since if, e.g. exp was `0*x+y` and got simplified to `y` we still need
649+
// to store `x`, so we must treat exp as an expression
650+
fn expression_as_single_symbol(exp: &ParameterExpression) -> Option<Symbol> {
651+
if let Ok(symbol) = exp.try_to_symbol() {
652+
if exp.num_of_symbols() == 1 {
653+
Some(symbol)
654+
} else {
655+
None
656+
}
657+
} else {
658+
None
659+
}
660+
}
661+
588662
pub(crate) fn pack_param_expression(
589663
exp: &ParameterExpression,
590664
qpy_data: &QPYWriteData,
591665
) -> Result<formats::GenericDataPack, QpyError> {
592666
// if the parameter expression is a single symbol, we should treat it like a parameter
593667
// or a parameter vector, depending on whether the `vector` field exists
594-
if let Ok(symbol) = exp.try_to_symbol() {
668+
if let Some(symbol) = expression_as_single_symbol(exp) {
595669
match symbol.vector {
596670
None => pack_generic_value(&GenericValue::ParameterExpressionSymbol(symbol), qpy_data),
597671
Some(_) => pack_generic_value(

test/python/circuit/test_circuit_load_from_qpy.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,8 @@ def test_degenerate_parameter_expression(self):
369369
"""Test a circuit with a parameter expression that simplifies to 0."""
370370
x = Parameter("x")
371371
y_vec = ParameterVector("y", 2)
372-
cases = [0 * x, x - x, 0 * y_vec[0], 0 * (x + y_vec[1])]
372+
z = Parameter("z")
373+
cases = [0 * x, 0 * x + 2, 0 * x + z, x - x, 0 * y_vec[0], 0 * (x + y_vec[1])]
373374
for case in cases:
374375
qc = QuantumCircuit(1)
375376
qc.rz(case, 0)

0 commit comments

Comments
 (0)