• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Qiskit / qiskit / 24896352538

24 Apr 2026 03:01PM UTC coverage: 87.679% (+0.07%) from 87.61%
24896352538

push

github

web-flow
Fix QPY loading delay integers durations incorrectly (#16076) (#16083)

* Fix QPY loading delay integers durations incorrectly

This commit fixes an issue with parsing QPY payloads which have circuits
that contain delays with duration of units dt. Durations of dt are
integers and that is preserved in the QPY data. However our rust data
model doesn't have support for an integer in a Rust PackedInstruction's
parameters (this is an inconsistency which we should arguably fix but
that is separate from this bugfix). To workaround this the QPY reader in
rust was sticking the integer into a ParameterExpression as a constant
without any symbols. This resulted in storing the integer in Rust but
treating the object as a ParameterExpression and not an int, which in
Qiskit's rust data model is mapped to a Param::Obj (indicating a Python
object parameter). This mismatch in types was not really noticeable to
Python because the ParameterExpression with the constant integer was
coerced to an integer when it's passed to Python. However, this would
break underlying assumptions for Rust code that is interacting with the
delay. For example, the experimental rust qasm3 exporter would encounter
the ParameterExpression on the delay and error because it can't handle
parameter expressions yet. However fundamentally it could because this
is just an integer. The reproducer for this failure is:

```python
import io
from qiskit import qpy, qasm3, QuantumCircuit

qc = QuantumCircuit(1)
qc.delay(1, 0)

qasm3.dumps_experimental(qc)

with io.BytesIO() as fptr:
    qpy.dump(qc, fptr)
    fptr.seek(0)
    qc2 = qpy.load(fptr)[0]

qasm3.dumps_experimental(qc2)
```

The OQ3 experimental exporter is wrong not to handle
`ParameterExpression::try_as_value` returning an `int`, but it's _more_
wrong that QPY is producing a `ParameterExpression` on deserialisation
in the first place.

To fix this issue this commit removes the conversion of the `int` in the
qpy payload into a ParameterExpression... (continued)

59 of 65 new or added lines in 3 files covered. (90.77%)

27 existing lines in 4 files now uncovered.

104206 of 118849 relevant lines covered (87.68%)

1012643.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.53
/crates/circuit/src/parameter/parameter_expression.rs
1
// This code is part of Qiskit.
2
//
3
// (C) Copyright IBM 2023, 2024
4
//
5
// This code is licensed under the Apache License, Version 2.0. You may
6
// obtain a copy of this license in the LICENSE.txt file in the root directory
7
// of this source tree or at https://www.apache.org/licenses/LICENSE-2.0.
8
//
9
// Any modifications or derivative works of this code must retain this
10
// copyright notice, and modified files need to carry a notice indicating
11
// that they have been altered from the originals.
12

13
use hashbrown::hash_map::Entry;
14
use hashbrown::{HashMap, HashSet};
15
use indexmap::IndexSet;
16
use num_complex::Complex64;
17
use pyo3::exceptions::{PyRuntimeError, PyTypeError, PyValueError, PyZeroDivisionError};
18
use pyo3::types::{IntoPyDict, PyComplex, PyFloat, PyInt, PyNotImplemented, PySet, PyString};
19
use std::sync::Arc;
20
use thiserror::Error;
21
use uuid::Uuid;
22

23
use std::collections::hash_map::DefaultHasher;
24
use std::fmt;
25
use std::hash::{Hash, Hasher};
26

27
use pyo3::IntoPyObjectExt;
28
use pyo3::prelude::*;
29

30
use crate::circuit_data::CircuitError;
31
use crate::imports::{BUILTIN_HASH, SYMPIFY_PARAMETER_EXPRESSION, UUID};
32
use crate::parameter::symbol_expr;
33
use crate::parameter::symbol_expr::SymbolExpr;
34
use crate::parameter::symbol_parser::parse_expression;
35

36
use super::symbol_expr::{SYMEXPR_EPSILON, Symbol, Value};
37

38
/// Errors for dealing with parameters and parameter expressions.
39
#[derive(Error, Debug)]
40
pub enum ParameterError {
41
    #[error("Division by zero.")]
42
    ZeroDivisionError,
43
    #[error("Binding to infinite value.")]
44
    BindingInf,
45
    #[error("Binding to NaN.")]
46
    BindingNaN,
47
    #[error("Invalid value: NaN or infinite.")]
48
    InvalidValue,
49
    #[error("Cannot bind following parameters not present in expression: {0:?}")]
50
    UnknownParameters(HashSet<Symbol>),
51
    #[error("Parameter expression with unbound parameters {0:?} is not numeric.")]
52
    UnboundParameters(HashSet<Symbol>),
53
    #[error("Name conflict adding parameters.")]
54
    NameConflict,
55
    #[error("Invalid cast to OpCode: {0}")]
56
    InvalidU8ToOpCode(u8),
57
    #[error("Could not cast to Symbol.")]
58
    NotASymbol,
59
    #[error("Derivative not supported on expression: {0}")]
60
    DerivativeNotSupported(String),
61
}
62

63
impl From<ParameterError> for PyErr {
64
    fn from(value: ParameterError) -> Self {
7,524✔
65
        match value {
7,524✔
66
            ParameterError::ZeroDivisionError => {
67
                PyZeroDivisionError::new_err("zero division occurs while binding parameter")
1,012✔
68
            }
69
            ParameterError::BindingInf => {
70
                PyZeroDivisionError::new_err("attempted to bind infinite value to parameter")
1,828✔
71
            }
72
            ParameterError::UnknownParameters(_) | ParameterError::NameConflict => {
73
                CircuitError::new_err(value.to_string())
12✔
74
            }
75
            ParameterError::UnboundParameters(_) => PyTypeError::new_err(value.to_string()),
4,628✔
76
            ParameterError::InvalidValue => PyValueError::new_err(value.to_string()),
40✔
77
            _ => PyRuntimeError::new_err(value.to_string()),
4✔
78
        }
79
    }
7,524✔
80
}
81

82
/// A parameter expression.
83
///
84
/// This is backed by Qiskit's symbolic expression engine and a cache
85
/// for the parameters inside the expression.
86
#[derive(Clone, Debug)]
87
pub struct ParameterExpression {
88
    // The symbolic expression.
89
    expr: SymbolExpr,
90
    // A map keeping track of all symbols, with their name. This map *must* have
91
    // exactly one entry per symbol used in the expression (no more, no less).
92
    name_map: HashMap<String, Symbol>,
93
}
94

95
impl Hash for ParameterExpression {
96
    fn hash<H: Hasher>(&self, state: &mut H) {
×
97
        // The string representation of a tree is unique.
98
        self.expr.string_id().hash(state);
×
99
    }
×
100
}
101

102
impl PartialEq for ParameterExpression {
103
    fn eq(&self, other: &Self) -> bool {
1,732✔
104
        self.expr.eq(&other.expr)
1,732✔
105
    }
1,732✔
106
}
107

108
impl Eq for ParameterExpression {}
109

110
impl Default for ParameterExpression {
111
    /// The default constructor returns zero.
112
    fn default() -> Self {
18✔
113
        Self {
18✔
114
            expr: SymbolExpr::Value(Value::Int(0)),
18✔
115
            name_map: HashMap::new(), // no parameters, hence empty name map
18✔
116
        }
18✔
117
    }
18✔
118
}
119

120
impl fmt::Display for ParameterExpression {
121
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336,795✔
122
        write!(f, "{}", {
336,795✔
123
            if let SymbolExpr::Symbol(s) = &self.expr {
336,795✔
124
                s.repr(false)
192,735✔
125
            } else {
126
                match self.expr.eval(true) {
144,060✔
127
                    Some(e) => e.to_string(),
184✔
128
                    None => self.expr.to_string(),
143,876✔
129
                }
130
            }
131
        })
132
    }
336,795✔
133
}
134

135
impl ParameterExpression {
136
    pub fn qpy_replay(&self) -> Vec<OPReplay> {
8,922✔
137
        let mut replay = Vec::new();
8,922✔
138
        let mut unused: IndexSet<_, ahash::RandomState> = self.name_map.values().cloned().collect();
8,922✔
139
        // The recursive inner `qpy_replay_inner` assumes it starts from a containing operation, so
140
        // fails to build a complete replay in the case it starts from a single symbol or value.
141
        match &self.expr {
8,922✔
142
            SymbolExpr::Value(v) => {
130✔
143
                let item = match *v {
130✔
144
                    Value::Int(v) => OPReplay {
130✔
145
                        op: OpCode::ADD,
130✔
146
                        lhs: Some(ParameterValueType::Int(v)),
130✔
147
                        rhs: Some(ParameterValueType::Int(0)),
130✔
148
                    },
130✔
149
                    Value::Real(v) => OPReplay {
×
150
                        op: OpCode::ADD,
×
151
                        lhs: Some(ParameterValueType::Float(v)),
×
152
                        // `-0.0` is technically the identity element of floating-point addition;
×
153
                        // `0.0 + x` is not bit-for-bit equal to `x` solely if `x` is `-0.0`.
×
154
                        rhs: Some(ParameterValueType::Float(-0.0)),
×
155
                    },
×
156
                    Value::Complex(v) => OPReplay {
×
157
                        op: OpCode::ADD,
×
158
                        lhs: Some(ParameterValueType::Complex(v)),
×
159
                        rhs: Some(ParameterValueType::Complex(Complex64 {
×
160
                            re: -0.0,
×
161
                            im: -0.0,
×
162
                        })),
×
163
                    },
×
164
                };
165
                replay.push(item);
130✔
166
            }
167
            SymbolExpr::Symbol(sym) => {
34✔
168
                unused.swap_remove(sym.as_ref());
34✔
169
                replay.push(OPReplay {
34✔
170
                    op: OpCode::ADD,
34✔
171
                    lhs: ParameterValueType::extract_from_expr(&self.expr),
34✔
172
                    rhs: Some(ParameterValueType::Int(0)),
34✔
173
                });
34✔
174
            }
34✔
175
            SymbolExpr::Unary { .. } | SymbolExpr::Binary { .. } => {
8,758✔
176
                qpy_replay_inner(self, &self.name_map, &mut replay, &mut unused);
8,758✔
177
            }
8,758✔
178
        }
179
        // For any unused symbols, we'll add something like `(x * 0) + expr`.  This sort of
180
        // cancellation is how unused symbols appear; it doesn't matter if the _actual_ cause was
181
        // `x - x` or whatever, because the end observable effect is the same.
182
        for symbol in unused {
8,922✔
183
            replay.push(OPReplay {
2,180✔
184
                op: OpCode::MUL,
2,180✔
185
                lhs: Some(ParameterValueType::from_symbol(symbol)),
2,180✔
186
                rhs: Some(ParameterValueType::Int(0)),
2,180✔
187
            });
2,180✔
188
            replay.push(OPReplay {
2,180✔
189
                op: OpCode::ADD,
2,180✔
190
                lhs: None,
2,180✔
191
                rhs: None,
2,180✔
192
            });
2,180✔
193
        }
2,180✔
194
        replay
8,922✔
195
    }
8,922✔
196
}
197
// This needs to be implemented manually, because PyO3 does not provide built-in
198
// conversions for the subclasses of ParameterExpression in Python. Specifically
199
// the Python classes Parameter and ParameterVector are subclasses of
200
// ParameterExpression and the default trait impl would not handle the specialization
201
// there.
202
impl<'py> IntoPyObject<'py> for ParameterExpression {
203
    type Target = PyParameterExpression;
204
    type Output = Bound<'py, Self::Target>;
205
    type Error = PyErr;
206

207
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
2✔
208
        let expr = PyParameterExpression::from(self.clone());
2✔
209
        expr.into_pyobject(py)
2✔
210
    }
2✔
211
}
212

213
/// Lookup for which operations are binary (i.e. require two operands).
214
static BINARY_OPS: [OpCode; 8] = [
215
    // a HashSet would be better but requires unstable features
216
    OpCode::ADD,
217
    OpCode::SUB,
218
    OpCode::MUL,
219
    OpCode::DIV,
220
    OpCode::POW,
221
    OpCode::RSUB,
222
    OpCode::RDIV,
223
    OpCode::RPOW,
224
];
225

226
impl ParameterExpression {
227
    /// Initialize with an existing [SymbolExpr] and its valid name map.
228
    ///
229
    /// Caution: The caller **guarantees** that ``name_map`` is consistent with ``expr``.
230
    /// If uncertain, call [Self::from_symbol_expr], which automatically builds the correct name map.
231
    pub fn new(expr: SymbolExpr, name_map: HashMap<String, Symbol>) -> Self {
2,510,066✔
232
        Self { expr, name_map }
2,510,066✔
233
    }
2,510,066✔
234

235
    /// Construct from a [Symbol].
236
    pub fn from_symbol(symbol: Symbol) -> Self {
85,586✔
237
        Self {
85,586✔
238
            expr: SymbolExpr::Symbol(Arc::new(symbol.clone())),
85,586✔
239
            name_map: [(symbol.repr(false), symbol)].into(),
85,586✔
240
        }
85,586✔
241
    }
85,586✔
242

243
    /// Try casting to a [Symbol].
244
    ///
245
    /// This only succeeds if the underlying expression is, in fact, only a symbol.
246
    pub fn try_to_symbol(&self) -> Result<Symbol, ParameterError> {
276,438✔
247
        self.try_to_symbol_ref().cloned()
276,438✔
248
    }
276,438✔
249

250
    /// Try casting to a [Symbol], returning a reference.
251
    ///
252
    /// This only succeeds if the underlying expression is, in fact, only a symbol.
253
    pub fn try_to_symbol_ref(&self) -> Result<&Symbol, ParameterError> {
276,438✔
254
        match &self.expr {
265,858✔
255
            SymbolExpr::Symbol(sym) if self.name_map.len() == 1 => Ok(sym.as_ref()),
265,858✔
256
            _ => Err(ParameterError::NotASymbol),
10,606✔
257
        }
258
    }
276,438✔
259

260
    /// Try casting to a [Value].
261
    ///
262
    /// Attempt to evaluate the expression recursively and return a [Value] if fully bound.
263
    ///
264
    /// # Arguments
265
    ///
266
    /// * strict - If ``true``, only allow returning a value if all symbols are bound. If
267
    ///   ``false``, allow casting expressions to values, even though symbols might still exist.
268
    ///   For example, ``0 * x`` will return ``0`` for ``strict=false`` and otherwise return
269
    ///   an error.
270
    pub fn try_to_value(&self, strict: bool) -> Result<Value, ParameterError> {
6,417,694✔
271
        if strict && !self.name_map.is_empty() {
6,417,694✔
272
            let free_symbols = self.expr.iter_symbols().cloned().collect();
74,756✔
273
            return Err(ParameterError::UnboundParameters(free_symbols));
74,756✔
274
        }
6,342,938✔
275

276
        match self.expr.eval(true) {
6,342,938✔
277
            Some(value) => {
413,216✔
278
                // we try to restrict complex to real, if possible
279
                if let Value::Complex(c) = value {
413,216✔
280
                    if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON).contains(&c.im)
37,138✔
281
                    {
282
                        return Ok(Value::Real(c.re));
×
283
                    }
37,138✔
284
                }
376,078✔
285
                Ok(value)
413,216✔
286
            }
287
            None => {
288
                let free_symbols = self.expr.iter_symbols().cloned().collect();
5,929,722✔
289
                Err(ParameterError::UnboundParameters(free_symbols))
5,929,722✔
290
            }
291
        }
292
    }
6,417,694✔
293

294
    /// Construct from a [SymbolExpr].
295
    ///
296
    /// This populates the name map with the symbols in the expression.
297
    pub fn from_symbol_expr(expr: SymbolExpr) -> Self {
4,791,996✔
298
        let name_map = expr.name_map();
4,791,996✔
299
        Self { expr, name_map }
4,791,996✔
300
    }
4,791,996✔
301

302
    /// Initialize from an f64.
303
    pub fn from_f64(value: f64) -> Self {
12,342✔
304
        Self {
12,342✔
305
            expr: SymbolExpr::Value(Value::Real(value)),
12,342✔
306
            name_map: HashMap::new(),
12,342✔
307
        }
12,342✔
308
    }
12,342✔
309

310
    /// Load from a sequence of [OPReplay]s. Used in serialization.
311
    pub fn from_qpy(replay: &[OPReplay]) -> Result<Self, ParameterError> {
18✔
312
        // the stack contains the latest lhs and rhs values
313
        let mut stack: Vec<ParameterExpression> = Vec::new();
18✔
314
        for OPReplay { op, lhs, rhs } in replay {
4,018✔
315
            // put the values on the stack, if they exist
316
            if let Some(value) = lhs {
4,018✔
317
                stack.push(value.clone().into());
2,016✔
318
            }
2,016✔
319
            if let Some(value) = rhs {
4,018✔
320
                stack.push(value.clone().into());
2,020✔
321
            }
2,020✔
322

323
            // if we need two operands, pop rhs from the stack
324
            let rhs = if BINARY_OPS.contains(op) {
4,018✔
325
                Some(stack.pop().expect("Pop from empty stack"))
4,018✔
326
            } else {
327
                None
×
328
            };
329

330
            // pop lhs from the stack, this we always need
331
            let lhs = stack.pop().expect("Pop from empty stack");
4,018✔
332

333
            // apply the operation and put the result onto the stack for the next replay
334
            let result: ParameterExpression = match op {
4,018✔
335
                OpCode::ADD => lhs.add(&rhs.unwrap())?,
2,010✔
336
                OpCode::MUL => lhs.mul(&rhs.unwrap())?,
2,000✔
337
                OpCode::SUB => lhs.sub(&rhs.unwrap())?,
4✔
338
                OpCode::RSUB => rhs.unwrap().sub(&lhs)?,
×
339
                OpCode::POW => lhs.pow(&rhs.unwrap())?,
4✔
340
                OpCode::RPOW => rhs.unwrap().pow(&lhs)?,
×
341
                OpCode::DIV => lhs.div(&rhs.unwrap())?,
×
342
                OpCode::RDIV => rhs.unwrap().div(&lhs)?,
×
343
                OpCode::ABS => lhs.abs(),
×
344
                OpCode::SIN => lhs.sin(),
×
345
                OpCode::ASIN => lhs.asin(),
×
346
                OpCode::COS => lhs.cos(),
×
347
                OpCode::ACOS => lhs.acos(),
×
348
                OpCode::TAN => lhs.tan(),
×
349
                OpCode::ATAN => lhs.atan(),
×
350
                OpCode::CONJ => lhs.conjugate(),
×
351
                OpCode::LOG => lhs.log(),
×
352
                OpCode::EXP => lhs.exp(),
×
353
                OpCode::SIGN => lhs.sign(),
×
354
                OpCode::GRAD | OpCode::SUBSTITUTE => {
355
                    panic!("GRAD and SUBSTITUTE are not supported.")
×
356
                }
357
            };
358
            stack.push(result);
4,018✔
359
        }
360

361
        // once we're done, just return the last element in the stack
362
        Ok(stack
18✔
363
            .pop()
18✔
364
            .expect("Invalid QPY replay encountered during deserialization: empty OPReplay."))
18✔
365
    }
18✔
366

367
    pub fn iter_symbols(&self) -> impl Iterator<Item = &Symbol> + '_ {
465,915✔
368
        self.name_map.values()
465,915✔
369
    }
465,915✔
370

371
    /// Get the number of [Symbol]s in the expression.
372
    pub fn num_symbols(&self) -> usize {
6✔
373
        self.name_map.len()
6✔
374
    }
6✔
375

376
    /// Whether the expression represents a complex number. None if cannot be determined.
377
    pub fn is_complex(&self) -> Option<bool> {
23,562✔
378
        self.expr.is_complex()
23,562✔
379
    }
23,562✔
380

381
    /// Whether the expression represents a int. None if cannot be determined.
382
    pub fn is_int(&self) -> Option<bool> {
86,132✔
383
        self.expr.is_int()
86,132✔
384
    }
86,132✔
385

386
    /// Add an expression; ``self + rhs``.
387
    pub fn add(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,394,176✔
388
        let name_map = self.merged_name_map(rhs)?;
2,394,176✔
389
        Ok(Self {
2,394,174✔
390
            expr: &self.expr + &rhs.expr,
2,394,174✔
391
            name_map,
2,394,174✔
392
        })
2,394,174✔
393
    }
2,394,176✔
394

395
    /// Multiply with an expression; ``self * rhs``.
396
    pub fn mul(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,418,038✔
397
        let name_map = self.merged_name_map(rhs)?;
2,418,038✔
398
        Ok(Self {
2,418,036✔
399
            expr: &self.expr * &rhs.expr,
2,418,036✔
400
            name_map,
2,418,036✔
401
        })
2,418,036✔
402
    }
2,418,038✔
403

404
    /// Subtract another expression; ``self - rhs``.
405
    pub fn sub(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
24,200✔
406
        let name_map = self.merged_name_map(rhs)?;
24,200✔
407
        Ok(Self {
24,198✔
408
            expr: &self.expr - &rhs.expr,
24,198✔
409
            name_map,
24,198✔
410
        })
24,198✔
411
    }
24,200✔
412

413
    /// Divide by another expression; ``self / rhs``.
414
    pub fn div(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
14,010✔
415
        if rhs.expr.is_zero() {
14,010✔
416
            return Err(ParameterError::ZeroDivisionError);
1,012✔
417
        }
12,998✔
418

419
        let name_map = self.merged_name_map(rhs)?;
12,998✔
420
        Ok(Self {
12,996✔
421
            expr: &self.expr / &rhs.expr,
12,996✔
422
            name_map,
12,996✔
423
        })
12,996✔
424
    }
14,010✔
425

426
    /// Raise this expression to a power; ``self ^ rhs``.
427
    pub fn pow(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
3,026✔
428
        let name_map = self.merged_name_map(rhs)?;
3,026✔
429
        Ok(Self {
3,026✔
430
            expr: self.expr.pow(&rhs.expr),
3,026✔
431
            name_map,
3,026✔
432
        })
3,026✔
433
    }
3,026✔
434

435
    /// Apply the sine to this expression; ``sin(self)``.
436
    pub fn sin(&self) -> Self {
362✔
437
        Self {
362✔
438
            expr: self.expr.sin(),
362✔
439
            name_map: self.name_map.clone(),
362✔
440
        }
362✔
441
    }
362✔
442

443
    /// Apply the cosine to this expression; ``cos(self)``.
444
    pub fn cos(&self) -> Self {
352✔
445
        Self {
352✔
446
            expr: self.expr.cos(),
352✔
447
            name_map: self.name_map.clone(),
352✔
448
        }
352✔
449
    }
352✔
450

451
    /// Apply the tangent to this expression; ``tan(self)``.
452
    pub fn tan(&self) -> Self {
352✔
453
        Self {
352✔
454
            expr: self.expr.tan(),
352✔
455
            name_map: self.name_map.clone(),
352✔
456
        }
352✔
457
    }
352✔
458

459
    /// Apply the arcsine to this expression; ``asin(self)``.
460
    pub fn asin(&self) -> Self {
100✔
461
        Self {
100✔
462
            expr: self.expr.asin(),
100✔
463
            name_map: self.name_map.clone(),
100✔
464
        }
100✔
465
    }
100✔
466

467
    /// Apply the arccosine to this expression; ``acos(self)``.
468
    pub fn acos(&self) -> Self {
100✔
469
        Self {
100✔
470
            expr: self.expr.acos(),
100✔
471
            name_map: self.name_map.clone(),
100✔
472
        }
100✔
473
    }
100✔
474

475
    /// Apply the arctangent to this expression; ``atan(self)``.
476
    pub fn atan(&self) -> Self {
100✔
477
        Self {
100✔
478
            expr: self.expr.atan(),
100✔
479
            name_map: self.name_map.clone(),
100✔
480
        }
100✔
481
    }
100✔
482

483
    /// Exponentiate this expression; ``exp(self)``.
484
    pub fn exp(&self) -> Self {
350✔
485
        Self {
350✔
486
            expr: self.expr.exp(),
350✔
487
            name_map: self.name_map.clone(),
350✔
488
        }
350✔
489
    }
350✔
490

491
    /// Take the (natural) logarithm of this expression; ``log(self)``.
492
    pub fn log(&self) -> Self {
266✔
493
        Self {
266✔
494
            expr: self.expr.log(),
266✔
495
            name_map: self.name_map.clone(),
266✔
496
        }
266✔
497
    }
266✔
498

499
    /// Take the absolute value of this expression; ``|self|``.
500
    pub fn abs(&self) -> Self {
374✔
501
        Self {
374✔
502
            expr: self.expr.abs(),
374✔
503
            name_map: self.name_map.clone(),
374✔
504
        }
374✔
505
    }
374✔
506

507
    /// Return the sign of this expression; ``sign(self)``.
508
    pub fn sign(&self) -> Self {
10✔
509
        Self {
10✔
510
            expr: self.expr.sign(),
10✔
511
            name_map: self.name_map.clone(),
10✔
512
        }
10✔
513
    }
10✔
514

515
    /// Complex conjugate the expression.
516
    pub fn conjugate(&self) -> Self {
1,832✔
517
        Self {
1,832✔
518
            expr: self.expr.conjugate(),
1,832✔
519
            name_map: self.name_map.clone(),
1,832✔
520
        }
1,832✔
521
    }
1,832✔
522

523
    /// negate the expression.
524
    pub fn neg(&self) -> Self {
×
525
        Self {
×
526
            expr: -&self.expr,
×
527
            name_map: self.name_map.clone(),
×
528
        }
×
529
    }
×
530

531
    /// Compute the derivative of the expression with respect to the provided symbol.
532
    ///
533
    /// Note that this keeps the name map unchanged. Meaning that computing the derivative
534
    /// of ``x`` will yield ``1`` but the expression still owns the symbol ``x``. This is
535
    /// done such that we can still bind the value ``x`` in an automated process.
536
    pub fn derivative(&self, param: &Symbol) -> Result<Self, ParameterError> {
74✔
537
        Ok(Self {
538
            expr: self
74✔
539
                .expr
74✔
540
                .derivative(param)
74✔
541
                .map_err(ParameterError::DerivativeNotSupported)?,
74✔
542
            name_map: self.name_map.clone(),
70✔
543
        })
544
    }
74✔
545

546
    /// Substitute symbols with [ParameterExpression]s.
547
    ///
548
    /// # Arguments
549
    ///
550
    /// * map - A hashmap with [Symbol] keys and [ParameterExpression]s to replace these
551
    ///   symbols with.
552
    /// * allow_unknown_parameters - If `false`, returns an error if any symbol in the
553
    ///   hashmap is not present in the expression. If `true`, unknown symbols are ignored.
554
    ///   Setting to `true` is slightly faster as it does not involve additional checks.
555
    ///
556
    /// # Returns
557
    ///
558
    /// * `Ok(Self)` - A parameter expression with the substituted expressions.
559
    /// * `Err(ParameterError)` - An error if the substitution failed.
560
    pub fn subs(
241,986✔
561
        &self,
241,986✔
562
        map: &HashMap<Symbol, Self>,
241,986✔
563
        allow_unknown_parameters: bool,
241,986✔
564
    ) -> Result<Self, ParameterError> {
241,986✔
565
        // Build the outgoing name map. In the process we check for any duplicates.
566
        let mut name_map: HashMap<String, Symbol> = HashMap::new();
241,986✔
567
        let mut symbol_map: HashMap<Symbol, SymbolExpr> = HashMap::new();
241,986✔
568

569
        // If we don't allow for unknown parameters, check if there are any.
570
        if !allow_unknown_parameters {
241,986✔
571
            let existing: HashSet<&Symbol> = self.name_map.values().collect();
36,878✔
572
            let to_replace: HashSet<&Symbol> = map.keys().collect();
36,878✔
573
            let mut difference = to_replace.difference(&existing).peekable();
36,878✔
574

575
            if difference.peek().is_some() {
36,878✔
576
                let different_symbols = difference.map(|s| (**s).clone()).collect();
2✔
577
                return Err(ParameterError::UnknownParameters(different_symbols));
2✔
578
            }
36,876✔
579
        }
205,108✔
580

581
        for (name, symbol) in self.name_map.iter() {
251,226✔
582
            // check if the symbol will get replaced
583
            if let Some(replacement) = map.get(symbol) {
251,226✔
584
                // If yes, update the name_map. This also checks for duplicates.
585
                for (replacement_name, replacement_symbol) in replacement.name_map.iter() {
39,368✔
586
                    if let Some(duplicate) = name_map.get(replacement_name) {
39,368✔
587
                        // If a symbol with the same name already exists, check whether it is
588
                        // the same symbol (fine) or a different symbol with the same name (conflict)!
589
                        if duplicate != replacement_symbol {
50✔
590
                            return Err(ParameterError::NameConflict);
2✔
591
                        }
48✔
592
                    } else {
39,318✔
593
                        // SAFETY: We know the key does not exist yet.
39,318✔
594
                        unsafe {
39,318✔
595
                            name_map.insert_unique_unchecked(
39,318✔
596
                                replacement_name.clone(),
39,318✔
597
                                replacement_symbol.clone(),
39,318✔
598
                            )
39,318✔
599
                        };
39,318✔
600
                    }
39,318✔
601
                }
602

603
                // If we got until here, there were no duplicates, so we are safe to
604
                // add this symbol to the internal replacement map.
605
                symbol_map.insert(symbol.clone(), replacement.expr.clone());
38,262✔
606
            } else {
607
                // no replacement for this symbol, carry on
608
                match name_map.entry(name.clone()) {
212,962✔
609
                    Entry::Occupied(duplicate) => {
28✔
610
                        if duplicate.get() != symbol {
28✔
UNCOV
611
                            return Err(ParameterError::NameConflict);
×
612
                        }
28✔
613
                    }
614
                    Entry::Vacant(e) => {
212,934✔
615
                        e.insert(symbol.clone());
212,934✔
616
                    }
212,934✔
617
                }
618
            }
619
        }
620

621
        let res = self.expr.subs(&symbol_map);
241,982✔
622
        Ok(Self {
241,982✔
623
            expr: res,
241,982✔
624
            name_map,
241,982✔
625
        })
241,982✔
626
    }
241,986✔
627

628
    /// Bind symbols to values.
629
    ///
630
    /// # Arguments
631
    ///
632
    /// * map - A hashmap with [Symbol] keys and [Value]s to replace these
633
    ///   symbols with.
634
    /// * allow_unknown_parameter - If `false`, returns an error if any symbol in the
635
    ///   hashmap is not present in the expression. If `true`, unknown symbols are ignored.
636
    ///   Setting to `true` is slightly faster as it does not involve additional checks.
637
    ///
638
    /// # Returns
639
    ///
640
    /// * `Ok(Self)` - A parameter expression with the bound symbols.
641
    /// * `Err(ParameterError)` - An error if binding failed.
642
    pub fn bind(
414,612✔
643
        &self,
414,612✔
644
        map: &HashMap<&Symbol, Value>,
414,612✔
645
        allow_unknown_parameters: bool,
414,612✔
646
    ) -> Result<Self, ParameterError> {
414,612✔
647
        // The set of symbols we will bind. Used twice, hence pre-computed here.
648
        let bind_symbols: HashSet<&Symbol> = map.keys().cloned().collect();
414,612✔
649

650
        // If we don't allow for unknown parameters, check if there are any.
651
        if !allow_unknown_parameters {
414,612✔
652
            let existing: HashSet<&Symbol> = self.name_map.values().collect();
209,416✔
653
            let mut difference = bind_symbols.difference(&existing).peekable();
209,416✔
654

655
            if difference.peek().is_some() {
209,416✔
656
                let different_symbols = difference.map(|s| (**s).clone()).collect();
×
657
                return Err(ParameterError::UnknownParameters(different_symbols));
×
658
            }
209,416✔
659
        }
205,196✔
660

661
        // bind the symbol expression and then check the outcome for inf/nan, or numeric values
662
        let bound_expr = self.expr.bind(map);
414,612✔
663
        let bound = match bound_expr.eval(true) {
414,612✔
664
            Some(v) => match &v {
411,886✔
665
                Value::Real(r) => {
346,594✔
666
                    if r.is_infinite() {
346,594✔
667
                        Err(ParameterError::BindingInf)
1,828✔
668
                    } else if r.is_nan() {
344,766✔
669
                        Err(ParameterError::BindingNaN)
×
670
                    } else {
671
                        Ok(SymbolExpr::Value(v))
344,766✔
672
                    }
673
                }
674
                Value::Int(_) => Ok(SymbolExpr::Value(v)),
26,624✔
675
                Value::Complex(c) => {
38,668✔
676
                    if c.re.is_infinite() || c.im.is_infinite() {
38,668✔
677
                        Err(ParameterError::BindingInf)
×
678
                    } else if c.re.is_nan() || c.im.is_nan() {
38,668✔
679
                        Err(ParameterError::BindingNaN)
×
680
                    } else if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON)
38,668✔
681
                        .contains(&c.im)
38,668✔
682
                    {
683
                        Ok(SymbolExpr::Value(Value::Real(c.re)))
1,728✔
684
                    } else {
685
                        Ok(SymbolExpr::Value(v))
36,940✔
686
                    }
687
                }
688
            },
689
            None => Ok(bound_expr),
2,726✔
690
        }?;
1,828✔
691

692
        // update the name map by removing the bound parameters
693
        let bound_name_map: HashMap<String, Symbol> = self
412,784✔
694
            .name_map
412,784✔
695
            .iter()
412,784✔
696
            .filter(|(_, symbol)| !bind_symbols.contains(symbol))
4,711,788✔
697
            .map(|(name, symbol)| (name.clone(), symbol.clone()))
412,784✔
698
            .collect();
412,784✔
699

700
        Ok(Self {
412,784✔
701
            expr: bound,
412,784✔
702
            name_map: bound_name_map,
412,784✔
703
        })
412,784✔
704
    }
414,612✔
705

706
    /// Merge name maps.
707
    ///
708
    /// # Arguments
709
    ///
710
    /// * `other` - The other parameter expression whose symbols we add to self.
711
    ///
712
    /// # Returns
713
    ///
714
    /// * `Ok(HashMap<String, Symbol>)` - The merged name map.
715
    /// * `Err(ParameterError)` - An error if there was a name conflict.
716
    fn merged_name_map(&self, other: &Self) -> Result<HashMap<String, Symbol>, ParameterError> {
4,852,438✔
717
        let mut merged = self.name_map.clone();
4,852,438✔
718
        for (name, param) in other.name_map.iter() {
7,055,052✔
719
            match merged.get(name) {
7,053,852✔
720
                Some(existing_param) => {
3,990,564✔
721
                    if param != existing_param {
3,990,564✔
722
                        return Err(ParameterError::NameConflict);
8✔
723
                    }
3,990,556✔
724
                }
725
                None => {
3,063,288✔
726
                    // SAFETY: We ensured the key is unique
3,063,288✔
727
                    let _ = unsafe { merged.insert_unique_unchecked(name.clone(), param.clone()) };
3,063,288✔
728
                }
3,063,288✔
729
            }
730
        }
731
        Ok(merged)
4,852,430✔
732
    }
4,852,438✔
733
}
734

735
/// A parameter expression.
736
///
737
/// This is backed by Qiskit's symbolic expression engine and a cache
738
/// for the parameters inside the expression.
739
#[pyclass(
740
    subclass,
741
    module = "qiskit._accelerate.circuit",
742
    name = "ParameterExpression",
743
    from_py_object
2,284,956✔
744
)]
745
#[derive(Clone, Debug)]
746
pub struct PyParameterExpression {
747
    pub inner: ParameterExpression,
748
}
749

750
impl Default for PyParameterExpression {
751
    /// The default constructor returns zero.
752
    fn default() -> Self {
18✔
753
        Self {
18✔
754
            inner: ParameterExpression::default(),
18✔
755
        }
18✔
756
    }
18✔
757
}
758

759
impl fmt::Display for PyParameterExpression {
760
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336,780✔
761
        self.inner.fmt(f)
336,780✔
762
    }
336,780✔
763
}
764

765
impl From<ParameterExpression> for PyParameterExpression {
766
    fn from(value: ParameterExpression) -> Self {
12,365,394✔
767
        Self { inner: value }
12,365,394✔
768
    }
12,365,394✔
769
}
770

771
impl PyParameterExpression {
772
    /// Attempt to extract a `PyParameterExpression` from a bound `PyAny`.
773
    ///
774
    /// This will try to coerce to the strictest data type:
775
    /// Int - Real - Complex - PyParameterVectorElement - PyParameter - PyParameterExpression.
776
    ///
777
    /// # Arguments:
778
    ///
779
    /// * ob - The bound `PyAny` to extract from.
780
    ///
781
    /// # Returns
782
    ///
783
    /// * `Ok(Self)` - The extracted expression.
784
    /// * `Err(PyResult)` - An error if extraction to all above types failed.
785
    pub fn extract_coerce(ob: Borrowed<PyAny>) -> PyResult<Self> {
4,967,974✔
786
        if let Ok(i) = ob.cast::<PyInt>() {
4,967,974✔
787
            Ok(ParameterExpression::new(
134,624✔
788
                SymbolExpr::Value(Value::from(i.extract::<i64>()?)),
134,624✔
789
                HashMap::new(),
134,624✔
790
            )
791
            .into())
134,624✔
792
        } else if let Ok(r) = ob.cast::<PyFloat>() {
4,833,350✔
793
            let r: f64 = r.extract()?;
13,388✔
794
            if r.is_infinite() || r.is_nan() {
13,388✔
795
                return Err(ParameterError::InvalidValue.into());
40✔
796
            }
13,348✔
797
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(r)), HashMap::new()).into())
13,348✔
798
        } else if let Ok(c) = ob.cast::<PyComplex>() {
4,819,962✔
799
            let c: Complex64 = c.extract()?;
2,361,356✔
800
            if c.is_infinite() || c.is_nan() {
2,361,356✔
801
                return Err(ParameterError::InvalidValue.into());
×
802
            }
2,361,356✔
803
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(c)), HashMap::new()).into())
2,361,356✔
804
        } else if let Ok(element) = ob.cast::<PyParameterVectorElement>() {
2,458,606✔
805
            Ok(ParameterExpression::from_symbol(element.borrow().symbol.clone()).into())
57,010✔
806
        } else if let Ok(parameter) = ob.cast::<PyParameter>() {
2,401,596✔
807
            Ok(ParameterExpression::from_symbol(parameter.borrow().symbol.clone()).into())
15,182✔
808
        } else {
809
            ob.extract::<PyParameterExpression>().map_err(Into::into)
2,386,414✔
810
        }
811
    }
4,967,974✔
812

813
    pub fn coerce_into_py(&self, py: Python) -> PyResult<Py<PyAny>> {
33,884✔
814
        if let Ok(value) = self.inner.try_to_value(true) {
33,884✔
815
            match value {
14✔
816
                Value::Int(i) => Ok(PyInt::new(py, i).unbind().into_any()),
×
817
                Value::Real(r) => Ok(PyFloat::new(py, r).unbind().into_any()),
14✔
818
                Value::Complex(c) => Ok(PyComplex::from_complex_bound(py, c).unbind().into_any()),
×
819
            }
820
        } else if let Ok(symbol) = self.inner.try_to_symbol() {
33,870✔
821
            if symbol.index.is_some() {
23,502✔
822
                Ok(Py::new(py, PyParameterVectorElement::from_symbol(symbol))?.into_any())
5,946✔
823
            } else {
824
                Ok(Py::new(py, PyParameter::from_symbol(symbol))?.into_any())
17,556✔
825
            }
826
        } else {
827
            self.clone().into_py_any(py)
10,368✔
828
        }
829
    }
33,884✔
830
}
831

832
#[pymethods]
×
833
impl PyParameterExpression {
834
    /// This is a **strictly internal** constructor and **should not be used**.
835
    /// It is subject to arbitrary change in between Qiskit versions and cannot be relied on.
836
    /// Parameter expressions should always be constructed from applying operations on
837
    /// parameters, or by loading via QPY.
838
    ///
839
    /// The input values are allowed to be None for pickling purposes.
840
    #[new]
841
    #[pyo3(signature = (name_map=None, expr=None))]
842
    pub fn py_new(
88✔
843
        name_map: Option<HashMap<String, PyParameter>>,
88✔
844
        expr: Option<String>,
88✔
845
    ) -> PyResult<Self> {
88✔
846
        match (name_map, expr) {
88✔
847
            (None, None) => Ok(Self::default()),
18✔
848
            (Some(name_map), Some(expr)) => {
70✔
849
                // We first parse the expression and then update the symbols with the ones
850
                // the user provided. The replacement relies on the names to match.
851
                // This is hacky and we likely want a more reliably conversion from a SymPy object,
852
                // if we decide we want to continue supporting this.
853
                let expr = parse_expression(&expr)
70✔
854
                    .map_err(|_| PyRuntimeError::new_err("Failed parsing input expression"))?;
70✔
855
                let symbol_map: HashMap<String, Symbol> = name_map
70✔
856
                    .iter()
70✔
857
                    .map(|(string, param)| (string.clone(), param.symbol.clone()))
70✔
858
                    .collect();
70✔
859

860
                let replaced_expr = symbol_expr::replace_symbol(&expr, &symbol_map);
70✔
861

862
                let inner = ParameterExpression::new(replaced_expr, symbol_map);
70✔
863
                Ok(Self { inner })
70✔
864
            }
865
            _ => Err(PyValueError::new_err(
×
866
                "Pass either both a name_map and expr, or neither",
×
867
            )),
×
868
        }
869
    }
88✔
870

871
    #[allow(non_snake_case)]
872
    #[staticmethod]
873
    pub fn _Value(value: &Bound<PyAny>) -> PyResult<Self> {
106✔
874
        Self::extract_coerce(value.as_borrowed())
106✔
875
    }
106✔
876

877
    /// Check if the expression corresponds to a plain symbol.
878
    ///
879
    /// Returns:
880
    ///     ``True`` is this expression corresponds to a symbol, ``False`` otherwise.
881
    pub fn is_symbol(&self) -> bool {
506✔
882
        matches!(self.inner.expr, SymbolExpr::Symbol(_))
506✔
883
    }
506✔
884

885
    /// Cast this expression to a numeric value.
886
    ///
887
    /// Args:
888
    ///     strict: If ``True`` (default) this function raises an error if there are any
889
    ///         unbound symbols in the expression. If ``False``, this allows casting
890
    ///         if the expression represents a numeric value, regardless of unbound symbols.
891
    ///         For example ``(0 * Parameter("x"))`` is 0 but has the symbol ``x`` present.
892
    #[pyo3(signature = (strict=true))]
893
    pub fn numeric(&self, py: Python, strict: bool) -> PyResult<Py<PyAny>> {
47,712✔
894
        match self.inner.try_to_value(strict)? {
47,712✔
895
            Value::Real(r) => r.into_py_any(py),
21,302✔
896
            Value::Int(i) => i.into_py_any(py),
11,792✔
897
            Value::Complex(c) => c.into_py_any(py),
14,488✔
898
        }
899
    }
47,712✔
900

901
    /// Return a SymPy equivalent of this expression.
902
    ///
903
    /// Returns:
904
    ///     A SymPy equivalent of this expression.
905
    pub fn sympify(&self, py: Python) -> PyResult<Py<PyAny>> {
506✔
906
        let py_sympify = SYMPIFY_PARAMETER_EXPRESSION.get(py);
506✔
907
        py_sympify.call1(py, (self.clone(),))
506✔
908
    }
506✔
909

910
    /// The number of unbound parameters in the expression.
911
    ///
912
    /// This is equivalent to ``len(expr.parameters)`` but does not involve the overhead of creating
913
    /// a set and counting its length.
914
    #[getter]
915
    pub fn num_parameters(&self) -> usize {
6✔
916
        self.inner.num_symbols()
6✔
917
    }
6✔
918

919
    /// Get the parameters present in the expression.
920
    ///
921
    /// .. note::
922
    ///
923
    ///     Qiskit guarantees equality (via ``==``) of parameters retrieved from an expression
924
    ///     with the original :class:`.Parameter` objects used to create this expression,
925
    ///     but does **not guarantee** ``is`` comparisons to succeed.
926
    ///
927
    #[getter]
928
    pub fn parameters<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PySet>> {
136,364✔
929
        let py_parameters: Vec<Py<PyAny>> = self
136,364✔
930
            .inner
136,364✔
931
            .iter_symbols()
136,364✔
932
            .map(|symbol| {
4,572,614✔
933
                if symbol.is_vector_element() {
4,572,614✔
934
                    Ok(
935
                        Py::new(py, PyParameterVectorElement::from_symbol(symbol.clone()))?
4,505,442✔
936
                            .into_any(),
4,505,442✔
937
                    )
938
                } else {
939
                    Ok(Py::new(py, PyParameter::from_symbol(symbol.clone()))?.into_any())
67,172✔
940
                }
941
            })
4,572,614✔
942
            .collect::<PyResult<_>>()?;
136,364✔
943
        PySet::new(py, py_parameters)
136,364✔
944
    }
136,364✔
945

946
    /// Sine of the expression.
947
    #[inline]
948
    #[pyo3(name = "sin")]
949
    pub fn py_sin(&self) -> Self {
356✔
950
        self.inner.sin().into()
356✔
951
    }
356✔
952

953
    /// Cosine of the expression.
954
    #[inline]
955
    #[pyo3(name = "cos")]
956
    pub fn py_cos(&self) -> Self {
350✔
957
        self.inner.cos().into()
350✔
958
    }
350✔
959

960
    /// Tangent of the expression.
961
    #[inline]
962
    #[pyo3(name = "tan")]
963
    pub fn py_tan(&self) -> Self {
350✔
964
        self.inner.tan().into()
350✔
965
    }
350✔
966

967
    /// Arcsine of the expression.
968
    #[inline]
969
    pub fn arcsin(&self) -> Self {
98✔
970
        self.inner.asin().into()
98✔
971
    }
98✔
972

973
    /// Arccosine of the expression.
974
    #[inline]
975
    pub fn arccos(&self) -> Self {
98✔
976
        self.inner.acos().into()
98✔
977
    }
98✔
978

979
    /// Arctangent of the expression.
980
    #[inline]
981
    pub fn arctan(&self) -> Self {
98✔
982
        self.inner.atan().into()
98✔
983
    }
98✔
984

985
    /// Exponentiate the expression.
986
    #[inline]
987
    #[pyo3(name = "exp")]
988
    pub fn py_exp(&self) -> Self {
348✔
989
        self.inner.exp().into()
348✔
990
    }
348✔
991

992
    /// Take the natural logarithm of the expression.
993
    #[inline]
994
    #[pyo3(name = "log")]
995
    pub fn py_log(&self) -> Self {
264✔
996
        self.inner.log().into()
264✔
997
    }
264✔
998

999
    /// Take the absolute value of the expression.
1000
    #[inline]
1001
    #[pyo3(name = "abs")]
1002
    pub fn py_abs(&self) -> Self {
10✔
1003
        self.inner.abs().into()
10✔
1004
    }
10✔
1005

1006
    /// Return the sign of the expression.
1007
    #[inline]
1008
    #[pyo3(name = "sign")]
1009
    pub fn py_sign(&self) -> Self {
10✔
1010
        self.inner.sign().into()
10✔
1011
    }
10✔
1012

1013
    /// Return the complex conjugate of the expression.
1014
    #[inline]
1015
    #[pyo3(name = "conjugate")]
1016
    pub fn py_conjugate(&self) -> Self {
1,830✔
1017
        self.inner.conjugate().into()
1,830✔
1018
    }
1,830✔
1019

1020
    /// Check whether the expression represents a real number.
1021
    ///
1022
    /// Note that this will return ``None`` if there are unbound parameters, in which case
1023
    /// it cannot be determined whether the expression is real.
1024
    #[inline]
1025
    #[pyo3(name = "is_real")]
1026
    pub fn py_is_real(&self) -> Option<bool> {
270✔
1027
        self.inner.expr.is_real()
270✔
1028
    }
270✔
1029

1030
    /// Return derivative of this expression with respect to the input parameter.
1031
    ///
1032
    /// Args:
1033
    ///     param: The parameter with respect to which the derivative is calculated.
1034
    ///
1035
    /// Returns:
1036
    ///     The derivative as either a constant numeric value or a symbolic
1037
    ///     :class:`.ParameterExpression`.
1038
    pub fn gradient(&self, param: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
74✔
1039
        let symbol = symbol_from_py_parameter(param)?;
74✔
1040
        let d_expr = self.inner.derivative(&symbol)?;
74✔
1041

1042
        // try converting to value and return as built-in numeric type
1043
        match d_expr.try_to_value(false) {
70✔
1044
            Ok(val) => match val {
20✔
1045
                Value::Real(r) => r.into_py_any(param.py()),
18✔
1046
                Value::Int(i) => i.into_py_any(param.py()),
2✔
1047
                Value::Complex(c) => c.into_py_any(param.py()),
×
1048
            },
1049
            Err(_) => PyParameterExpression::from(d_expr).into_py_any(param.py()),
50✔
1050
        }
1051
    }
74✔
1052

1053
    /// Return all values in this equation.
1054
    pub fn _values(&self, py: Python) -> PyResult<Vec<Py<PyAny>>> {
78✔
1055
        self.inner
78✔
1056
            .expr
78✔
1057
            .values()
78✔
1058
            .iter()
78✔
1059
            .map(|val| match val {
78✔
1060
                Value::Real(r) => r.into_py_any(py),
34✔
1061
                Value::Int(i) => i.into_py_any(py),
10✔
1062
                Value::Complex(c) => c.into_py_any(py),
×
1063
            })
44✔
1064
            .collect()
78✔
1065
    }
78✔
1066

1067
    /// Returns a new expression with replacement parameters.
1068
    ///
1069
    /// Args:
1070
    ///     parameter_map: Mapping from :class:`.Parameter`\ s in ``self`` to the
1071
    ///         :class:`.ParameterExpression` instances with which they should be replaced.
1072
    ///     allow_unknown_parameters: If ``False``, raises an error if ``parameter_map``
1073
    ///         contains :class:`.Parameter`\ s in the keys outside those present in the expression.
1074
    ///         If ``True``, any such parameters are simply ignored.
1075
    ///
1076
    /// Raises:
1077
    ///     CircuitError:
1078
    ///         - If parameter_map contains parameters outside those in self.
1079
    ///         - If the replacement parameters in ``parameter_map`` would result in
1080
    ///           a name conflict in the generated expression.
1081
    ///
1082
    /// Returns:
1083
    ///     A new expression with the specified parameters replaced.
1084
    #[pyo3(name = "subs")]
1085
    #[pyo3(signature = (parameter_map, allow_unknown_parameters=false))]
1086
    pub fn py_subs(
252✔
1087
        &self,
252✔
1088
        parameter_map: HashMap<PyParameter, Self>,
252✔
1089
        allow_unknown_parameters: bool,
252✔
1090
    ) -> PyResult<Self> {
252✔
1091
        // reduce the map to a HashMap<Symbol, ParameterExpression>
1092
        let map = parameter_map
252✔
1093
            .into_iter()
252✔
1094
            .map(|(param, expr)| Ok((param.symbol, expr.inner)))
256✔
1095
            .collect::<PyResult<_>>()?;
252✔
1096

1097
        // apply to the inner expression
1098
        match self.inner.subs(&map, allow_unknown_parameters) {
252✔
1099
            Ok(subbed) => Ok(subbed.into()),
248✔
1100
            Err(e) => Err(e.into()),
4✔
1101
        }
1102
    }
252✔
1103

1104
    /// Binds the provided set of parameters to their corresponding values.
1105
    ///
1106
    /// Args:
1107
    ///     parameter_values: Mapping of :class:`.Parameter` instances to the numeric value to which
1108
    ///         they will be bound.
1109
    ///     allow_unknown_parameters: If ``False``, raises an error if ``parameter_values``
1110
    ///         contains :class:`.Parameter`\ s in the keys outside those present in the expression.
1111
    ///         If ``True``, any such parameters are simply ignored.
1112
    ///
1113
    /// Raises:
1114
    ///     CircuitError:
1115
    ///         - If parameter_values contains parameters outside those in self.
1116
    ///         - If a non-numeric value is passed in ``parameter_values``.
1117
    ///     ZeroDivisionError:
1118
    ///         - If binding the provided values requires division by zero.
1119
    ///
1120
    /// Returns:
1121
    ///     A new expression parameterized by any parameters which were not bound by
1122
    ///     ``parameter_values``.
1123
    #[pyo3(name = "bind")]
1124
    #[pyo3(signature = (parameter_values, allow_unknown_parameters=false))]
1125
    pub fn py_bind(
127,050✔
1126
        &self,
127,050✔
1127
        parameter_values: HashMap<PyParameter, Bound<PyAny>>,
127,050✔
1128
        allow_unknown_parameters: bool,
127,050✔
1129
    ) -> PyResult<Self> {
127,050✔
1130
        // reduce the map to a HashMap<Symbol, Value>
1131
        let map = parameter_values
127,050✔
1132
            .iter()
127,050✔
1133
            .map(|(param, value)| {
4,555,024✔
1134
                let value = value.extract()?;
4,555,024✔
1135
                Ok((param.symbol(), value))
4,555,024✔
1136
            })
4,555,024✔
1137
            .collect::<PyResult<_>>()?;
127,050✔
1138

1139
        // apply to the inner expression
1140
        match self.inner.bind(&map, allow_unknown_parameters) {
127,050✔
1141
            Ok(bound) => Ok(bound.into()),
125,222✔
1142
            Err(e) => Err(e.into()),
1,828✔
1143
        }
1144
    }
127,050✔
1145

1146
    /// Bind all of the parameters in ``self`` to numeric values in the dictionary, returning a
1147
    /// numeric value.
1148
    ///
1149
    /// This is a special case of :meth:`bind` which can reach higher performance.  It is no problem
1150
    /// for the ``values`` dictionary to contain parameters that are not used in this expression;
1151
    /// the expectation is that the same bindings dictionary will be fed to other expressions as
1152
    /// well.
1153
    ///
1154
    /// It is an error to call this method with a ``values`` dictionary that does not bind all of
1155
    /// the values, or to call this method with non-numeric values, but this is not explicitly
1156
    /// checked, since this method is intended for performance-sensitive use.  Passing an incorrect
1157
    /// dictionary may result in unexpected behavior.
1158
    ///
1159
    /// Unlike :meth:`bind`, this method will not raise an exception if non-finite floating-point
1160
    /// values are encountered.
1161
    ///
1162
    /// Args:
1163
    ///     values: mapping of parameters to numeric values.
1164
    #[pyo3(name = "bind_all")]
1165
    #[pyo3(signature = (values, *))]
1166
    pub fn py_bind_all(&self, values: Bound<PyAny>) -> PyResult<Value> {
4✔
1167
        let mut partial_map = HashMap::with_capacity(self.inner.name_map.len());
4✔
1168
        for symbol in self.inner.name_map.values() {
6✔
1169
            let py_parameter = symbol.clone().into_pyobject(values.py())?;
6✔
1170
            partial_map.insert(symbol, values.get_item(py_parameter)?.extract()?);
6✔
1171
        }
1172
        let bound = self.inner.expr.bind(&partial_map);
2✔
1173
        bound.eval(true).ok_or_else(|| {
2✔
1174
            PyTypeError::new_err(format!(
×
1175
                "binding did not produce a numeric quantity: {bound:?}"
1176
            ))
1177
        })
×
1178
    }
4✔
1179

1180
    /// Assign one parameter to a value, which can either be numeric or another parameter
1181
    /// expression.
1182
    ///
1183
    /// Args:
1184
    ///     parameter: A parameter in this expression whose value will be updated.
1185
    ///     value: The new value to bind to.
1186
    ///
1187
    /// Returns:
1188
    ///     A new expression parameterized by any parameters which were not bound by assignment.
1189
    #[pyo3(name = "assign")]
1190
    pub fn py_assign(&self, parameter: PyParameter, value: &Bound<PyAny>) -> PyResult<Self> {
174✔
1191
        if let Ok(expr) = value.cast::<Self>() {
174✔
1192
            let map = [(parameter, expr.borrow().clone())].into_iter().collect();
150✔
1193
            self.py_subs(map, false)
150✔
1194
        } else if value.extract::<Value>().is_ok() {
24✔
1195
            let map = [(parameter, value.clone())].into_iter().collect();
24✔
1196
            self.py_bind(map, false)
24✔
1197
        } else {
1198
            Err(PyValueError::new_err(
×
1199
                "Unexpected value in assign: {replacement:?}",
×
1200
            ))
×
1201
        }
1202
    }
174✔
1203

1204
    #[inline]
1205
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
18✔
1206
        // ParameterExpression is immutable.
1207
        slf
18✔
1208
    }
18✔
1209

1210
    #[inline]
1211
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
1,732✔
1212
        // Everything a ParameterExpression contains is immutable.
1213
        slf
1,732✔
1214
    }
1,732✔
1215

1216
    pub fn __eq__(&self, rhs: &Bound<PyAny>) -> PyResult<bool> {
39,896✔
1217
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
39,896✔
1218
            match rhs.inner.expr {
39,882✔
1219
                SymbolExpr::Value(v) => match self.inner.try_to_value(false) {
2,720✔
1220
                    Ok(e) => Ok(e == v),
2,506✔
1221
                    Err(_) => Ok(false),
214✔
1222
                },
1223
                _ => Ok(self.inner.expr == rhs.inner.expr),
37,162✔
1224
            }
1225
        } else {
1226
            Ok(false)
14✔
1227
        }
1228
    }
39,896✔
1229

1230
    #[inline]
1231
    pub fn __abs__(&self) -> Self {
362✔
1232
        self.inner.abs().into()
362✔
1233
    }
362✔
1234

1235
    #[inline]
1236
    pub fn __pos__(&self) -> Self {
4✔
1237
        self.clone()
4✔
1238
    }
4✔
1239

1240
    pub fn __neg__(&self) -> Self {
668✔
1241
        Self {
668✔
1242
            inner: ParameterExpression::new(-&self.inner.expr, self.inner.name_map.clone()),
668✔
1243
        }
668✔
1244
    }
668✔
1245

1246
    pub fn __add__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
2,318,660✔
1247
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
2,318,660✔
1248
            Ok(self.inner.add(&rhs.inner)?.into())
2,318,648✔
1249
        } else {
1250
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1251
                "Unsupported data type for __add__",
12✔
1252
            ))
12✔
1253
        }
1254
    }
2,318,660✔
1255

1256
    pub fn __radd__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
63,928✔
1257
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
63,928✔
1258
            Ok(lhs.inner.add(&self.inner)?.into())
63,916✔
1259
        } else {
1260
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1261
                "Unsupported data type for __radd__",
12✔
1262
            ))
12✔
1263
        }
1264
    }
63,928✔
1265

1266
    pub fn __sub__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
18,484✔
1267
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
18,484✔
1268
            Ok(self.inner.sub(&rhs.inner)?.into())
18,472✔
1269
        } else {
1270
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1271
                "Unsupported data type for __sub__",
12✔
1272
            ))
12✔
1273
        }
1274
    }
18,484✔
1275

1276
    pub fn __rsub__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
4,320✔
1277
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
4,320✔
1278
            Ok(lhs.inner.sub(&self.inner)?.into())
4,308✔
1279
        } else {
1280
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1281
                "Unsupported data type for __rsub__",
12✔
1282
            ))
12✔
1283
        }
1284
    }
4,320✔
1285

1286
    pub fn __mul__<'py>(&self, rhs: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
2,408,110✔
1287
        let py = rhs.py();
2,408,110✔
1288
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
2,408,110✔
1289
            match self.inner.mul(&rhs.inner) {
2,400,750✔
1290
                Ok(result) => PyParameterExpression::from(result).into_bound_py_any(py),
2,400,748✔
1291
                Err(e) => Err(PyErr::from(e)),
2✔
1292
            }
1293
        } else {
1294
            PyNotImplemented::get(py).into_bound_py_any(py)
7,360✔
1295
        }
1296
    }
2,408,110✔
1297

1298
    pub fn __rmul__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
12,162✔
1299
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
12,162✔
1300
            Ok(lhs.inner.mul(&self.inner)?.into())
12,150✔
1301
        } else {
1302
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1303
                "Unsupported data type for __rmul__",
12✔
1304
            ))
12✔
1305
        }
1306
    }
12,162✔
1307

1308
    pub fn __truediv__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
8,594✔
1309
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
8,594✔
1310
            Ok(self.inner.div(&rhs.inner)?.into())
8,582✔
1311
        } else {
1312
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1313
                "Unsupported data type for __truediv__",
12✔
1314
            ))
12✔
1315
        }
1316
    }
8,594✔
1317

1318
    pub fn __rtruediv__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
4,068✔
1319
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
4,068✔
1320
            Ok(lhs.inner.div(&self.inner)?.into())
4,056✔
1321
        } else {
1322
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1323
                "Unsupported data type for __rtruediv__",
12✔
1324
            ))
12✔
1325
        }
1326
    }
4,068✔
1327

1328
    pub fn __pow__(&self, rhs: &Bound<PyAny>, _modulo: Option<i32>) -> PyResult<Self> {
1,996✔
1329
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
1,996✔
1330
            Ok(self.inner.pow(&rhs.inner)?.into())
1,984✔
1331
        } else {
1332
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1333
                "Unsupported data type for __pow__",
12✔
1334
            ))
12✔
1335
        }
1336
    }
1,996✔
1337

1338
    pub fn __rpow__(&self, lhs: &Bound<PyAny>, _modulo: Option<i32>) -> PyResult<Self> {
1,038✔
1339
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
1,038✔
1340
            Ok(lhs.inner.pow(&self.inner)?.into())
1,026✔
1341
        } else {
1342
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1343
                "Unsupported data type for __rpow__",
12✔
1344
            ))
12✔
1345
        }
1346
    }
1,038✔
1347

1348
    pub fn __int__(&self) -> PyResult<i64> {
176✔
1349
        match self.inner.try_to_value(false)? {
176✔
1350
            Value::Complex(_) => Err(PyTypeError::new_err(
42✔
1351
                "Cannot cast complex parameter to int.",
42✔
1352
            )),
42✔
1353
            Value::Real(r) => {
90✔
1354
                let rounded = r.floor();
90✔
1355
                Ok(rounded as i64)
90✔
1356
            }
1357
            Value::Int(i) => Ok(i),
42✔
1358
        }
1359
    }
176✔
1360

1361
    pub fn __float__(&self) -> PyResult<f64> {
2,688✔
1362
        match self.inner.try_to_value(false)? {
2,688✔
1363
            Value::Complex(c) => {
44✔
1364
                if c.im.abs() > SYMEXPR_EPSILON {
44✔
1365
                    Err(PyTypeError::new_err(
44✔
1366
                        "Could not cast complex parameter expression to float.",
44✔
1367
                    ))
44✔
1368
                } else {
1369
                    Ok(c.re)
×
1370
                }
1371
            }
1372
            Value::Real(r) => Ok(r),
114✔
1373
            Value::Int(i) => Ok(i as f64),
56✔
1374
        }
1375
    }
2,688✔
1376

1377
    pub fn __complex__(&self) -> PyResult<Complex64> {
79,876✔
1378
        match self.inner.try_to_value(false)? {
79,876✔
1379
            Value::Complex(c) => Ok(c),
22,476✔
1380
            Value::Real(r) => Ok(Complex64::new(r, 0.)),
40,668✔
1381
            Value::Int(i) => Ok(Complex64::new(i as f64, 0.)),
14,710✔
1382
        }
1383
    }
79,876✔
1384

1385
    pub fn __str__(&self) -> String {
336,780✔
1386
        self.to_string()
336,780✔
1387
    }
336,780✔
1388

1389
    pub fn __hash__(&self, py: Python) -> PyResult<u64> {
5,924,840✔
1390
        match self.inner.try_to_value(false) {
5,924,840✔
1391
            // if a value, we promise to match the hash of the raw value!
1392
            Ok(value) => {
2✔
1393
                let py_hash = BUILTIN_HASH.get_bound(py);
2✔
1394
                match value {
2✔
1395
                    Value::Complex(c) => py_hash.call1((c,))?.extract::<u64>(),
×
1396
                    Value::Real(r) => py_hash.call1((r,))?.extract::<u64>(),
2✔
1397
                    Value::Int(i) => py_hash.call1((i,))?.extract::<u64>(),
×
1398
                }
1399
            }
1400
            Err(_) => {
1401
                let mut hasher = DefaultHasher::new();
5,924,838✔
1402
                self.inner.expr.string_id().hash(&mut hasher);
5,924,838✔
1403
                Ok(hasher.finish())
5,924,838✔
1404
            }
1405
        }
1406
    }
5,924,840✔
1407

1408
    fn __getstate__(&self) -> Vec<OPReplay> {
8,334✔
1409
        self._qpy_replay()
8,334✔
1410
    }
8,334✔
1411

1412
    fn __setstate__(&mut self, state: Vec<OPReplay>) -> PyResult<()> {
18✔
1413
        self.inner = ParameterExpression::from_qpy(&state)?;
18✔
1414
        Ok(())
18✔
1415
    }
18✔
1416

1417
    #[getter]
1418
    fn _qpy_replay(&self) -> Vec<OPReplay> {
8,682✔
1419
        self.inner.qpy_replay()
8,682✔
1420
    }
8,682✔
1421
}
1422

1423
/// A compile-time symbolic parameter.
1424
///
1425
/// The value of a :class:`.Parameter` must be entirely determined before a circuit begins execution.
1426
/// Typically this will mean that you should supply values for all :class:`.Parameter`\ s in a
1427
/// circuit using :meth:`.QuantumCircuit.assign_parameters`, though certain hardware vendors may
1428
/// allow you to give them a circuit in terms of these parameters, provided you also pass the values
1429
/// separately.
1430
///
1431
/// This is the atom of :class:`.ParameterExpression`, and is itself an expression.  The numeric
1432
/// value of a parameter need not be fixed while the circuit is being defined.
1433
///
1434
/// Examples:
1435
///
1436
///     Construct a variable-rotation X gate using circuit parameters.
1437
///
1438
///     .. plot::
1439
///         :alt: Circuit diagram output by the previous code.
1440
///         :include-source:
1441
///
1442
///         from qiskit.circuit import QuantumCircuit, Parameter
1443
///
1444
///         # create the parameter
1445
///         phi = Parameter("phi")
1446
///         qc = QuantumCircuit(1)
1447
///
1448
///         # parameterize the rotation
1449
///         qc.rx(phi, 0)
1450
///         qc.draw("mpl")
1451
///
1452
///         # bind the parameters after circuit to create a bound circuit
1453
///         bc = qc.assign_parameters({phi: 3.14})
1454
///         bc.measure_all()
1455
///         bc.draw("mpl")
1456
#[pyclass(
1457
    sequence,
1458
    subclass,
1459
    module="qiskit._accelerate.circuit",
1460
    extends=PyParameterExpression,
1461
    name="Parameter",
1462
    from_py_object
×
1463
)]
1464
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)]
1465
pub struct PyParameter {
1466
    pub symbol: Symbol,
1467
}
1468

1469
impl Hash for PyParameter {
1470
    fn hash<H: Hasher>(&self, state: &mut H) {
4,556,406✔
1471
        self.symbol.hash(state);
4,556,406✔
1472
    }
4,556,406✔
1473
}
1474

1475
// This needs to be implemented manually, since PyO3 does not provide this conversion
1476
// for subclasses.
1477
impl<'py> IntoPyObject<'py> for PyParameter {
1478
    type Target = PyParameter;
1479
    type Output = Bound<'py, Self::Target>;
1480
    type Error = PyErr;
1481

1482
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
9,762✔
1483
        let symbol = &self.symbol;
9,762✔
1484
        let symbol_expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
9,762✔
1485
        let expr = ParameterExpression::from_symbol_expr(symbol_expr);
9,762✔
1486
        let py_expr = PyParameterExpression::from(expr);
9,762✔
1487

1488
        Ok(Py::new(py, (self, py_expr))?.into_bound(py))
9,762✔
1489
    }
9,762✔
1490
}
1491

1492
impl PyParameter {
1493
    /// Get a Python class initialization from a symbol.
1494
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
4,689,274✔
1495
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
4,689,274✔
1496

1497
        let py_parameter = Self { symbol };
4,689,274✔
1498
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
4,689,274✔
1499

1500
        PyClassInitializer::from(py_expr).add_subclass(py_parameter)
4,689,274✔
1501
    }
4,689,274✔
1502

1503
    /// Get a reference to the underlying symbol.
1504
    pub fn symbol(&self) -> &Symbol {
4,557,138✔
1505
        &self.symbol
4,557,138✔
1506
    }
4,557,138✔
1507
}
1508

1509
#[pymethods]
×
1510
impl PyParameter {
1511
    /// Args:
1512
    ///     name: name of the parameter, used for visual representation. This can
1513
    ///         be any Unicode string, e.g. ``"Ï•"``.
1514
    ///     uuid: For advanced usage only.  Override the UUID of this parameter, in order to make it
1515
    ///         compare equal to some other parameter object.  By default, two parameters with the
1516
    ///         same name do not compare equal to help catch shadowing bugs when two circuits
1517
    ///         containing the same named parameters are spurious combined.  Setting the ``uuid``
1518
    ///         field when creating two parameters to the same thing (along with the same name)
1519
    ///         allows them to be equal.  This is useful during serialization and deserialization.
1520
    #[new]
1521
    #[pyo3(signature = (name, uuid=None))]
1522
    fn py_new(
88,386✔
1523
        py: Python<'_>,
88,386✔
1524
        name: String,
88,386✔
1525
        uuid: Option<Py<PyAny>>,
88,386✔
1526
    ) -> PyResult<PyClassInitializer<Self>> {
88,386✔
1527
        let uuid = uuid_from_py(py, uuid)?;
88,386✔
1528
        let symbol = Symbol::new(name.as_str(), uuid, None);
88,386✔
1529
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
88,386✔
1530

1531
        let py_parameter = Self { symbol };
88,386✔
1532
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
88,386✔
1533

1534
        Ok(PyClassInitializer::from(py_expr).add_subclass(py_parameter))
88,386✔
1535
    }
88,386✔
1536

1537
    /// Returns the name of the :class:`.Parameter`.
1538
    #[getter]
1539
    fn name<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
21,552✔
1540
        PyString::new(py, &self.symbol.repr(false))
21,552✔
1541
    }
21,552✔
1542

1543
    /// Returns the :class:`~uuid.UUID` of the :class:`Parameter`.
1544
    ///
1545
    /// In advanced use cases, this property can be passed to the
1546
    /// :class:`.Parameter` constructor to produce an instance that compares
1547
    /// equal to another instance.
1548
    #[getter]
1549
    fn uuid(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
1,002✔
1550
        uuid_to_py(py, self.symbol.uuid)
1,002✔
1551
    }
1,002✔
1552

1553
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
64✔
1554
        let str = format!("Parameter({})", self.symbol.repr(false),);
64✔
1555
        PyString::new(py, str.as_str())
64✔
1556
    }
64✔
1557

1558
    pub fn __getnewargs__(&self) -> (String, u128) {
25,244✔
1559
        (self.symbol.repr(false), self.symbol.uuid.as_u128())
25,244✔
1560
    }
25,244✔
1561

1562
    pub fn __getstate__(&self) -> (String, u128) {
25,244✔
1563
        (self.symbol.repr(false), self.symbol.uuid.as_u128())
25,244✔
1564
    }
25,244✔
1565

1566
    pub fn __setstate__(&mut self, state: (String, u128)) {
36✔
1567
        let name = state.0.as_str();
36✔
1568
        let uuid = Uuid::from_u128(state.1);
36✔
1569
        let symbol = Symbol::new(name, Some(uuid), None);
36✔
1570
        self.symbol = symbol;
36✔
1571
    }
36✔
1572

1573
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
4✔
1574
        // Parameter is immutable. Note that this **cannot** be deferred to the parent class
1575
        // since PyO3 would then always return the parent type.
1576
        slf
4✔
1577
    }
4✔
1578

1579
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
156,608✔
1580
        // Everything inside a Parameter is immutable. Note that this **cannot** be deferred to the
1581
        // parent class since PyO3 would then always return the parent type.
1582
        slf
156,608✔
1583
    }
156,608✔
1584

1585
    #[pyo3(name = "subs")]
1586
    #[pyo3(signature = (parameter_map, allow_unknown_parameters=false))]
1587
    pub fn py_subs<'py>(
416✔
1588
        &self,
416✔
1589
        py: Python<'py>,
416✔
1590
        parameter_map: HashMap<PyParameter, Bound<'py, PyAny>>,
416✔
1591
        allow_unknown_parameters: bool,
416✔
1592
    ) -> PyResult<Bound<'py, PyAny>> {
416✔
1593
        // We implement this method on this class, and do not defer to the parent, such
1594
        // that x.subs({x: y}) remains a Parameter, and is not upgraded to an expression.
1595
        // Also this should be faster than going via ParameterExpression, which constructs
1596
        // intermediary HashMaps we don't need here.
1597
        match parameter_map.get(self) {
416✔
1598
            None => {
1599
                if allow_unknown_parameters {
4✔
1600
                    self.clone().into_bound_py_any(py)
2✔
1601
                } else {
1602
                    Err(CircuitError::new_err(
2✔
1603
                        "Cannot bind parameters not present in parameter.",
2✔
1604
                    ))
2✔
1605
                }
1606
            }
1607
            Some(replacement) => {
412✔
1608
                if allow_unknown_parameters || parameter_map.len() == 1 {
412✔
1609
                    Ok(replacement.clone())
412✔
1610
                } else {
1611
                    Err(CircuitError::new_err(
×
1612
                        "Cannot bind parameters not present in parameter.",
×
1613
                    ))
×
1614
                }
1615
            }
1616
        }
1617
    }
416✔
1618

1619
    #[pyo3(name = "bind")]
1620
    #[pyo3(signature = (parameter_values, allow_unknown_parameters=false))]
1621
    pub fn py_bind<'py>(
146✔
1622
        &self,
146✔
1623
        py: Python<'py>,
146✔
1624
        parameter_values: HashMap<PyParameter, Bound<'py, PyAny>>,
146✔
1625
        allow_unknown_parameters: bool,
146✔
1626
    ) -> PyResult<Bound<'py, PyAny>> {
146✔
1627
        // Returns PyAny to cover Parameter and ParameterExpression(value).
1628
        match parameter_values.get(self) {
146✔
1629
            None => {
1630
                if allow_unknown_parameters {
×
1631
                    self.clone().into_bound_py_any(py)
×
1632
                } else {
1633
                    Err(CircuitError::new_err(
×
1634
                        "Cannot bind parameters not present in parameter.",
×
1635
                    ))
×
1636
                }
1637
            }
1638
            Some(replacement) => {
146✔
1639
                if allow_unknown_parameters || parameter_values.len() == 1 {
146✔
1640
                    let expr = PyParameterExpression::extract_coerce(replacement.as_borrowed())?;
146✔
1641
                    if let SymbolExpr::Value(_) = &expr.inner.expr {
146✔
1642
                        expr.clone().into_bound_py_any(py)
146✔
1643
                    } else {
1644
                        Err(PyValueError::new_err("Invalid binding value."))
×
1645
                    }
1646
                } else {
1647
                    Err(CircuitError::new_err(
×
1648
                        "Cannot bind parameters not present in parameter.",
×
1649
                    ))
×
1650
                }
1651
            }
1652
        }
1653
    }
146✔
1654

1655
    #[pyo3(name = "bind_all")]
1656
    #[pyo3(signature = (values, *))]
1657
    pub fn py_bind_all<'py>(
4✔
1658
        slf_: Bound<'py, Self>,
4✔
1659
        values: Bound<'py, PyAny>,
4✔
1660
    ) -> PyResult<Bound<'py, PyAny>> {
4✔
1661
        values.get_item(slf_)
4✔
1662
    }
4✔
1663

1664
    #[pyo3(name = "assign")]
1665
    pub fn py_assign<'py>(
408✔
1666
        &self,
408✔
1667
        py: Python<'py>,
408✔
1668
        parameter: PyParameter,
408✔
1669
        value: &Bound<'py, PyAny>,
408✔
1670
    ) -> PyResult<Bound<'py, PyAny>> {
408✔
1671
        if value.cast::<PyParameterExpression>().is_ok() {
408✔
1672
            let map = [(parameter, value.clone())].into_iter().collect();
408✔
1673
            self.py_subs(py, map, false)
408✔
1674
        } else if value.extract::<Value>().is_ok() {
×
1675
            let map = [(parameter, value.clone())].into_iter().collect();
×
1676
            self.py_bind(py, map, false)
×
1677
        } else {
1678
            Err(PyValueError::new_err(
×
1679
                "Unexpected value in assign: {replacement:?}",
×
1680
            ))
×
1681
        }
1682
    }
408✔
1683
}
1684

1685
/// An element of a :class:`.ParameterVector`.
1686
///
1687
/// .. note::
1688
///     There is very little reason to ever construct this class directly.  Objects of this type are
1689
///     automatically constructed efficiently as part of creating a :class:`.ParameterVector`.
1690
#[pyclass(
1691
    sequence,
1692
    subclass,
1693
    module="qiskit._accelerate.circuit",
1694
    extends=PyParameter,
1695
    name="ParameterVectorElement",
1696
    from_py_object
2,034✔
1697
)]
1698
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
1699
pub struct PyParameterVectorElement {
1700
    pub symbol: Symbol,
1701
}
1702

1703
impl<'py> IntoPyObject<'py> for PyParameterVectorElement {
1704
    type Target = PyParameterVectorElement;
1705
    type Output = Bound<'py, Self::Target>;
1706
    type Error = PyErr;
1707

1708
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
2,074✔
1709
        let symbol = &self.symbol;
2,074✔
1710
        let py_param = PyParameter::from_symbol(symbol.clone());
2,074✔
1711
        let py_element = py_param.add_subclass(self);
2,074✔
1712

1713
        Ok(Py::new(py, py_element)?.into_bound(py))
2,074✔
1714
    }
2,074✔
1715
}
1716

1717
impl PyParameterVectorElement {
1718
    pub fn symbol(&self) -> &Symbol {
16,838✔
1719
        &self.symbol
16,838✔
1720
    }
16,838✔
1721

1722
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
4,564,478✔
1723
        let py_element = Self {
4,564,478✔
1724
            symbol: symbol.clone(),
4,564,478✔
1725
        };
4,564,478✔
1726
        let py_parameter = PyParameter::from_symbol(symbol);
4,564,478✔
1727

1728
        py_parameter.add_subclass(py_element)
4,564,478✔
1729
    }
4,564,478✔
1730
}
1731

1732
#[pymethods]
×
1733
impl PyParameterVectorElement {
1734
    #[new]
1735
    #[pyo3(signature = (vector, index, uuid=None))]
1736
    pub fn py_new(
32,168✔
1737
        py: Python<'_>,
32,168✔
1738
        vector: Py<PyAny>,
32,168✔
1739
        index: u32,
32,168✔
1740
        uuid: Option<Py<PyAny>>,
32,168✔
1741
    ) -> PyResult<PyClassInitializer<Self>> {
32,168✔
1742
        let vector_name = vector.getattr(py, "name")?.extract::<String>(py)?;
32,168✔
1743
        let uuid = uuid_from_py(py, uuid)?.unwrap_or(Uuid::new_v4());
32,168✔
1744

1745
        let symbol = Symbol::py_new(
32,168✔
1746
            &vector_name,
32,168✔
1747
            Some(uuid.as_u128()),
32,168✔
1748
            Some(index),
32,168✔
1749
            Some(vector.clone_ref(py)),
32,168✔
1750
        )?;
×
1751

1752
        let py_parameter = PyParameter::from_symbol(symbol.clone());
32,168✔
1753
        let py_element = Self { symbol };
32,168✔
1754

1755
        Ok(py_parameter.add_subclass(py_element))
32,168✔
1756
    }
32,168✔
1757

1758
    pub fn __getnewargs__(&self, py: Python) -> PyResult<(Py<PyAny>, u32, Option<Py<PyAny>>)> {
6,222✔
1759
        let vector = self
6,222✔
1760
            .symbol
6,222✔
1761
            .vector
6,222✔
1762
            .clone()
6,222✔
1763
            .expect("vector element should have a vector");
6,222✔
1764
        let index = self
6,222✔
1765
            .symbol
6,222✔
1766
            .index
6,222✔
1767
            .expect("vector element should have an index");
6,222✔
1768
        let uuid = uuid_to_py(py, self.symbol.uuid)?;
6,222✔
1769
        Ok((vector, index, Some(uuid)))
6,222✔
1770
    }
6,222✔
1771

1772
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
48✔
1773
        let str = format!("ParameterVectorElement({})", self.symbol.repr(false),);
48✔
1774
        PyString::new(py, str.as_str())
48✔
1775
    }
48✔
1776

1777
    pub fn __getstate__(&self, py: Python) -> PyResult<(Py<PyAny>, u32, Option<Py<PyAny>>)> {
4,216✔
1778
        self.__getnewargs__(py)
4,216✔
1779
    }
4,216✔
1780

1781
    pub fn __setstate__(
2,006✔
1782
        &mut self,
2,006✔
1783
        py: Python,
2,006✔
1784
        state: (Py<PyAny>, u32, Option<Py<PyAny>>),
2,006✔
1785
    ) -> PyResult<()> {
2,006✔
1786
        let vector = state.0;
2,006✔
1787
        let index = state.1;
2,006✔
1788
        let vector_name = vector.getattr(py, "name")?.extract::<String>(py)?;
2,006✔
1789
        let uuid = uuid_from_py(py, state.2)?.map(|id| id.as_u128());
2,006✔
1790
        self.symbol = Symbol::py_new(&vector_name, uuid, Some(index), Some(vector))?;
2,006✔
1791
        Ok(())
2,006✔
1792
    }
2,006✔
1793

1794
    /// Get the index of this element in the parent vector.
1795
    #[getter]
1796
    pub fn index(&self) -> u32 {
72✔
1797
        self.symbol
72✔
1798
            .index
72✔
1799
            .expect("A vector element should have an index")
72✔
1800
    }
72✔
1801

1802
    /// Get the parent vector instance.
1803
    #[getter]
1804
    pub fn vector(&self) -> Py<PyAny> {
140✔
1805
        self.symbol
140✔
1806
            .clone()
140✔
1807
            .vector
140✔
1808
            .expect("A vector element should have a vector")
140✔
1809
    }
140✔
1810

1811
    /// For backward compatibility only. This should not be used and we ought to update those
1812
    /// usages!
1813
    #[getter]
1814
    pub fn _vector(&self) -> Py<PyAny> {
×
1815
        self.vector()
×
1816
    }
×
1817

1818
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
2✔
1819
        // ParameterVectorElement is immutable.
1820
        slf
2✔
1821
    }
2✔
1822

1823
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
10,990✔
1824
        // Everything a ParameterVectorElement contains is immutable.
1825
        slf
10,990✔
1826
    }
10,990✔
1827
}
1828

1829
/// Try to extract a Uuid from a Python object, which could be a Python UUID or int.
1830
fn uuid_from_py(py: Python<'_>, uuid: Option<Py<PyAny>>) -> PyResult<Option<Uuid>> {
122,560✔
1831
    if let Some(val) = uuid {
122,560✔
1832
        // construct from u128
1833
        let as_u128 = if let Ok(as_u128) = val.extract::<u128>(py) {
34,432✔
1834
            as_u128
36✔
1835
        // construct from Python UUID type
1836
        } else if val.bind(py).is_exact_instance(UUID.get_bound(py)) {
34,396✔
1837
            val.getattr(py, "int")?.extract::<u128>(py)?
34,396✔
1838
        // invalid format
1839
        } else {
1840
            return Err(PyTypeError::new_err("not a UUID!"));
×
1841
        };
1842
        Ok(Some(Uuid::from_u128(as_u128)))
34,432✔
1843
    } else {
1844
        Ok(None)
88,128✔
1845
    }
1846
}
122,560✔
1847

1848
/// Convert a Rust Uuid object to a Python UUID object.
1849
fn uuid_to_py(py: Python<'_>, uuid: Uuid) -> PyResult<Py<PyAny>> {
7,224✔
1850
    let uuid = uuid.as_u128();
7,224✔
1851
    let kwargs = [("int", uuid)].into_py_dict(py)?;
7,224✔
1852
    Ok(UUID.get_bound(py).call((), Some(&kwargs))?.unbind())
7,224✔
1853
}
7,224✔
1854

1855
/// Extract a [Symbol] for a Python object, which could either be a Parameter or a
1856
/// ParameterVectorElement.
1857
fn symbol_from_py_parameter(param: &Bound<'_, PyAny>) -> PyResult<Symbol> {
74✔
1858
    if let Ok(element) = param.extract::<PyParameterVectorElement>() {
74✔
1859
        Ok(element.symbol.clone())
×
1860
    } else if let Ok(parameter) = param.extract::<PyParameter>() {
74✔
1861
        Ok(parameter.symbol.clone())
74✔
1862
    } else {
1863
        Err(PyValueError::new_err("Could not extract parameter"))
×
1864
    }
1865
}
74✔
1866

1867
/// A singular parameter value used for QPY serialization. This covers anything
1868
/// but a [PyParameterExpression], which is represented by [None] in the serialization.
1869
#[derive(IntoPyObject, FromPyObject, Clone, Debug)]
1870
pub enum ParameterValueType {
1871
    Int(i64),
1872
    Float(f64),
1873
    Complex(Complex64),
1874
    Parameter(PyParameter),
1875
    VectorElement(PyParameterVectorElement),
1876
}
1877

1878
impl ParameterValueType {
1879
    fn from_symbol(symbol: Symbol) -> Self {
12,202✔
1880
        if symbol.index.is_some() {
12,202✔
1881
            Self::VectorElement(PyParameterVectorElement { symbol })
2,256✔
1882
        } else {
1883
            Self::Parameter(PyParameter { symbol })
9,946✔
1884
        }
1885
    }
12,202✔
1886
    fn extract_from_expr(expr: &SymbolExpr) -> Option<ParameterValueType> {
19,244✔
1887
        if let Some(value) = expr.eval(true) {
19,244✔
1888
            match value {
7,804✔
1889
                Value::Int(i) => Some(ParameterValueType::Int(i)),
140✔
1890
                Value::Real(r) => Some(ParameterValueType::Float(r)),
7,656✔
1891
                Value::Complex(c) => Some(ParameterValueType::Complex(c)),
8✔
1892
            }
1893
        } else if let SymbolExpr::Symbol(symbol) = expr {
11,440✔
1894
            Some(Self::from_symbol(symbol.as_ref().clone()))
10,022✔
1895
        } else {
1896
            // ParameterExpressions have the value None, as they must be constructed
1897
            None
1,418✔
1898
        }
1899
    }
19,244✔
1900
}
1901

1902
impl From<ParameterValueType> for ParameterExpression {
1903
    fn from(value: ParameterValueType) -> Self {
4,570✔
1904
        match value {
4,570✔
1905
            ParameterValueType::Parameter(param) => {
2,020✔
1906
                let expr = SymbolExpr::Symbol(Arc::new(param.symbol));
2,020✔
1907
                Self::from_symbol_expr(expr)
2,020✔
1908
            }
1909
            ParameterValueType::VectorElement(param) => {
×
1910
                let expr = SymbolExpr::Symbol(Arc::new(param.symbol));
×
1911
                Self::from_symbol_expr(expr)
×
1912
            }
1913
            ParameterValueType::Int(i) => {
2,326✔
1914
                let expr = SymbolExpr::Value(Value::Int(i));
2,326✔
1915
                Self::from_symbol_expr(expr)
2,326✔
1916
            }
1917
            ParameterValueType::Float(f) => {
224✔
1918
                let expr = SymbolExpr::Value(Value::Real(f));
224✔
1919
                Self::from_symbol_expr(expr)
224✔
1920
            }
1921
            ParameterValueType::Complex(c) => {
×
1922
                let expr = SymbolExpr::Value(Value::Complex(c));
×
1923
                Self::from_symbol_expr(expr)
×
1924
            }
1925
        }
1926
    }
4,570✔
1927
}
1928

1929
#[pyclass(module = "qiskit._accelerate.circuit", from_py_object)]
×
1930
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
1931
#[repr(u8)]
1932
pub enum OpCode {
1933
    ADD = 0,
1934
    SUB = 1,
1935
    MUL = 2,
1936
    DIV = 3,
1937
    POW = 4,
1938
    SIN = 5,
1939
    COS = 6,
1940
    TAN = 7,
1941
    ASIN = 8,
1942
    ACOS = 9,
1943
    EXP = 10,
1944
    LOG = 11,
1945
    SIGN = 12,
1946
    GRAD = 13, // for backward compatibility, unused in Rust's ParameterExpression
1947
    CONJ = 14,
1948
    SUBSTITUTE = 15, // for backward compatibility, unused in Rust's ParameterExpression
1949
    ABS = 16,
1950
    ATAN = 17,
1951
    RSUB = 18,
1952
    RDIV = 19,
1953
    RPOW = 20,
1954
}
1955

1956
impl From<OpCode> for u8 {
1957
    fn from(value: OpCode) -> Self {
×
1958
        value as u8
×
1959
    }
×
1960
}
1961

1962
unsafe impl ::bytemuck::CheckedBitPattern for OpCode {
1963
    type Bits = u8;
1964

1965
    #[inline(always)]
1966
    fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
26✔
1967
        *bits <= 20
26✔
1968
    }
26✔
1969
}
1970

1971
unsafe impl ::bytemuck::NoUninit for OpCode {}
1972

1973
impl OpCode {
1974
    pub fn from_u8(value: u8) -> PyResult<OpCode> {
×
1975
        Ok(bytemuck::checked::cast::<u8, OpCode>(value))
×
1976
    }
×
1977
}
1978

1979
#[pymethods]
×
1980
impl OpCode {
1981
    #[new]
1982
    fn py_new(value: u8) -> PyResult<Self> {
26✔
1983
        let code: OpCode = ::bytemuck::checked::try_cast(value)
26✔
1984
            .map_err(|_| ParameterError::InvalidU8ToOpCode(value))?;
26✔
1985
        Ok(code)
26✔
1986
    }
26✔
1987

1988
    fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
356✔
1989
        if let Ok(code) = other.cast::<OpCode>() {
356✔
1990
            *code.borrow() == *self
356✔
1991
        } else {
1992
            false
×
1993
        }
1994
    }
356✔
1995

1996
    fn __hash__(&self) -> u8 {
3,016✔
1997
        *self as u8
3,016✔
1998
    }
3,016✔
1999

2000
    fn __getnewargs__(&self) -> (u8,) {
170✔
2001
        (*self as u8,)
170✔
2002
    }
170✔
2003
}
2004

2005
// enum for QPY replay
2006
#[pyclass(sequence, module = "qiskit._accelerate.circuit", from_py_object)]
×
2007
#[derive(Clone, Debug)]
2008
pub struct OPReplay {
2009
    pub op: OpCode,
2010
    pub lhs: Option<ParameterValueType>,
2011
    pub rhs: Option<ParameterValueType>,
2012
}
2013

2014
#[pymethods]
×
2015
impl OPReplay {
2016
    #[new]
2017
    pub fn py_new(
4,018✔
2018
        op: OpCode,
4,018✔
2019
        lhs: Option<ParameterValueType>,
4,018✔
2020
        rhs: Option<ParameterValueType>,
4,018✔
2021
    ) -> OPReplay {
4,018✔
2022
        OPReplay { op, lhs, rhs }
4,018✔
2023
    }
4,018✔
2024

2025
    #[getter]
2026
    fn op(&self) -> OpCode {
1,560✔
2027
        self.op
1,560✔
2028
    }
1,560✔
2029

2030
    #[getter]
2031
    fn lhs(&self) -> Option<ParameterValueType> {
754✔
2032
        self.lhs.clone()
754✔
2033
    }
754✔
2034

2035
    #[getter]
2036
    fn rhs(&self) -> Option<ParameterValueType> {
754✔
2037
        self.rhs.clone()
754✔
2038
    }
754✔
2039

2040
    fn __getnewargs__(
13,414✔
2041
        &self,
13,414✔
2042
    ) -> (
13,414✔
2043
        OpCode,
13,414✔
2044
        Option<ParameterValueType>,
13,414✔
2045
        Option<ParameterValueType>,
13,414✔
2046
    ) {
13,414✔
2047
        (self.op, self.lhs.clone(), self.rhs.clone())
13,414✔
2048
    }
13,414✔
2049
}
2050

2051
/// Internal helper. Extract one part of the expression tree, keeping the name map up to date.
2052
///
2053
/// Example: Given expr1 + expr2, each being [PyParameterExpression], we need the ability to
2054
/// extract one of the expressions with the proper name map.
2055
///
2056
/// Args:
2057
///     - joint_parameter_expr: The full expression, e.g. expr1 + expr2.
2058
///     - sub_expr: The sub expression, on whose symbols we restrict the name map.
2059
fn filter_name_map(
19,210✔
2060
    sub_expr: &SymbolExpr,
19,210✔
2061
    name_map: &HashMap<String, Symbol>,
19,210✔
2062
) -> ParameterExpression {
19,210✔
2063
    let sub_symbols: HashSet<&Symbol> = sub_expr.iter_symbols().collect();
19,210✔
2064
    let restricted_name_map: HashMap<String, Symbol> = name_map
19,210✔
2065
        .iter()
19,210✔
2066
        .filter(|(_, symbol)| sub_symbols.contains(*symbol))
32,546✔
2067
        .map(|(name, symbol)| (name.clone(), symbol.clone()))
19,210✔
2068
        .collect();
19,210✔
2069

2070
    ParameterExpression {
19,210✔
2071
        expr: sub_expr.clone(),
19,210✔
2072
        name_map: restricted_name_map,
19,210✔
2073
    }
19,210✔
2074
}
19,210✔
2075

2076
fn qpy_replay_inner(
27,968✔
2077
    expr: &ParameterExpression,
27,968✔
2078
    name_map: &HashMap<String, Symbol>,
27,968✔
2079
    replay: &mut Vec<OPReplay>,
27,968✔
2080
    unused: &mut IndexSet<Symbol, ahash::RandomState>,
27,968✔
2081
) {
27,968✔
2082
    match &expr.expr {
27,968✔
2083
        // This function is written under the assumption that the top-level expression involves an
2084
        // operation, since `OPReplay` items correspond to operations that own their operands.
2085
        SymbolExpr::Value(_) => (),
7,804✔
2086
        SymbolExpr::Symbol(sym) => {
9,988✔
2087
            unused.swap_remove(sym.as_ref());
9,988✔
2088
        }
9,988✔
2089
        SymbolExpr::Unary { op, expr } => {
1,142✔
2090
            let op = match op {
1,142✔
2091
                symbol_expr::UnaryOp::Abs => OpCode::ABS,
6✔
2092
                symbol_expr::UnaryOp::Acos => OpCode::ACOS,
6✔
2093
                symbol_expr::UnaryOp::Asin => OpCode::ASIN,
6✔
2094
                symbol_expr::UnaryOp::Atan => OpCode::ATAN,
6✔
2095
                symbol_expr::UnaryOp::Conj => OpCode::CONJ,
6✔
2096
                symbol_expr::UnaryOp::Cos => OpCode::COS,
6✔
2097
                symbol_expr::UnaryOp::Exp => OpCode::EXP,
6✔
2098
                symbol_expr::UnaryOp::Log => OpCode::LOG,
6✔
2099
                symbol_expr::UnaryOp::Neg => OpCode::MUL,
1,066✔
2100
                symbol_expr::UnaryOp::Sign => OpCode::SIGN,
4✔
2101
                symbol_expr::UnaryOp::Sin => OpCode::SIN,
18✔
2102
                symbol_expr::UnaryOp::Tan => OpCode::TAN,
6✔
2103
            };
2104
            // TODO filter shouldn't be necessary for unary ops
2105
            let lhs = filter_name_map(expr, name_map);
1,142✔
2106

2107
            // recurse on the instruction
2108
            qpy_replay_inner(&lhs, name_map, replay, unused);
1,142✔
2109

2110
            let lhs_value = ParameterValueType::extract_from_expr(expr);
1,142✔
2111

2112
            // MUL is special: we implement ``neg`` as multiplication by -1
2113
            if let OpCode::MUL = &op {
1,142✔
2114
                replay.push(OPReplay {
1,066✔
2115
                    op,
1,066✔
2116
                    lhs: lhs_value,
1,066✔
2117
                    rhs: Some(ParameterValueType::Int(-1)),
1,066✔
2118
                });
1,066✔
2119
            } else {
1,066✔
2120
                replay.push(OPReplay {
76✔
2121
                    op,
76✔
2122
                    lhs: lhs_value,
76✔
2123
                    rhs: None,
76✔
2124
                });
76✔
2125
            }
76✔
2126
        }
2127
        SymbolExpr::Binary { op, lhs, rhs } => {
9,034✔
2128
            let lhs_value = ParameterValueType::extract_from_expr(lhs);
9,034✔
2129
            let rhs_value = ParameterValueType::extract_from_expr(rhs);
9,034✔
2130

2131
            // recurse on the parameter expressions
2132
            let lhs = filter_name_map(lhs, name_map);
9,034✔
2133
            let rhs = filter_name_map(rhs, name_map);
9,034✔
2134
            qpy_replay_inner(&lhs, name_map, replay, unused);
9,034✔
2135
            qpy_replay_inner(&rhs, name_map, replay, unused);
9,034✔
2136

2137
            // add the expression to the replay
2138
            match lhs_value {
8,776✔
2139
                Some(ParameterValueType::Parameter(_))
2140
                | Some(ParameterValueType::VectorElement(_)) => {
2141
                    // For non-commutative operations (SUB, DIV, POW): if LHS is a Parameter and RHS is
2142
                    // an expression (None), we need to use reverse operations (RSUB, RDIV, RPOW)
2143
                    let op = match op {
1,132✔
2144
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
812✔
2145
                        symbol_expr::BinaryOp::Sub => {
2146
                            // If RHS is None (an expression), use RSUB
2147
                            if rhs_value.is_none() {
288✔
2148
                                OpCode::RSUB
×
2149
                            } else {
2150
                                OpCode::SUB
288✔
2151
                            }
2152
                        }
2153
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
4✔
2154
                        symbol_expr::BinaryOp::Div => {
2155
                            // If RHS is None (an expression), use RDIV
2156
                            if rhs_value.is_none() {
×
2157
                                OpCode::RDIV
×
2158
                            } else {
2159
                                OpCode::DIV
×
2160
                            }
2161
                        }
2162
                        symbol_expr::BinaryOp::Pow => {
2163
                            // If RHS is None (an expression), use RPOW
2164
                            if rhs_value.is_none() {
28✔
2165
                                OpCode::RPOW
6✔
2166
                            } else {
2167
                                OpCode::POW
22✔
2168
                            }
2169
                        }
2170
                    };
2171
                    if op == OpCode::RPOW || op == OpCode::RDIV || op == OpCode::RSUB {
1,132✔
2172
                        // For reverse operations, swap lhs and rhs (Python's sympify will swap again to get correct order)
6✔
2173
                        replay.push(OPReplay {
6✔
2174
                            op,
6✔
2175
                            lhs: rhs_value,
6✔
2176
                            rhs: lhs_value,
6✔
2177
                        });
6✔
2178
                    } else {
1,126✔
2179
                        replay.push(OPReplay {
1,126✔
2180
                            op,
1,126✔
2181
                            lhs: lhs_value,
1,126✔
2182
                            rhs: rhs_value,
1,126✔
2183
                        });
1,126✔
2184
                    }
1,126✔
2185
                }
2186
                None => {
2187
                    // When LHS is an expression (None), use normal operations
2188
                    let op = match op {
258✔
2189
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
30✔
2190
                        symbol_expr::BinaryOp::Sub => OpCode::SUB,
132✔
2191
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
82✔
2192
                        symbol_expr::BinaryOp::Div => OpCode::DIV,
12✔
2193
                        symbol_expr::BinaryOp::Pow => OpCode::POW,
2✔
2194
                    };
2195
                    replay.push(OPReplay {
258✔
2196
                        op,
258✔
2197
                        lhs: lhs_value,
258✔
2198
                        rhs: rhs_value,
258✔
2199
                    });
258✔
2200
                }
2201
                _ => {
2202
                    let op = match op {
7,644✔
2203
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
584✔
2204
                        symbol_expr::BinaryOp::Sub => OpCode::RSUB,
122✔
2205
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
6,928✔
2206
                        symbol_expr::BinaryOp::Div => OpCode::RDIV,
6✔
2207
                        symbol_expr::BinaryOp::Pow => OpCode::RPOW,
4✔
2208
                    };
2209
                    if let OpCode::ADD | OpCode::MUL = op {
7,644✔
2210
                        replay.push(OPReplay {
7,512✔
2211
                            op,
7,512✔
2212
                            lhs: lhs_value,
7,512✔
2213
                            rhs: rhs_value,
7,512✔
2214
                        });
7,512✔
2215
                    } else {
7,512✔
2216
                        // this covers RSUB, RDIV, RPOW, hence we swap lhs and rhs
132✔
2217
                        replay.push(OPReplay {
132✔
2218
                            op,
132✔
2219
                            lhs: rhs_value,
132✔
2220
                            rhs: lhs_value,
132✔
2221
                        });
132✔
2222
                    }
132✔
2223
                }
2224
            }
2225
        }
2226
    }
2227
}
27,968✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc