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

Qiskit / qiskit / 20079556946

09 Dec 2025 09:46PM UTC coverage: 88.316% (-0.006%) from 88.322%
20079556946

push

github

web-flow
Fix ``QkObs`` docs (#15429)

- code block didn't include last statement
- prefer QkExitCode over plain int

96183 of 108908 relevant lines covered (88.32%)

1208688.78 hits per line

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

82.3
/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 http://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 std::sync::Arc;
14

15
use hashbrown::hash_map::Entry;
16
use hashbrown::{HashMap, HashSet};
17
use num_complex::Complex64;
18
use pyo3::exceptions::{PyRuntimeError, PyTypeError, PyValueError, PyZeroDivisionError};
19
use pyo3::types::{IntoPyDict, PyComplex, PyFloat, PyInt, PyNotImplemented, PySet, PyString};
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 {
3,478✔
65
        match value {
3,478✔
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()),
582✔
76
            ParameterError::InvalidValue => PyValueError::new_err(value.to_string()),
40✔
77
            _ => PyRuntimeError::new_err(value.to_string()),
4✔
78
        }
79
    }
3,478✔
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 {
17,942✔
104
        self.expr.eq(&other.expr)
17,942✔
105
    }
17,942✔
106
}
107

108
impl Eq for ParameterExpression {}
109

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

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

135
// This needs to be implemented manually, because PyO3 does not provide built-in
136
// conversions for the subclasses of ParameterExpression in Python. Specifically
137
// the Python classes Parameter and ParameterVector are subclasses of
138
// ParameterExpression and the default trait impl would not handle the specialization
139
// there.
140
impl<'py> IntoPyObject<'py> for ParameterExpression {
141
    type Target = PyParameterExpression;
142
    type Output = Bound<'py, Self::Target>;
143
    type Error = PyErr;
144

145
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
×
146
        let expr = PyParameterExpression::from(self.clone());
×
147
        expr.into_pyobject(py)
×
148
    }
×
149
}
150

151
/// Lookup for which operations are binary (i.e. require two operands).
152
static BINARY_OPS: [OpCode; 8] = [
153
    // a HashSet would be better but requires unstable features
154
    OpCode::ADD,
155
    OpCode::SUB,
156
    OpCode::MUL,
157
    OpCode::DIV,
158
    OpCode::POW,
159
    OpCode::RSUB,
160
    OpCode::RDIV,
161
    OpCode::RPOW,
162
];
163

164
impl ParameterExpression {
165
    /// Initialize with an existing [SymbolExpr] and its valid name map.
166
    ///
167
    /// Caution: The caller **guarantees** that ``name_map`` is consistent with ``expr``.
168
    /// If uncertain, call [Self::from_symbol_expr], which automatically builds the correct name map.
169
    pub fn new(expr: SymbolExpr, name_map: HashMap<String, Symbol>) -> Self {
2,509,824✔
170
        Self { expr, name_map }
2,509,824✔
171
    }
2,509,824✔
172

173
    /// Construct from a [Symbol].
174
    pub fn from_symbol(symbol: Symbol) -> Self {
77,714✔
175
        Self {
77,714✔
176
            expr: SymbolExpr::Symbol(Arc::new(symbol.clone())),
77,714✔
177
            name_map: [(symbol.repr(false), symbol)].into(),
77,714✔
178
        }
77,714✔
179
    }
77,714✔
180

181
    /// Try casting to a [Symbol].
182
    ///
183
    /// This only succeeds if the underlying expression is, in fact, only a symbol.
184
    pub fn try_to_symbol(&self) -> Result<Symbol, ParameterError> {
272,508✔
185
        if let SymbolExpr::Symbol(symbol) = &self.expr {
272,508✔
186
            Ok(symbol.as_ref().clone())
262,840✔
187
        } else {
188
            Err(ParameterError::NotASymbol)
9,668✔
189
        }
190
    }
272,508✔
191

192
    /// Try casting to a [Symbol], returning a reference.
193
    ///
194
    /// This only succeeds if the underlying expression is, in fact, only a symbol.
195
    pub fn try_to_symbol_ref(&self) -> Result<&Symbol, ParameterError> {
×
196
        if let SymbolExpr::Symbol(symbol) = &self.expr {
×
197
            Ok(symbol.as_ref())
×
198
        } else {
199
            Err(ParameterError::NotASymbol)
×
200
        }
201
    }
×
202

203
    /// Try casting to a [Value].
204
    ///
205
    /// Attempt to evaluate the expression recursively and return a [Value] if fully bound.
206
    ///
207
    /// # Arguments
208
    ///
209
    /// * strict - If ``true``, only allow returning a value if all symbols are bound. If
210
    ///   ``false``, allow casting expressions to values, even though symbols might still exist.
211
    ///   For example, ``0 * x`` will return ``0`` for ``strict=false`` and otherwise return
212
    ///   an error.
213
    pub fn try_to_value(&self, strict: bool) -> Result<Value, ParameterError> {
9,651,708✔
214
        if strict && !self.name_map.is_empty() {
9,651,708✔
215
            let free_symbols = self.expr.iter_symbols().cloned().collect();
73,136✔
216
            return Err(ParameterError::UnboundParameters(free_symbols));
73,136✔
217
        }
9,578,572✔
218

219
        match self.expr.eval(true) {
9,578,572✔
220
            Some(value) => {
412,780✔
221
                // we try to restrict complex to real, if possible
222
                if let Value::Complex(c) = value {
412,780✔
223
                    if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON).contains(&c.im)
41,738✔
224
                    {
225
                        return Ok(Value::Real(c.re));
×
226
                    }
41,738✔
227
                }
371,042✔
228
                Ok(value)
412,780✔
229
            }
230
            None => {
231
                let free_symbols = self.expr.iter_symbols().cloned().collect();
9,165,792✔
232
                Err(ParameterError::UnboundParameters(free_symbols))
9,165,792✔
233
            }
234
        }
235
    }
9,651,708✔
236

237
    /// Construct from a [SymbolExpr].
238
    ///
239
    /// This populates the name map with the symbols in the expression.
240
    pub fn from_symbol_expr(expr: SymbolExpr) -> Self {
4,766,828✔
241
        let name_map = expr.name_map();
4,766,828✔
242
        Self { expr, name_map }
4,766,828✔
243
    }
4,766,828✔
244

245
    /// Initialize from an f64.
246
    pub fn from_f64(value: f64) -> Self {
28,294✔
247
        Self {
28,294✔
248
            expr: SymbolExpr::Value(Value::Real(value)),
28,294✔
249
            name_map: HashMap::new(),
28,294✔
250
        }
28,294✔
251
    }
28,294✔
252

253
    /// Load from a sequence of [OPReplay]s. Used in serialization.
254
    pub fn from_qpy(replay: &[OPReplay]) -> Result<Self, ParameterError> {
×
255
        // the stack contains the latest lhs and rhs values
256
        let mut stack: Vec<ParameterExpression> = Vec::new();
×
257

258
        for inst in replay.iter() {
×
259
            let OPReplay { op, lhs, rhs } = inst;
×
260

261
            // put the values on the stack, if they exist
262
            if let Some(value) = lhs {
×
263
                stack.push(value.clone().into());
×
264
            }
×
265
            if let Some(value) = rhs {
×
266
                stack.push(value.clone().into());
×
267
            }
×
268

269
            // if we need two operands, pop rhs from the stack
270
            let rhs = if BINARY_OPS.contains(op) {
×
271
                Some(stack.pop().expect("Pop from empty stack"))
×
272
            } else {
273
                None
×
274
            };
275

276
            // pop lhs from the stack, this we always need
277
            let lhs = stack.pop().expect("Pop from empty stack");
×
278

279
            // apply the operation and put the result onto the stack for the next replay
280
            let result: ParameterExpression = match op {
×
281
                OpCode::ADD => lhs.add(&rhs.unwrap())?,
×
282
                OpCode::MUL => lhs.mul(&rhs.unwrap())?,
×
283
                OpCode::SUB => lhs.sub(&rhs.unwrap())?,
×
284
                OpCode::RSUB => rhs.unwrap().sub(&lhs)?,
×
285
                OpCode::POW => lhs.pow(&rhs.unwrap())?,
×
286
                OpCode::RPOW => rhs.unwrap().pow(&lhs)?,
×
287
                OpCode::DIV => lhs.div(&rhs.unwrap())?,
×
288
                OpCode::RDIV => rhs.unwrap().div(&lhs)?,
×
289
                OpCode::ABS => lhs.abs(),
×
290
                OpCode::SIN => lhs.sin(),
×
291
                OpCode::ASIN => lhs.asin(),
×
292
                OpCode::COS => lhs.cos(),
×
293
                OpCode::ACOS => lhs.acos(),
×
294
                OpCode::TAN => lhs.tan(),
×
295
                OpCode::ATAN => lhs.atan(),
×
296
                OpCode::CONJ => lhs.conjugate(),
×
297
                OpCode::LOG => lhs.log(),
×
298
                OpCode::EXP => lhs.exp(),
×
299
                OpCode::SIGN => lhs.sign(),
×
300
                OpCode::GRAD | OpCode::SUBSTITUTE => {
301
                    panic!("GRAD and SUBSTITUTE are not supported.")
×
302
                }
303
            };
304
            stack.push(result);
×
305
        }
306

307
        // once we're done, just return the last element in the stack
308
        Ok(stack
×
309
            .pop()
×
310
            .expect("Invalid QPY replay encountered during deserialization: empty OPReplay."))
×
311
    }
×
312

313
    pub fn iter_symbols(&self) -> impl Iterator<Item = &Symbol> + '_ {
325,108✔
314
        self.name_map.values()
325,108✔
315
    }
325,108✔
316

317
    /// Get the number of [Symbol]s in the expression.
318
    pub fn num_symbols(&self) -> usize {
6✔
319
        self.name_map.len()
6✔
320
    }
6✔
321

322
    /// Whether the expression represents a complex number. None if cannot be determined.
323
    pub fn is_complex(&self) -> Option<bool> {
23,136✔
324
        self.expr.is_complex()
23,136✔
325
    }
23,136✔
326

327
    /// Whether the expression represents a int. None if cannot be determined.
328
    pub fn is_int(&self) -> Option<bool> {
85,668✔
329
        self.expr.is_int()
85,668✔
330
    }
85,668✔
331

332
    /// Add an expression; ``self + rhs``.
333
    pub fn add(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,391,354✔
334
        let name_map = self.merged_name_map(rhs)?;
2,391,354✔
335
        Ok(Self {
2,391,352✔
336
            expr: &self.expr + &rhs.expr,
2,391,352✔
337
            name_map,
2,391,352✔
338
        })
2,391,352✔
339
    }
2,391,354✔
340

341
    /// Multiply with an expression; ``self * rhs``.
342
    pub fn mul(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,415,318✔
343
        let name_map = self.merged_name_map(rhs)?;
2,415,318✔
344
        Ok(Self {
2,415,316✔
345
            expr: &self.expr * &rhs.expr,
2,415,316✔
346
            name_map,
2,415,316✔
347
        })
2,415,316✔
348
    }
2,415,318✔
349

350
    /// Subtract another expression; ``self - rhs``.
351
    pub fn sub(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
24,084✔
352
        let name_map = self.merged_name_map(rhs)?;
24,084✔
353
        Ok(Self {
24,082✔
354
            expr: &self.expr - &rhs.expr,
24,082✔
355
            name_map,
24,082✔
356
        })
24,082✔
357
    }
24,084✔
358

359
    /// Divide by another expression; ``self / rhs``.
360
    pub fn div(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
13,916✔
361
        if rhs.expr.is_zero() {
13,916✔
362
            return Err(ParameterError::ZeroDivisionError);
1,012✔
363
        }
12,904✔
364

365
        let name_map = self.merged_name_map(rhs)?;
12,904✔
366
        Ok(Self {
12,902✔
367
            expr: &self.expr / &rhs.expr,
12,902✔
368
            name_map,
12,902✔
369
        })
12,902✔
370
    }
13,916✔
371

372
    /// Raise this expression to a power; ``self ^ rhs``.
373
    pub fn pow(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
3,016✔
374
        let name_map = self.merged_name_map(rhs)?;
3,016✔
375
        Ok(Self {
3,016✔
376
            expr: self.expr.pow(&rhs.expr),
3,016✔
377
            name_map,
3,016✔
378
        })
3,016✔
379
    }
3,016✔
380

381
    /// Apply the sine to this expression; ``sin(self)``.
382
    pub fn sin(&self) -> Self {
362✔
383
        Self {
362✔
384
            expr: self.expr.sin(),
362✔
385
            name_map: self.name_map.clone(),
362✔
386
        }
362✔
387
    }
362✔
388

389
    /// Apply the cosine to this expression; ``cos(self)``.
390
    pub fn cos(&self) -> Self {
352✔
391
        Self {
352✔
392
            expr: self.expr.cos(),
352✔
393
            name_map: self.name_map.clone(),
352✔
394
        }
352✔
395
    }
352✔
396

397
    /// Apply the tangent to this expression; ``tan(self)``.
398
    pub fn tan(&self) -> Self {
352✔
399
        Self {
352✔
400
            expr: self.expr.tan(),
352✔
401
            name_map: self.name_map.clone(),
352✔
402
        }
352✔
403
    }
352✔
404

405
    /// Apply the arcsine to this expression; ``asin(self)``.
406
    pub fn asin(&self) -> Self {
100✔
407
        Self {
100✔
408
            expr: self.expr.asin(),
100✔
409
            name_map: self.name_map.clone(),
100✔
410
        }
100✔
411
    }
100✔
412

413
    /// Apply the arccosine to this expression; ``acos(self)``.
414
    pub fn acos(&self) -> Self {
100✔
415
        Self {
100✔
416
            expr: self.expr.acos(),
100✔
417
            name_map: self.name_map.clone(),
100✔
418
        }
100✔
419
    }
100✔
420

421
    /// Apply the arctangent to this expression; ``atan(self)``.
422
    pub fn atan(&self) -> Self {
100✔
423
        Self {
100✔
424
            expr: self.expr.atan(),
100✔
425
            name_map: self.name_map.clone(),
100✔
426
        }
100✔
427
    }
100✔
428

429
    /// Exponentiate this expression; ``exp(self)``.
430
    pub fn exp(&self) -> Self {
350✔
431
        Self {
350✔
432
            expr: self.expr.exp(),
350✔
433
            name_map: self.name_map.clone(),
350✔
434
        }
350✔
435
    }
350✔
436

437
    /// Take the (natural) logarithm of this expression; ``log(self)``.
438
    pub fn log(&self) -> Self {
266✔
439
        Self {
266✔
440
            expr: self.expr.log(),
266✔
441
            name_map: self.name_map.clone(),
266✔
442
        }
266✔
443
    }
266✔
444

445
    /// Take the absolute value of this expression; ``|self|``.
446
    pub fn abs(&self) -> Self {
374✔
447
        Self {
374✔
448
            expr: self.expr.abs(),
374✔
449
            name_map: self.name_map.clone(),
374✔
450
        }
374✔
451
    }
374✔
452

453
    /// Return the sign of this expression; ``sign(self)``.
454
    pub fn sign(&self) -> Self {
10✔
455
        Self {
10✔
456
            expr: self.expr.sign(),
10✔
457
            name_map: self.name_map.clone(),
10✔
458
        }
10✔
459
    }
10✔
460

461
    /// Complex conjugate the expression.
462
    pub fn conjugate(&self) -> Self {
1,832✔
463
        Self {
1,832✔
464
            expr: self.expr.conjugate(),
1,832✔
465
            name_map: self.name_map.clone(),
1,832✔
466
        }
1,832✔
467
    }
1,832✔
468

469
    /// negate the expression.
470
    pub fn neg(&self) -> Self {
×
471
        Self {
×
472
            expr: -&self.expr,
×
473
            name_map: self.name_map.clone(),
×
474
        }
×
475
    }
×
476

477
    /// Compute the derivative of the expression with respect to the provided symbol.
478
    ///
479
    /// Note that this keeps the name map unchanged. Meaning that computing the derivative
480
    /// of ``x`` will yield ``1`` but the expression still owns the symbol ``x``. This is
481
    /// done such that we can still bind the value ``x`` in an automated process.
482
    pub fn derivative(&self, param: &Symbol) -> Result<Self, ParameterError> {
74✔
483
        Ok(Self {
484
            expr: self
74✔
485
                .expr
74✔
486
                .derivative(param)
74✔
487
                .map_err(ParameterError::DerivativeNotSupported)?,
74✔
488
            name_map: self.name_map.clone(),
70✔
489
        })
490
    }
74✔
491

492
    /// Substitute symbols with [ParameterExpression]s.
493
    ///
494
    /// # Arguments
495
    ///
496
    /// * map - A hashmap with [Symbol] keys and [ParameterExpression]s to replace these
497
    ///   symbols with.
498
    /// * allow_unknown_parameters - If `false`, returns an error if any symbol in the
499
    ///   hashmap is not present in the expression. If `true`, unknown symbols are ignored.
500
    ///   Setting to `true` is slightly faster as it does not involve additional checks.
501
    ///
502
    /// # Returns
503
    ///
504
    /// * `Ok(Self)` - A parameter expression with the substituted expressions.
505
    /// * `Err(ParameterError)` - An error if the subtitution failed.
506
    pub fn subs(
241,952✔
507
        &self,
241,952✔
508
        map: &HashMap<Symbol, Self>,
241,952✔
509
        allow_unknown_parameters: bool,
241,952✔
510
    ) -> Result<Self, ParameterError> {
241,952✔
511
        // Build the outgoing name map. In the process we check for any duplicates.
512
        let mut name_map: HashMap<String, Symbol> = HashMap::new();
241,952✔
513
        let mut symbol_map: HashMap<Symbol, SymbolExpr> = HashMap::new();
241,952✔
514

515
        // If we don't allow for unknown parameters, check if there are any.
516
        if !allow_unknown_parameters {
241,952✔
517
            let existing: HashSet<&Symbol> = self.name_map.values().collect();
36,714✔
518
            let to_replace: HashSet<&Symbol> = map.keys().collect();
36,714✔
519
            let mut difference = to_replace.difference(&existing).peekable();
36,714✔
520

521
            if difference.peek().is_some() {
36,714✔
522
                let different_symbols = difference.map(|s| (**s).clone()).collect();
2✔
523
                return Err(ParameterError::UnknownParameters(different_symbols));
2✔
524
            }
36,712✔
525
        }
205,238✔
526

527
        for (name, symbol) in self.name_map.iter() {
251,240✔
528
            // check if the symbol will get replaced
529
            if let Some(replacement) = map.get(symbol) {
251,240✔
530
                // If yes, update the name_map. This also checks for duplicates.
531
                for (replacement_name, replacement_symbol) in replacement.name_map.iter() {
39,204✔
532
                    if let Some(duplicate) = name_map.get(replacement_name) {
39,204✔
533
                        // If a symbol with the same name already exists, check whether it is
534
                        // the same symbol (fine) or a different symbol with the same name (conflict)!
535
                        if duplicate != replacement_symbol {
36✔
536
                            return Err(ParameterError::NameConflict);
×
537
                        }
36✔
538
                    } else {
39,168✔
539
                        // SAFETY: We know the key does not exist yet.
39,168✔
540
                        unsafe {
39,168✔
541
                            name_map.insert_unique_unchecked(
39,168✔
542
                                replacement_name.clone(),
39,168✔
543
                                replacement_symbol.clone(),
39,168✔
544
                            )
39,168✔
545
                        };
39,168✔
546
                    }
39,168✔
547
                }
548

549
                // If we got until here, there were no duplicates, so we are safe to
550
                // add this symbol to the internal replacement map.
551
                symbol_map.insert(symbol.clone(), replacement.expr.clone());
38,100✔
552
            } else {
553
                // no replacement for this symbol, carry on
554
                match name_map.entry(name.clone()) {
213,140✔
555
                    Entry::Occupied(duplicate) => {
18✔
556
                        if duplicate.get() != symbol {
18✔
557
                            return Err(ParameterError::NameConflict);
2✔
558
                        }
16✔
559
                    }
560
                    Entry::Vacant(e) => {
213,122✔
561
                        e.insert(symbol.clone());
213,122✔
562
                    }
213,122✔
563
                }
564
            }
565
        }
566

567
        let res = self.expr.subs(&symbol_map);
241,948✔
568
        Ok(Self {
241,948✔
569
            expr: res,
241,948✔
570
            name_map,
241,948✔
571
        })
241,948✔
572
    }
241,952✔
573

574
    /// Bind symbols to values.
575
    ///
576
    /// # Arguments
577
    ///
578
    /// * map - A hashmap with [Symbol] keys and [Value]s to replace these
579
    ///   symbols with.
580
    /// * allow_unknown_parameter - If `false`, returns an error if any symbol in the
581
    ///   hashmap is not present in the expression. If `true`, unknown symbols are ignored.
582
    ///   Setting to `true` is slightly faster as it does not involve additional checks.
583
    ///
584
    /// # Returns
585
    ///
586
    /// * `Ok(Self)` - A parameter expression with the bound symbols.
587
    /// * `Err(ParameterError)` - An error if binding failed.
588
    pub fn bind(
414,136✔
589
        &self,
414,136✔
590
        map: &HashMap<&Symbol, Value>,
414,136✔
591
        allow_unknown_parameters: bool,
414,136✔
592
    ) -> Result<Self, ParameterError> {
414,136✔
593
        // The set of symbols we will bind. Used twice, hence pre-computed here.
594
        let bind_symbols: HashSet<&Symbol> = map.keys().cloned().collect();
414,136✔
595

596
        // If we don't allow for unknown parameters, check if there are any.
597
        if !allow_unknown_parameters {
414,136✔
598
            let existing: HashSet<&Symbol> = self.name_map.values().collect();
208,810✔
599
            let mut difference = bind_symbols.difference(&existing).peekable();
208,810✔
600

601
            if difference.peek().is_some() {
208,810✔
602
                let different_symbols = difference.map(|s| (**s).clone()).collect();
×
603
                return Err(ParameterError::UnknownParameters(different_symbols));
×
604
            }
208,810✔
605
        }
205,326✔
606

607
        // bind the symbol expression and then check the outcome for inf/nan, or numeric values
608
        let bound_expr = self.expr.bind(map);
414,136✔
609
        let bound = match bound_expr.eval(true) {
414,136✔
610
            Some(v) => match &v {
411,434✔
611
                Value::Real(r) => {
340,780✔
612
                    if r.is_infinite() {
340,780✔
613
                        Err(ParameterError::BindingInf)
1,828✔
614
                    } else if r.is_nan() {
338,952✔
615
                        Err(ParameterError::BindingNaN)
×
616
                    } else {
617
                        Ok(SymbolExpr::Value(v))
338,952✔
618
                    }
619
                }
620
                Value::Int(_) => Ok(SymbolExpr::Value(v)),
27,386✔
621
                Value::Complex(c) => {
43,268✔
622
                    if c.re.is_infinite() || c.im.is_infinite() {
43,268✔
623
                        Err(ParameterError::BindingInf)
×
624
                    } else if c.re.is_nan() || c.im.is_nan() {
43,268✔
625
                        Err(ParameterError::BindingNaN)
×
626
                    } else if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON)
43,268✔
627
                        .contains(&c.im)
43,268✔
628
                    {
629
                        Ok(SymbolExpr::Value(Value::Real(c.re)))
1,728✔
630
                    } else {
631
                        Ok(SymbolExpr::Value(v))
41,540✔
632
                    }
633
                }
634
            },
635
            None => Ok(bound_expr),
2,702✔
636
        }?;
1,828✔
637

638
        // update the name map by removing the bound parameters
639
        let bound_name_map: HashMap<String, Symbol> = self
412,308✔
640
            .name_map
412,308✔
641
            .iter()
412,308✔
642
            .filter(|(_, symbol)| !bind_symbols.contains(symbol))
4,711,356✔
643
            .map(|(name, symbol)| (name.clone(), symbol.clone()))
412,308✔
644
            .collect();
412,308✔
645

646
        Ok(Self {
412,308✔
647
            expr: bound,
412,308✔
648
            name_map: bound_name_map,
412,308✔
649
        })
412,308✔
650
    }
414,136✔
651

652
    /// Merge name maps.
653
    ///
654
    /// # Arguments
655
    ///
656
    /// * `other` - The other parameter expression whose symbols we add to self.
657
    ///
658
    /// # Returns
659
    ///
660
    /// * `Ok(HashMap<String, Symbol>)` - The merged name map.
661
    /// * `Err(ParameterError)` - An error if there was a name conflict.
662
    fn merged_name_map(&self, other: &Self) -> Result<HashMap<String, Symbol>, ParameterError> {
4,846,676✔
663
        let mut merged = self.name_map.clone();
4,846,676✔
664
        for (name, param) in other.name_map.iter() {
7,051,940✔
665
            match merged.get(name) {
7,050,980✔
666
                Some(existing_param) => {
3,990,500✔
667
                    if param != existing_param {
3,990,500✔
668
                        return Err(ParameterError::NameConflict);
8✔
669
                    }
3,990,492✔
670
                }
671
                None => {
3,060,480✔
672
                    // SAFETY: We ensured the key is unique
3,060,480✔
673
                    let _ = unsafe { merged.insert_unique_unchecked(name.clone(), param.clone()) };
3,060,480✔
674
                }
3,060,480✔
675
            }
676
        }
677
        Ok(merged)
4,846,668✔
678
    }
4,846,676✔
679
}
680

681
/// A parameter expression.
682
///
683
/// This is backed by Qiskit's symbolic expression engine and a cache
684
/// for the parameters inside the expression.
685
#[pyclass(
686
    subclass,
687
    module = "qiskit._accelerate.circuit",
688
    name = "ParameterExpression"
689
)]
690
#[derive(Clone, Debug)]
691
pub struct PyParameterExpression {
692
    pub inner: ParameterExpression,
693
}
694

695
impl Default for PyParameterExpression {
696
    /// The default constructor returns zero.
697
    fn default() -> Self {
×
698
        Self {
×
699
            inner: ParameterExpression::default(),
×
700
        }
×
701
    }
×
702
}
703

704
impl fmt::Display for PyParameterExpression {
705
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336,816✔
706
        self.inner.fmt(f)
336,816✔
707
    }
336,816✔
708
}
709

710
impl From<ParameterExpression> for PyParameterExpression {
711
    fn from(value: ParameterExpression) -> Self {
12,335,898✔
712
        Self { inner: value }
12,335,898✔
713
    }
12,335,898✔
714
}
715

716
impl PyParameterExpression {
717
    /// Attempt to extract a `PyParameterExpression` from a bound `PyAny`.
718
    ///
719
    /// This will try to coerce to the strictest data type:
720
    /// Int - Real - Complex - PyParameterVectorElement - PyParameter - PyParameterExpression.
721
    ///
722
    /// # Arguments:
723
    ///
724
    /// * ob - The bound `PyAny` to extract from.
725
    ///
726
    /// # Returns
727
    ///
728
    /// * `Ok(Self)` - The extracted expression.
729
    /// * `Err(PyResult)` - An error if extraction to all above types failed.
730
    pub fn extract_coerce(ob: Borrowed<PyAny>) -> PyResult<Self> {
4,960,592✔
731
        if let Ok(i) = ob.cast::<PyInt>() {
4,960,592✔
732
            Ok(ParameterExpression::new(
134,224✔
733
                SymbolExpr::Value(Value::from(i.extract::<i64>()?)),
134,224✔
734
                HashMap::new(),
134,224✔
735
            )
736
            .into())
134,224✔
737
        } else if let Ok(r) = ob.cast::<PyFloat>() {
4,826,368✔
738
            let r: f64 = r.extract()?;
13,570✔
739
            if r.is_infinite() || r.is_nan() {
13,570✔
740
                return Err(ParameterError::InvalidValue.into());
40✔
741
            }
13,530✔
742
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(r)), HashMap::new()).into())
13,530✔
743
        } else if let Ok(c) = ob.cast::<PyComplex>() {
4,812,798✔
744
            let c: Complex64 = c.extract()?;
2,361,336✔
745
            if c.is_infinite() || c.is_nan() {
2,361,336✔
746
                return Err(ParameterError::InvalidValue.into());
×
747
            }
2,361,336✔
748
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(c)), HashMap::new()).into())
2,361,336✔
749
        } else if let Ok(element) = ob.cast::<PyParameterVectorElement>() {
2,451,462✔
750
            Ok(ParameterExpression::from_symbol(element.borrow().symbol.clone()).into())
50,858✔
751
        } else if let Ok(parameter) = ob.cast::<PyParameter>() {
2,400,604✔
752
            Ok(ParameterExpression::from_symbol(parameter.borrow().symbol.clone()).into())
14,384✔
753
        } else {
754
            ob.extract::<PyParameterExpression>().map_err(Into::into)
2,386,220✔
755
        }
756
    }
4,960,592✔
757

758
    pub fn coerce_into_py(&self, py: Python) -> PyResult<Py<PyAny>> {
32,510✔
759
        if let Ok(value) = self.inner.try_to_value(true) {
32,510✔
760
            match value {
14✔
761
                Value::Int(i) => Ok(PyInt::new(py, i).unbind().into_any()),
×
762
                Value::Real(r) => Ok(PyFloat::new(py, r).unbind().into_any()),
14✔
763
                Value::Complex(c) => Ok(PyComplex::from_complex_bound(py, c).unbind().into_any()),
×
764
            }
765
        } else if let Ok(symbol) = self.inner.try_to_symbol() {
32,496✔
766
            if symbol.index.is_some() {
22,828✔
767
                Ok(Py::new(py, PyParameterVectorElement::from_symbol(symbol))?.into_any())
5,964✔
768
            } else {
769
                Ok(Py::new(py, PyParameter::from_symbol(symbol))?.into_any())
16,864✔
770
            }
771
        } else {
772
            self.clone().into_py_any(py)
9,668✔
773
        }
774
    }
32,510✔
775
}
776

777
#[pymethods]
×
778
impl PyParameterExpression {
779
    /// This is a **strictly internal** constructor and **should not be used**.
780
    /// It is subject to arbitrary change in between Qiskit versions and cannot be relied on.
781
    /// Parameter expressions should always be constructed from applying operations on
782
    /// parameters, or by loading via QPY.
783
    ///
784
    /// The input values are allowed to be None for pickling purposes.
785
    #[new]
786
    #[pyo3(signature = (name_map=None, expr=None))]
787
    pub fn py_new(
70✔
788
        name_map: Option<HashMap<String, PyParameter>>,
70✔
789
        expr: Option<String>,
70✔
790
    ) -> PyResult<Self> {
70✔
791
        match (name_map, expr) {
70✔
792
            (None, None) => Ok(Self::default()),
×
793
            (Some(name_map), Some(expr)) => {
70✔
794
                // We first parse the expression and then update the symbols with the ones
795
                // the user provided. The replacement relies on the names to match.
796
                // This is hacky and we likely want a more reliably conversion from a SymPy object,
797
                // if we decide we want to continue supporting this.
798
                let expr = parse_expression(&expr)
70✔
799
                    .map_err(|_| PyRuntimeError::new_err("Failed parsing input expression"))?;
70✔
800
                let symbol_map: HashMap<String, Symbol> = name_map
70✔
801
                    .iter()
70✔
802
                    .map(|(string, param)| (string.clone(), param.symbol.clone()))
70✔
803
                    .collect();
70✔
804

805
                let replaced_expr = symbol_expr::replace_symbol(&expr, &symbol_map);
70✔
806

807
                let inner = ParameterExpression::new(replaced_expr, symbol_map);
70✔
808
                Ok(Self { inner })
70✔
809
            }
810
            _ => Err(PyValueError::new_err(
×
811
                "Pass either both a name_map and expr, or neither",
×
812
            )),
×
813
        }
814
    }
70✔
815

816
    #[allow(non_snake_case)]
817
    #[staticmethod]
818
    pub fn _Value(value: &Bound<PyAny>) -> PyResult<Self> {
34✔
819
        Self::extract_coerce(value.as_borrowed())
34✔
820
    }
34✔
821

822
    /// Check if the expression corresponds to a plain symbol.
823
    ///
824
    /// Returns:
825
    ///     ``True`` is this expression corresponds to a symbol, ``False`` otherwise.
826
    pub fn is_symbol(&self) -> bool {
480✔
827
        matches!(self.inner.expr, SymbolExpr::Symbol(_))
480✔
828
    }
480✔
829

830
    /// Cast this expression to a numeric value.
831
    ///
832
    /// Args:
833
    ///     strict: If ``True`` (default) this function raises an error if there are any
834
    ///         unbound symbols in the expression. If ``False``, this allows casting
835
    ///         if the expression represents a numeric value, regardless of unbound symbols.
836
    ///         For example ``(0 * Parameter("x"))`` is 0 but has the symbol ``x`` present.
837
    #[pyo3(signature = (strict=true))]
838
    pub fn numeric(&self, py: Python, strict: bool) -> PyResult<Py<PyAny>> {
47,702✔
839
        match self.inner.try_to_value(strict)? {
47,702✔
840
            Value::Real(r) => r.into_py_any(py),
21,302✔
841
            Value::Int(i) => i.into_py_any(py),
11,792✔
842
            Value::Complex(c) => c.into_py_any(py),
14,488✔
843
        }
844
    }
47,702✔
845

846
    /// Return a SymPy equivalent of this expression.
847
    ///
848
    /// Returns:
849
    ///     A SymPy equivalent of this expression.
850
    pub fn sympify(&self, py: Python) -> PyResult<Py<PyAny>> {
480✔
851
        let py_sympify = SYMPIFY_PARAMETER_EXPRESSION.get(py);
480✔
852
        py_sympify.call1(py, (self.clone(),))
480✔
853
    }
480✔
854

855
    /// The number of unbound parameters in the expression.
856
    ///
857
    /// This is equivalent to ``len(expr.parameters)`` but does not involve the overhead of creating
858
    /// a set and counting its length.
859
    #[getter]
860
    pub fn num_parameters(&self) -> usize {
6✔
861
        self.inner.num_symbols()
6✔
862
    }
6✔
863

864
    /// Get the parameters present in the expression.
865
    ///
866
    /// .. note::
867
    ///
868
    ///     Qiskit guarantees equality (via ``==``) of parameters retrieved from an expression
869
    ///     with the original :class:`.Parameter` objects used to create this expression,
870
    ///     but does **not guarantee** ``is`` comparisons to succeed.
871
    ///
872
    #[getter]
873
    pub fn parameters<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PySet>> {
135,928✔
874
        let py_parameters: Vec<Py<PyAny>> = self
135,928✔
875
            .inner
135,928✔
876
            .name_map
135,928✔
877
            .values()
135,928✔
878
            .map(|symbol| {
4,564,032✔
879
                match (&symbol.index, &symbol.vector) {
4,564,032✔
880
                    // if index and vector is set, it is an element
881
                    (Some(_index), Some(_vector)) => Ok(Py::new(
4,497,456✔
882
                        py,
4,497,456✔
883
                        PyParameterVectorElement::from_symbol(symbol.clone()),
4,497,456✔
884
                    )?
×
885
                    .into_any()),
4,497,456✔
886
                    // else, a normal parameter
887
                    _ => Ok(Py::new(py, PyParameter::from_symbol(symbol.clone()))?.into_any()),
66,576✔
888
                }
889
            })
4,564,032✔
890
            .collect::<PyResult<_>>()?;
135,928✔
891
        PySet::new(py, py_parameters)
135,928✔
892
    }
135,928✔
893

894
    /// Sine of the expression.
895
    #[inline]
896
    #[pyo3(name = "sin")]
897
    pub fn py_sin(&self) -> Self {
362✔
898
        self.inner.sin().into()
362✔
899
    }
362✔
900

901
    /// Cosine of the expression.
902
    #[inline]
903
    #[pyo3(name = "cos")]
904
    pub fn py_cos(&self) -> Self {
352✔
905
        self.inner.cos().into()
352✔
906
    }
352✔
907

908
    /// Tangent of the expression.
909
    #[inline]
910
    #[pyo3(name = "tan")]
911
    pub fn py_tan(&self) -> Self {
352✔
912
        self.inner.tan().into()
352✔
913
    }
352✔
914

915
    /// Arcsine of the expression.
916
    #[inline]
917
    pub fn arcsin(&self) -> Self {
100✔
918
        self.inner.asin().into()
100✔
919
    }
100✔
920

921
    /// Arccosine of the expression.
922
    #[inline]
923
    pub fn arccos(&self) -> Self {
100✔
924
        self.inner.acos().into()
100✔
925
    }
100✔
926

927
    /// Arctangent of the expression.
928
    #[inline]
929
    pub fn arctan(&self) -> Self {
100✔
930
        self.inner.atan().into()
100✔
931
    }
100✔
932

933
    /// Exponentiate the expression.
934
    #[inline]
935
    #[pyo3(name = "exp")]
936
    pub fn py_exp(&self) -> Self {
350✔
937
        self.inner.exp().into()
350✔
938
    }
350✔
939

940
    /// Take the natural logarithm of the expression.
941
    #[inline]
942
    #[pyo3(name = "log")]
943
    pub fn py_log(&self) -> Self {
266✔
944
        self.inner.log().into()
266✔
945
    }
266✔
946

947
    /// Take the absolute value of the expression.
948
    #[inline]
949
    #[pyo3(name = "abs")]
950
    pub fn py_abs(&self) -> Self {
12✔
951
        self.inner.abs().into()
12✔
952
    }
12✔
953

954
    /// Return the sign of the expression.
955
    #[inline]
956
    #[pyo3(name = "sign")]
957
    pub fn py_sign(&self) -> Self {
10✔
958
        self.inner.sign().into()
10✔
959
    }
10✔
960

961
    /// Return the complex conjugate of the expression.
962
    #[inline]
963
    #[pyo3(name = "conjugate")]
964
    pub fn py_conjugate(&self) -> Self {
1,832✔
965
        self.inner.conjugate().into()
1,832✔
966
    }
1,832✔
967

968
    /// Check whether the expression represents a real number.
969
    ///
970
    /// Note that this will return ``None`` if there are unbound parameters, in which case
971
    /// it cannot be determined whether the expression is real.
972
    #[inline]
973
    #[pyo3(name = "is_real")]
974
    pub fn py_is_real(&self) -> Option<bool> {
270✔
975
        self.inner.expr.is_real()
270✔
976
    }
270✔
977

978
    /// Return derivative of this expression with respect to the input parameter.
979
    ///
980
    /// Args:
981
    ///     param: The parameter with respect to which the derivative is calculated.
982
    ///
983
    /// Returns:
984
    ///     The derivative as either a constant numeric value or a symbolic
985
    ///     :class:`.ParameterExpression`.
986
    pub fn gradient(&self, param: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
74✔
987
        let symbol = symbol_from_py_parameter(param)?;
74✔
988
        let d_expr = self.inner.derivative(&symbol)?;
74✔
989

990
        // try converting to value and return as built-in numeric type
991
        match d_expr.try_to_value(false) {
70✔
992
            Ok(val) => match val {
20✔
993
                Value::Real(r) => r.into_py_any(param.py()),
18✔
994
                Value::Int(i) => i.into_py_any(param.py()),
2✔
995
                Value::Complex(c) => c.into_py_any(param.py()),
×
996
            },
997
            Err(_) => PyParameterExpression::from(d_expr).into_py_any(param.py()),
50✔
998
        }
999
    }
74✔
1000

1001
    /// Return all values in this equation.
1002
    pub fn _values(&self, py: Python) -> PyResult<Vec<Py<PyAny>>> {
78✔
1003
        self.inner
78✔
1004
            .expr
78✔
1005
            .values()
78✔
1006
            .iter()
78✔
1007
            .map(|val| match val {
78✔
1008
                Value::Real(r) => r.into_py_any(py),
34✔
1009
                Value::Int(i) => i.into_py_any(py),
10✔
1010
                Value::Complex(c) => c.into_py_any(py),
×
1011
            })
44✔
1012
            .collect()
78✔
1013
    }
78✔
1014

1015
    /// Returns a new expression with replacement parameters.
1016
    ///
1017
    /// Args:
1018
    ///     parameter_map: Mapping from :class:`.Parameter`\ s in ``self`` to the
1019
    ///         :class:`.ParameterExpression` instances with which they should be replaced.
1020
    ///     allow_unknown_parameters: If ``False``, raises an error if ``parameter_map``
1021
    ///         contains :class:`.Parameter`\ s in the keys outside those present in the expression.
1022
    ///         If ``True``, any such parameters are simply ignored.
1023
    ///
1024
    /// Raises:
1025
    ///     CircuitError:
1026
    ///         - If parameter_map contains parameters outside those in self.
1027
    ///         - If the replacement parameters in ``parameter_map`` would result in
1028
    ///           a name conflict in the generated expression.
1029
    ///
1030
    /// Returns:
1031
    ///     A new expression with the specified parameters replaced.
1032
    #[pyo3(name = "subs")]
1033
    #[pyo3(signature = (parameter_map, allow_unknown_parameters=false))]
1034
    pub fn py_subs(
228✔
1035
        &self,
228✔
1036
        parameter_map: HashMap<PyParameter, Self>,
228✔
1037
        allow_unknown_parameters: bool,
228✔
1038
    ) -> PyResult<Self> {
228✔
1039
        // reduce the map to a HashMap<Symbol, ParameterExpression>
1040
        let map = parameter_map
228✔
1041
            .into_iter()
228✔
1042
            .map(|(param, expr)| Ok((param.symbol, expr.inner)))
232✔
1043
            .collect::<PyResult<_>>()?;
228✔
1044

1045
        // apply to the inner expression
1046
        match self.inner.subs(&map, allow_unknown_parameters) {
228✔
1047
            Ok(subbed) => Ok(subbed.into()),
224✔
1048
            Err(e) => Err(e.into()),
4✔
1049
        }
1050
    }
228✔
1051

1052
    /// Binds the provided set of parameters to their corresponding values.
1053
    ///
1054
    /// Args:
1055
    ///     parameter_values: Mapping of :class:`.Parameter` instances to the numeric value to which
1056
    ///         they will be bound.
1057
    ///     allow_unknown_parameters: If ``False``, raises an error if ``parameter_values``
1058
    ///         contains :class:`.Parameter`\ s in the keys outside those present in the expression.
1059
    ///         If ``True``, any such parameters are simply ignored.
1060
    ///
1061
    /// Raises:
1062
    ///     CircuitError:
1063
    ///         - If parameter_values contains parameters outside those in self.
1064
    ///         - If a non-numeric value is passed in ``parameter_values``.
1065
    ///     ZeroDivisionError:
1066
    ///         - If binding the provided values requires division by zero.
1067
    ///
1068
    /// Returns:
1069
    ///     A new expression parameterized by any parameters which were not bound by
1070
    ///     ``parameter_values``.
1071
    #[pyo3(name = "bind")]
1072
    #[pyo3(signature = (parameter_values, allow_unknown_parameters=false))]
1073
    pub fn py_bind(
127,050✔
1074
        &self,
127,050✔
1075
        parameter_values: HashMap<PyParameter, Bound<PyAny>>,
127,050✔
1076
        allow_unknown_parameters: bool,
127,050✔
1077
    ) -> PyResult<Self> {
127,050✔
1078
        // reduce the map to a HashMap<Symbol, Value>
1079
        let map = parameter_values
127,050✔
1080
            .iter()
127,050✔
1081
            .map(|(param, value)| {
4,555,024✔
1082
                let value = value.extract()?;
4,555,024✔
1083
                Ok((param.symbol(), value))
4,555,024✔
1084
            })
4,555,024✔
1085
            .collect::<PyResult<_>>()?;
127,050✔
1086

1087
        // apply to the inner expression
1088
        match self.inner.bind(&map, allow_unknown_parameters) {
127,050✔
1089
            Ok(bound) => Ok(bound.into()),
125,222✔
1090
            Err(e) => Err(e.into()),
1,828✔
1091
        }
1092
    }
127,050✔
1093

1094
    /// Bind all of the parameters in ``self`` to numeric values in the dictionary, returning a
1095
    /// numeric value.
1096
    ///
1097
    /// This is a special case of :meth:`bind` which can reach higher performance.  It is no problem
1098
    /// for the ``values`` dictionary to contain parameters that are not used in this expression;
1099
    /// the expectation is that the same bindings dictionary will be fed to other expressions as
1100
    /// well.
1101
    ///
1102
    /// It is an error to call this method with a ``values`` dictionary that does not bind all of
1103
    /// the values, or to call this method with non-numeric values, but this is not explicitly
1104
    /// checked, since this method is intended for performance-sensitive use.  Passing an incorrect
1105
    /// dictionary may result in unexpected behavior.
1106
    ///
1107
    /// Unlike :meth:`bind`, this method will not raise an exception if non-finite floating-point
1108
    /// values are encountered.
1109
    ///
1110
    /// Args:
1111
    ///     values: mapping of parameters to numeric values.
1112
    #[pyo3(name = "bind_all")]
1113
    #[pyo3(signature = (values, *))]
1114
    pub fn py_bind_all(&self, values: Bound<PyAny>) -> PyResult<Value> {
4✔
1115
        let mut partial_map = HashMap::with_capacity(self.inner.name_map.len());
4✔
1116
        for symbol in self.inner.name_map.values() {
8✔
1117
            let py_parameter = symbol.clone().into_pyobject(values.py())?;
8✔
1118
            partial_map.insert(symbol, values.get_item(py_parameter)?.extract()?);
8✔
1119
        }
1120
        let bound = self.inner.expr.bind(&partial_map);
2✔
1121
        bound.eval(true).ok_or_else(|| {
2✔
1122
            PyTypeError::new_err(format!(
×
1123
                "binding did not produce a numeric quantity: {bound:?}"
×
1124
            ))
1125
        })
×
1126
    }
4✔
1127

1128
    /// Assign one parameter to a value, which can either be numeric or another parameter
1129
    /// expression.
1130
    ///
1131
    /// Args:
1132
    ///     parameter: A parameter in this expression whose value will be updated.
1133
    ///     value: The new value to bind to.
1134
    ///
1135
    /// Returns:
1136
    ///     A new expression parameterized by any parameters which were not bound by assignment.
1137
    #[pyo3(name = "assign")]
1138
    pub fn py_assign(&self, parameter: PyParameter, value: &Bound<PyAny>) -> PyResult<Self> {
174✔
1139
        if let Ok(expr) = value.cast::<Self>() {
174✔
1140
            let map = [(parameter, expr.borrow().clone())].into_iter().collect();
150✔
1141
            self.py_subs(map, false)
150✔
1142
        } else if value.extract::<Value>().is_ok() {
24✔
1143
            let map = [(parameter, value.clone())].into_iter().collect();
24✔
1144
            self.py_bind(map, false)
24✔
1145
        } else {
1146
            Err(PyValueError::new_err(
×
1147
                "Unexpected value in assign: {replacement:?}",
×
1148
            ))
×
1149
        }
1150
    }
174✔
1151

1152
    #[inline]
1153
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1154
        // ParameterExpression is immutable.
1155
        slf
×
1156
    }
×
1157

1158
    #[inline]
1159
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
1,606✔
1160
        // Everything a ParameterExpression contains is immutable.
1161
        slf
1,606✔
1162
    }
1,606✔
1163

1164
    pub fn __eq__(&self, rhs: &Bound<PyAny>) -> PyResult<bool> {
33,470✔
1165
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
33,470✔
1166
            match rhs.inner.expr {
33,458✔
1167
                SymbolExpr::Value(v) => match self.inner.try_to_value(false) {
2,732✔
1168
                    Ok(e) => Ok(e == v),
2,522✔
1169
                    Err(_) => Ok(false),
210✔
1170
                },
1171
                _ => Ok(self.inner.expr == rhs.inner.expr),
30,726✔
1172
            }
1173
        } else {
1174
            Ok(false)
12✔
1175
        }
1176
    }
33,470✔
1177

1178
    #[inline]
1179
    pub fn __abs__(&self) -> Self {
362✔
1180
        self.inner.abs().into()
362✔
1181
    }
362✔
1182

1183
    #[inline]
1184
    pub fn __pos__(&self) -> Self {
4✔
1185
        self.clone()
4✔
1186
    }
4✔
1187

1188
    pub fn __neg__(&self) -> Self {
664✔
1189
        Self {
664✔
1190
            inner: ParameterExpression::new(-&self.inner.expr, self.inner.name_map.clone()),
664✔
1191
        }
664✔
1192
    }
664✔
1193

1194
    pub fn __add__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
2,318,328✔
1195
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
2,318,328✔
1196
            Ok(self.inner.add(&rhs.inner)?.into())
2,318,316✔
1197
        } else {
1198
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1199
                "Unsupported data type for __add__",
12✔
1200
            ))
12✔
1201
        }
1202
    }
2,318,328✔
1203

1204
    pub fn __radd__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
64,000✔
1205
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
64,000✔
1206
            Ok(lhs.inner.add(&self.inner)?.into())
63,988✔
1207
        } else {
1208
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1209
                "Unsupported data type for __radd__",
12✔
1210
            ))
12✔
1211
        }
1212
    }
64,000✔
1213

1214
    pub fn __sub__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
18,462✔
1215
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
18,462✔
1216
            Ok(self.inner.sub(&rhs.inner)?.into())
18,450✔
1217
        } else {
1218
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1219
                "Unsupported data type for __sub__",
12✔
1220
            ))
12✔
1221
        }
1222
    }
18,462✔
1223

1224
    pub fn __rsub__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
4,366✔
1225
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
4,366✔
1226
            Ok(lhs.inner.sub(&self.inner)?.into())
4,354✔
1227
        } else {
1228
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1229
                "Unsupported data type for __rsub__",
12✔
1230
            ))
12✔
1231
        }
1232
    }
4,366✔
1233

1234
    pub fn __mul__<'py>(&self, rhs: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
2,408,100✔
1235
        let py = rhs.py();
2,408,100✔
1236
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
2,408,100✔
1237
            match self.inner.mul(&rhs.inner) {
2,400,744✔
1238
                Ok(result) => PyParameterExpression::from(result).into_bound_py_any(py),
2,400,742✔
1239
                Err(e) => Err(PyErr::from(e)),
2✔
1240
            }
1241
        } else {
1242
            PyNotImplemented::get(py).into_bound_py_any(py)
7,356✔
1243
        }
1244
    }
2,408,100✔
1245

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

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

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

1276
    pub fn __pow__(&self, rhs: &Bound<PyAny>, _modulo: Option<i32>) -> PyResult<Self> {
1,998✔
1277
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
1,998✔
1278
            Ok(self.inner.pow(&rhs.inner)?.into())
1,986✔
1279
        } else {
1280
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1281
                "Unsupported data type for __pow__",
12✔
1282
            ))
12✔
1283
        }
1284
    }
1,998✔
1285

1286
    pub fn __rpow__(&self, lhs: &Bound<PyAny>, _modulo: Option<i32>) -> PyResult<Self> {
1,042✔
1287
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
1,042✔
1288
            Ok(lhs.inner.pow(&self.inner)?.into())
1,030✔
1289
        } else {
1290
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1291
                "Unsupported data type for __rpow__",
12✔
1292
            ))
12✔
1293
        }
1294
    }
1,042✔
1295

1296
    pub fn __int__(&self) -> PyResult<i64> {
176✔
1297
        match self.inner.try_to_value(false)? {
176✔
1298
            Value::Complex(_) => Err(PyTypeError::new_err(
42✔
1299
                "Cannot cast complex parameter to int.",
42✔
1300
            )),
42✔
1301
            Value::Real(r) => {
90✔
1302
                let rounded = r.floor();
90✔
1303
                Ok(rounded as i64)
90✔
1304
            }
1305
            Value::Int(i) => Ok(i),
42✔
1306
        }
1307
    }
176✔
1308

1309
    pub fn __float__(&self) -> PyResult<f64> {
672✔
1310
        match self.inner.try_to_value(false)? {
672✔
1311
            Value::Complex(c) => {
44✔
1312
                if c.im.abs() > SYMEXPR_EPSILON {
44✔
1313
                    Err(PyTypeError::new_err(
44✔
1314
                        "Could not cast complex parameter expression to float.",
44✔
1315
                    ))
44✔
1316
                } else {
1317
                    Ok(c.re)
×
1318
                }
1319
            }
1320
            Value::Real(r) => Ok(r),
114✔
1321
            Value::Int(i) => Ok(i as f64),
56✔
1322
        }
1323
    }
672✔
1324

1325
    pub fn __complex__(&self) -> PyResult<Complex64> {
77,856✔
1326
        match self.inner.try_to_value(false)? {
77,856✔
1327
            Value::Complex(c) => Ok(c),
27,076✔
1328
            Value::Real(r) => Ok(Complex64::new(r, 0.)),
35,318✔
1329
            Value::Int(i) => Ok(Complex64::new(i as f64, 0.)),
15,460✔
1330
        }
1331
    }
77,856✔
1332

1333
    pub fn __str__(&self) -> String {
336,816✔
1334
        self.to_string()
336,816✔
1335
    }
336,816✔
1336

1337
    pub fn __hash__(&self, py: Python) -> PyResult<u64> {
9,164,960✔
1338
        match self.inner.try_to_value(false) {
9,164,960✔
1339
            // if a value, we promise to match the hash of the raw value!
1340
            Ok(value) => {
2✔
1341
                let py_hash = BUILTIN_HASH.get_bound(py);
2✔
1342
                match value {
2✔
1343
                    Value::Complex(c) => py_hash.call1((c,))?.extract::<u64>(),
×
1344
                    Value::Real(r) => py_hash.call1((r,))?.extract::<u64>(),
2✔
1345
                    Value::Int(i) => py_hash.call1((i,))?.extract::<u64>(),
×
1346
                }
1347
            }
1348
            Err(_) => {
1349
                let mut hasher = DefaultHasher::new();
9,164,958✔
1350
                self.inner.expr.string_id().hash(&mut hasher);
9,164,958✔
1351
                Ok(hasher.finish())
9,164,958✔
1352
            }
1353
        }
1354
    }
9,164,960✔
1355

1356
    fn __getstate__(&self) -> PyResult<(Vec<OPReplay>, Option<ParameterValueType>)> {
7,884✔
1357
        // We distinguish in two cases:
1358
        //  (a) This is indeed an expression which can be rebuild from the QPY replay. This means
1359
        //      the replay is *not empty* and it contains all symbols.
1360
        //  (b) This expression is in fact only a Value or a Symbol. In this case, the QPY replay
1361
        //      will be empty and we instead pass a `ParameterValueType` for reconstruction.
1362
        let qpy = self._qpy_replay()?;
7,884✔
1363
        if !qpy.is_empty() {
7,884✔
1364
            Ok((qpy, None))
7,884✔
1365
        } else {
1366
            let value = ParameterValueType::extract_from_expr(&self.inner.expr);
×
1367
            if value.is_none() {
×
1368
                Err(PyValueError::new_err(format!(
×
1369
                    "Failed to serialize the parameter expression: {self:?}"
×
1370
                )))
×
1371
            } else {
1372
                Ok((qpy, value))
×
1373
            }
1374
        }
1375
    }
7,884✔
1376

1377
    fn __setstate__(&mut self, state: (Vec<OPReplay>, Option<ParameterValueType>)) -> PyResult<()> {
×
1378
        // if there a replay, load from the replay
1379
        if !state.0.is_empty() {
×
1380
            let from_qpy = ParameterExpression::from_qpy(&state.0)?;
×
1381
            self.inner = from_qpy;
×
1382
        // otherwise, load from the ParameterValueType
1383
        } else if let Some(value) = state.1 {
×
1384
            let expr = ParameterExpression::from(value);
×
1385
            self.inner = expr;
×
1386
        } else {
×
1387
            return Err(PyValueError::new_err(
×
1388
                "Failed to read QPY replay or extract value.",
×
1389
            ));
×
1390
        }
1391
        Ok(())
×
1392
    }
×
1393

1394
    #[getter]
1395
    fn _qpy_replay(&self) -> PyResult<Vec<OPReplay>> {
8,186✔
1396
        let mut replay = Vec::new();
8,186✔
1397
        qpy_replay(&self.inner, &self.inner.name_map, &mut replay);
8,186✔
1398
        Ok(replay)
8,186✔
1399
    }
8,186✔
1400
}
1401

1402
/// A compile-time symbolic parameter.
1403
///
1404
/// The value of a :class:`.Parameter` must be entirely determined before a circuit begins execution.
1405
/// Typically this will mean that you should supply values for all :class:`.Parameter`\ s in a
1406
/// circuit using :meth:`.QuantumCircuit.assign_parameters`, though certain hardware vendors may
1407
/// allow you to give them a circuit in terms of these parameters, provided you also pass the values
1408
/// separately.
1409
///
1410
/// This is the atom of :class:`.ParameterExpression`, and is itself an expression.  The numeric
1411
/// value of a parameter need not be fixed while the circuit is being defined.
1412
///
1413
/// Examples:
1414
///
1415
///     Construct a variable-rotation X gate using circuit parameters.
1416
///
1417
///     .. plot::
1418
///         :alt: Circuit diagram output by the previous code.
1419
///         :include-source:
1420
///
1421
///         from qiskit.circuit import QuantumCircuit, Parameter
1422
///
1423
///         # create the parameter
1424
///         phi = Parameter("phi")
1425
///         qc = QuantumCircuit(1)
1426
///
1427
///         # parameterize the rotation
1428
///         qc.rx(phi, 0)
1429
///         qc.draw("mpl")
1430
///
1431
///         # bind the parameters after circuit to create a bound circuit
1432
///         bc = qc.assign_parameters({phi: 3.14})
1433
///         bc.measure_all()
1434
///         bc.draw("mpl")
1435
#[pyclass(sequence, subclass, module="qiskit._accelerate.circuit", extends=PyParameterExpression, name="Parameter")]
1436
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)]
1437
pub struct PyParameter {
1438
    symbol: Symbol,
1439
}
1440

1441
impl Hash for PyParameter {
1442
    fn hash<H: Hasher>(&self, state: &mut H) {
4,556,382✔
1443
        self.symbol.hash(state);
4,556,382✔
1444
    }
4,556,382✔
1445
}
1446

1447
// This needs to be implemented manually, since PyO3 does not provide this conversion
1448
// for subclasses.
1449
impl<'py> IntoPyObject<'py> for PyParameter {
1450
    type Target = PyParameter;
1451
    type Output = Bound<'py, Self::Target>;
1452
    type Error = PyErr;
1453

1454
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
9,136✔
1455
        let symbol = &self.symbol;
9,136✔
1456
        let symbol_expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
9,136✔
1457
        let expr = ParameterExpression::from_symbol_expr(symbol_expr);
9,136✔
1458
        let py_expr = PyParameterExpression::from(expr);
9,136✔
1459

1460
        Ok(Py::new(py, (self, py_expr))?.into_bound(py))
9,136✔
1461
    }
9,136✔
1462
}
1463

1464
impl PyParameter {
1465
    /// Get a Python class initialization from a symbol.
1466
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
4,673,146✔
1467
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
4,673,146✔
1468

1469
        let py_parameter = Self { symbol };
4,673,146✔
1470
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
4,673,146✔
1471

1472
        PyClassInitializer::from(py_expr).add_subclass(py_parameter)
4,673,146✔
1473
    }
4,673,146✔
1474

1475
    /// Get a reference to the underlying symbol.
1476
    pub fn symbol(&self) -> &Symbol {
4,557,486✔
1477
        &self.symbol
4,557,486✔
1478
    }
4,557,486✔
1479
}
1480

1481
#[pymethods]
×
1482
impl PyParameter {
1483
    /// Args:
1484
    ///     name: name of the parameter, used for visual representation. This can
1485
    ///         be any Unicode string, e.g. ``"Ï•"``.
1486
    ///     uuid: For advanced usage only.  Override the UUID of this parameter, in order to make it
1487
    ///         compare equal to some other parameter object.  By default, two parameters with the
1488
    ///         same name do not compare equal to help catch shadowing bugs when two circuits
1489
    ///         containing the same named parameters are spurious combined.  Setting the ``uuid``
1490
    ///         field when creating two parameters to the same thing (along with the same name)
1491
    ///         allows them to be equal.  This is useful during serialization and deserialization.
1492
    #[new]
1493
    #[pyo3(signature = (name, uuid=None))]
1494
    fn py_new(
84,542✔
1495
        py: Python<'_>,
84,542✔
1496
        name: String,
84,542✔
1497
        uuid: Option<Py<PyAny>>,
84,542✔
1498
    ) -> PyResult<PyClassInitializer<Self>> {
84,542✔
1499
        let uuid = uuid_from_py(py, uuid)?;
84,542✔
1500
        let symbol = Symbol::new(name.as_str(), uuid, None);
84,542✔
1501
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
84,542✔
1502

1503
        let py_parameter = Self { symbol };
84,542✔
1504
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
84,542✔
1505

1506
        Ok(PyClassInitializer::from(py_expr).add_subclass(py_parameter))
84,542✔
1507
    }
84,542✔
1508

1509
    /// Returns the name of the :class:`.Parameter`.
1510
    #[getter]
1511
    fn name<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
21,370✔
1512
        PyString::new(py, &self.symbol.repr(false))
21,370✔
1513
    }
21,370✔
1514

1515
    /// Returns the :class:`~uuid.UUID` of the :class:`Parameter`.
1516
    ///
1517
    /// In advanced use cases, this property can be passed to the
1518
    /// :class:`.Parameter` constructor to produce an instance that compares
1519
    /// equal to another instance.
1520
    #[getter]
1521
    fn uuid(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
1,368✔
1522
        uuid_to_py(py, self.symbol.uuid)
1,368✔
1523
    }
1,368✔
1524

1525
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
64✔
1526
        let str = format!("Parameter({})", self.symbol.repr(false),);
64✔
1527
        PyString::new(py, str.as_str())
64✔
1528
    }
64✔
1529

1530
    pub fn __getnewargs__(&self) -> (String, u128) {
24,144✔
1531
        (self.symbol.repr(false), self.symbol.uuid.as_u128())
24,144✔
1532
    }
24,144✔
1533

1534
    pub fn __getstate__(&self) -> (String, u128) {
24,144✔
1535
        (self.symbol.repr(false), self.symbol.uuid.as_u128())
24,144✔
1536
    }
24,144✔
1537

1538
    pub fn __setstate__(&mut self, state: (String, u128)) {
16✔
1539
        let name = state.0.as_str();
16✔
1540
        let uuid = Uuid::from_u128(state.1);
16✔
1541
        let symbol = Symbol::new(name, Some(uuid), None);
16✔
1542
        self.symbol = symbol;
16✔
1543
    }
16✔
1544

1545
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1546
        // Parameter is immutable. Note that this **cannot** be deferred to the parent class
1547
        // since PyO3 would then always return the parent type.
1548
        slf
×
1549
    }
×
1550

1551
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
150,206✔
1552
        // Everything inside a Parameter is immutable. Note that this **cannot** be deferred to the
1553
        // parent class since PyO3 would then always return the parent type.
1554
        slf
150,206✔
1555
    }
150,206✔
1556

1557
    #[pyo3(name = "subs")]
1558
    #[pyo3(signature = (parameter_map, allow_unknown_parameters=false))]
1559
    pub fn py_subs<'py>(
416✔
1560
        &self,
416✔
1561
        py: Python<'py>,
416✔
1562
        parameter_map: HashMap<PyParameter, Bound<'py, PyAny>>,
416✔
1563
        allow_unknown_parameters: bool,
416✔
1564
    ) -> PyResult<Bound<'py, PyAny>> {
416✔
1565
        // We implement this method on this class, and do not defer to the parent, such
1566
        // that x.subs({x: y}) remains a Parameter, and is not upgraded to an expression.
1567
        // Also this should be faster than going via ParameterExpression, which constructs
1568
        // intermediary HashMaps we don't need here.
1569
        match parameter_map.get(self) {
416✔
1570
            None => {
1571
                if allow_unknown_parameters {
4✔
1572
                    self.clone().into_bound_py_any(py)
2✔
1573
                } else {
1574
                    Err(CircuitError::new_err(
2✔
1575
                        "Cannot bind parameters not present in parameter.",
2✔
1576
                    ))
2✔
1577
                }
1578
            }
1579
            Some(replacement) => {
412✔
1580
                if allow_unknown_parameters || parameter_map.len() == 1 {
412✔
1581
                    Ok(replacement.clone())
412✔
1582
                } else {
1583
                    Err(CircuitError::new_err(
×
1584
                        "Cannot bind parameters not present in parameter.",
×
1585
                    ))
×
1586
                }
1587
            }
1588
        }
1589
    }
416✔
1590

1591
    #[pyo3(name = "bind")]
1592
    #[pyo3(signature = (parameter_values, allow_unknown_parameters=false))]
1593
    pub fn py_bind<'py>(
146✔
1594
        &self,
146✔
1595
        py: Python<'py>,
146✔
1596
        parameter_values: HashMap<PyParameter, Bound<'py, PyAny>>,
146✔
1597
        allow_unknown_parameters: bool,
146✔
1598
    ) -> PyResult<Bound<'py, PyAny>> {
146✔
1599
        // Returns PyAny to cover Parameter and ParameterExpression(value).
1600
        match parameter_values.get(self) {
146✔
1601
            None => {
1602
                if allow_unknown_parameters {
×
1603
                    self.clone().into_bound_py_any(py)
×
1604
                } else {
1605
                    Err(CircuitError::new_err(
×
1606
                        "Cannot bind parameters not present in parameter.",
×
1607
                    ))
×
1608
                }
1609
            }
1610
            Some(replacement) => {
146✔
1611
                if allow_unknown_parameters || parameter_values.len() == 1 {
146✔
1612
                    let expr = PyParameterExpression::extract_coerce(replacement.as_borrowed())?;
146✔
1613
                    if let SymbolExpr::Value(_) = &expr.inner.expr {
146✔
1614
                        expr.clone().into_bound_py_any(py)
146✔
1615
                    } else {
1616
                        Err(PyValueError::new_err("Invalid binding value."))
×
1617
                    }
1618
                } else {
1619
                    Err(CircuitError::new_err(
×
1620
                        "Cannot bind parameters not present in parameter.",
×
1621
                    ))
×
1622
                }
1623
            }
1624
        }
1625
    }
146✔
1626

1627
    #[pyo3(name = "bind_all")]
1628
    #[pyo3(signature = (values, *))]
1629
    pub fn py_bind_all<'py>(
4✔
1630
        slf_: Bound<'py, Self>,
4✔
1631
        values: Bound<'py, PyAny>,
4✔
1632
    ) -> PyResult<Bound<'py, PyAny>> {
4✔
1633
        values.get_item(slf_)
4✔
1634
    }
4✔
1635

1636
    #[pyo3(name = "assign")]
1637
    pub fn py_assign<'py>(
408✔
1638
        &self,
408✔
1639
        py: Python<'py>,
408✔
1640
        parameter: PyParameter,
408✔
1641
        value: &Bound<'py, PyAny>,
408✔
1642
    ) -> PyResult<Bound<'py, PyAny>> {
408✔
1643
        if value.cast::<PyParameterExpression>().is_ok() {
408✔
1644
            let map = [(parameter, value.clone())].into_iter().collect();
408✔
1645
            self.py_subs(py, map, false)
408✔
1646
        } else if value.extract::<Value>().is_ok() {
×
1647
            let map = [(parameter, value.clone())].into_iter().collect();
×
1648
            self.py_bind(py, map, false)
×
1649
        } else {
1650
            Err(PyValueError::new_err(
×
1651
                "Unexpected value in assign: {replacement:?}",
×
1652
            ))
×
1653
        }
1654
    }
408✔
1655
}
1656

1657
/// An element of a :class:`.ParameterVector`.
1658
///
1659
/// .. note::
1660
///     There is very little reason to ever construct this class directly.  Objects of this type are
1661
///     automatically constructed efficiently as part of creating a :class:`.ParameterVector`.
1662
#[pyclass(sequence, subclass, module="qiskit._accelerate.circuit", extends=PyParameter, name="ParameterVectorElement")]
1663
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
1664
pub struct PyParameterVectorElement {
1665
    symbol: Symbol,
1666
}
1667

1668
impl<'py> IntoPyObject<'py> for PyParameterVectorElement {
1669
    type Target = PyParameterVectorElement;
1670
    type Output = Bound<'py, Self::Target>;
1671
    type Error = PyErr;
1672

1673
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
176✔
1674
        let symbol = &self.symbol;
176✔
1675
        let py_param = PyParameter::from_symbol(symbol.clone());
176✔
1676
        let py_element = py_param.add_subclass(self);
176✔
1677

1678
        Ok(Py::new(py, py_element)?.into_bound(py))
176✔
1679
    }
176✔
1680
}
1681

1682
impl PyParameterVectorElement {
1683
    pub fn symbol(&self) -> &Symbol {
16,460✔
1684
        &self.symbol
16,460✔
1685
    }
16,460✔
1686

1687
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
4,556,092✔
1688
        let py_element = Self {
4,556,092✔
1689
            symbol: symbol.clone(),
4,556,092✔
1690
        };
4,556,092✔
1691
        let py_parameter = PyParameter::from_symbol(symbol);
4,556,092✔
1692

1693
        py_parameter.add_subclass(py_element)
4,556,092✔
1694
    }
4,556,092✔
1695
}
1696

1697
#[pymethods]
×
1698
impl PyParameterVectorElement {
1699
    #[new]
1700
    #[pyo3(signature = (vector, index, uuid=None))]
1701
    pub fn py_new(
27,860✔
1702
        py: Python<'_>,
27,860✔
1703
        vector: Py<PyAny>,
27,860✔
1704
        index: u32,
27,860✔
1705
        uuid: Option<Py<PyAny>>,
27,860✔
1706
    ) -> PyResult<PyClassInitializer<Self>> {
27,860✔
1707
        let vector_name = vector.getattr(py, "name")?.extract::<String>(py)?;
27,860✔
1708
        let uuid = uuid_from_py(py, uuid)?.unwrap_or(Uuid::new_v4());
27,860✔
1709

1710
        let symbol = Symbol::py_new(
27,860✔
1711
            &vector_name,
27,860✔
1712
            Some(uuid.as_u128()),
27,860✔
1713
            Some(index),
27,860✔
1714
            Some(vector.clone_ref(py)),
27,860✔
1715
        )?;
×
1716

1717
        let py_parameter = PyParameter::from_symbol(symbol.clone());
27,860✔
1718
        let py_element = Self { symbol };
27,860✔
1719

1720
        Ok(py_parameter.add_subclass(py_element))
27,860✔
1721
    }
27,860✔
1722

1723
    pub fn __getnewargs__(&self, py: Python) -> PyResult<(Py<PyAny>, u32, Option<Py<PyAny>>)> {
8✔
1724
        let vector = self
8✔
1725
            .symbol
8✔
1726
            .vector
8✔
1727
            .clone()
8✔
1728
            .expect("vector element should have a vector");
8✔
1729
        let index = self
8✔
1730
            .symbol
8✔
1731
            .index
8✔
1732
            .expect("vector element should have an index");
8✔
1733
        let uuid = uuid_to_py(py, self.symbol.uuid)?;
8✔
1734
        Ok((vector, index, Some(uuid)))
8✔
1735
    }
8✔
1736

1737
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
48✔
1738
        let str = format!("ParameterVectorElement({})", self.symbol.repr(false),);
48✔
1739
        PyString::new(py, str.as_str())
48✔
1740
    }
48✔
1741

1742
    pub fn __getstate__(&self, py: Python) -> PyResult<(Py<PyAny>, u32, Option<Py<PyAny>>)> {
8✔
1743
        self.__getnewargs__(py)
8✔
1744
    }
8✔
1745

1746
    pub fn __setstate__(
×
1747
        &mut self,
×
1748
        py: Python,
×
1749
        state: (Py<PyAny>, u32, Option<Py<PyAny>>),
×
1750
    ) -> PyResult<()> {
×
1751
        let vector = state.0;
×
1752
        let index = state.1;
×
1753
        let vector_name = vector.getattr(py, "name")?.extract::<String>(py)?;
×
1754
        let uuid = uuid_from_py(py, state.2)?.map(|id| id.as_u128());
×
1755
        self.symbol = Symbol::py_new(&vector_name, uuid, Some(index), Some(vector))?;
×
1756
        Ok(())
×
1757
    }
×
1758

1759
    /// Get the index of this element in the parent vector.
1760
    #[getter]
1761
    pub fn index(&self) -> u32 {
336✔
1762
        self.symbol
336✔
1763
            .index
336✔
1764
            .expect("A vector element should have an index")
336✔
1765
    }
336✔
1766

1767
    /// Get the parent vector instance.
1768
    #[getter]
1769
    pub fn vector(&self) -> Py<PyAny> {
668✔
1770
        self.symbol
668✔
1771
            .clone()
668✔
1772
            .vector
668✔
1773
            .expect("A vector element should have a vector")
668✔
1774
    }
668✔
1775

1776
    /// For backward compatibility only. This should not be used and we ought to update those
1777
    /// usages!
1778
    #[getter]
1779
    pub fn _vector(&self) -> Py<PyAny> {
×
1780
        self.vector()
×
1781
    }
×
1782

1783
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1784
        // ParameterVectorElement is immutable.
1785
        slf
×
1786
    }
×
1787

1788
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
10,988✔
1789
        // Everything a ParameterVectorElement contains is immutable.
1790
        slf
10,988✔
1791
    }
10,988✔
1792
}
1793

1794
/// Try to extract a Uuid from a Python object, which could be a Python UUID or int.
1795
fn uuid_from_py(py: Python<'_>, uuid: Option<Py<PyAny>>) -> PyResult<Option<Uuid>> {
112,402✔
1796
    if let Some(val) = uuid {
112,402✔
1797
        // construct from u128
1798
        let as_u128 = if let Ok(as_u128) = val.extract::<u128>(py) {
28,042✔
1799
            as_u128
16✔
1800
        // construct from Python UUID type
1801
        } else if val.bind(py).is_exact_instance(UUID.get_bound(py)) {
28,026✔
1802
            val.getattr(py, "int")?.extract::<u128>(py)?
28,026✔
1803
        // invalid format
1804
        } else {
1805
            return Err(PyTypeError::new_err("not a UUID!"));
×
1806
        };
1807
        Ok(Some(Uuid::from_u128(as_u128)))
28,042✔
1808
    } else {
1809
        Ok(None)
84,360✔
1810
    }
1811
}
112,402✔
1812

1813
/// Convert a Rust Uuid object to a Python UUID object.
1814
fn uuid_to_py(py: Python<'_>, uuid: Uuid) -> PyResult<Py<PyAny>> {
1,376✔
1815
    let uuid = uuid.as_u128();
1,376✔
1816
    let kwargs = [("int", uuid)].into_py_dict(py)?;
1,376✔
1817
    Ok(UUID.get_bound(py).call((), Some(&kwargs))?.unbind())
1,376✔
1818
}
1,376✔
1819

1820
/// Extract a [Symbol] for a Python object, which could either be a Parameter or a
1821
/// ParameterVectorElement.
1822
fn symbol_from_py_parameter(param: &Bound<'_, PyAny>) -> PyResult<Symbol> {
74✔
1823
    if let Ok(element) = param.extract::<PyParameterVectorElement>() {
74✔
1824
        Ok(element.symbol.clone())
×
1825
    } else if let Ok(parameter) = param.extract::<PyParameter>() {
74✔
1826
        Ok(parameter.symbol.clone())
74✔
1827
    } else {
1828
        Err(PyValueError::new_err("Could not extract parameter"))
×
1829
    }
1830
}
74✔
1831

1832
/// A singular parameter value used for QPY serialization. This covers anything
1833
/// but a [PyParameterExpression], which is represented by [None] in the serialization.
1834
#[derive(IntoPyObject, FromPyObject, Clone, Debug)]
1835
pub enum ParameterValueType {
1836
    Int(i64),
1837
    Float(f64),
1838
    Complex(Complex64),
1839
    Parameter(PyParameter),
1840
    VectorElement(PyParameterVectorElement),
1841
}
1842

1843
impl ParameterValueType {
1844
    fn extract_from_expr(expr: &SymbolExpr) -> Option<ParameterValueType> {
18,190✔
1845
        if let Some(value) = expr.eval(true) {
18,190✔
1846
            match value {
7,472✔
1847
                Value::Int(i) => Some(ParameterValueType::Int(i)),
100✔
1848
                Value::Real(r) => Some(ParameterValueType::Float(r)),
7,364✔
1849
                Value::Complex(c) => Some(ParameterValueType::Complex(c)),
8✔
1850
            }
1851
        } else if let SymbolExpr::Symbol(symbol) = expr {
10,718✔
1852
            match symbol.index {
9,310✔
1853
                None => {
1854
                    let param = PyParameter {
9,134✔
1855
                        symbol: symbol.as_ref().clone(),
9,134✔
1856
                    };
9,134✔
1857
                    Some(ParameterValueType::Parameter(param))
9,134✔
1858
                }
1859
                Some(_) => {
1860
                    let param = PyParameterVectorElement {
176✔
1861
                        symbol: symbol.as_ref().clone(),
176✔
1862
                    };
176✔
1863
                    Some(ParameterValueType::VectorElement(param))
176✔
1864
                }
1865
            }
1866
        } else {
1867
            // ParameterExpressions have the value None, as they must be constructed
1868
            None
1,408✔
1869
        }
1870
    }
18,190✔
1871
}
1872

1873
impl From<ParameterValueType> for ParameterExpression {
1874
    fn from(value: ParameterValueType) -> Self {
×
1875
        match value {
×
1876
            ParameterValueType::Parameter(param) => {
×
1877
                let expr = SymbolExpr::Symbol(Arc::new(param.symbol));
×
1878
                Self::from_symbol_expr(expr)
×
1879
            }
1880
            ParameterValueType::VectorElement(param) => {
×
1881
                let expr = SymbolExpr::Symbol(Arc::new(param.symbol));
×
1882
                Self::from_symbol_expr(expr)
×
1883
            }
1884
            ParameterValueType::Int(i) => {
×
1885
                let expr = SymbolExpr::Value(Value::Int(i));
×
1886
                Self::from_symbol_expr(expr)
×
1887
            }
1888
            ParameterValueType::Float(f) => {
×
1889
                let expr = SymbolExpr::Value(Value::Real(f));
×
1890
                Self::from_symbol_expr(expr)
×
1891
            }
1892
            ParameterValueType::Complex(c) => {
×
1893
                let expr = SymbolExpr::Value(Value::Complex(c));
×
1894
                Self::from_symbol_expr(expr)
×
1895
            }
1896
        }
1897
    }
×
1898
}
1899

1900
#[pyclass(module = "qiskit._accelerate.circuit")]
×
1901
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
1902
#[repr(u8)]
1903
pub enum OpCode {
1904
    ADD = 0,
1905
    SUB = 1,
1906
    MUL = 2,
1907
    DIV = 3,
1908
    POW = 4,
1909
    SIN = 5,
1910
    COS = 6,
1911
    TAN = 7,
1912
    ASIN = 8,
1913
    ACOS = 9,
1914
    EXP = 10,
1915
    LOG = 11,
1916
    SIGN = 12,
1917
    GRAD = 13, // for backward compatibility, unused in Rust's ParameterExpression
1918
    CONJ = 14,
1919
    SUBSTITUTE = 15, // for backward compatibility, unused in Rust's ParameterExpression
1920
    ABS = 16,
1921
    ATAN = 17,
1922
    RSUB = 18,
1923
    RDIV = 19,
1924
    RPOW = 20,
1925
}
1926

1927
impl From<OpCode> for u8 {
1928
    fn from(value: OpCode) -> Self {
×
1929
        value as u8
×
1930
    }
×
1931
}
1932

1933
unsafe impl ::bytemuck::CheckedBitPattern for OpCode {
1934
    type Bits = u8;
1935

1936
    #[inline(always)]
1937
    fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
×
1938
        *bits <= 20
×
1939
    }
×
1940
}
1941

1942
unsafe impl ::bytemuck::NoUninit for OpCode {}
1943

1944
#[pymethods]
×
1945
impl OpCode {
1946
    #[new]
1947
    fn py_new(value: u8) -> PyResult<Self> {
×
1948
        let code: OpCode = ::bytemuck::checked::try_cast(value)
×
1949
            .map_err(|_| ParameterError::InvalidU8ToOpCode(value))?;
×
1950
        Ok(code)
×
1951
    }
×
1952

1953
    fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
560✔
1954
        if let Ok(code) = other.cast::<OpCode>() {
560✔
1955
            *code.borrow() == *self
560✔
1956
        } else {
1957
            false
×
1958
        }
1959
    }
560✔
1960

1961
    fn __hash__(&self) -> u8 {
2,088✔
1962
        *self as u8
2,088✔
1963
    }
2,088✔
1964

1965
    fn __getnewargs__(&self) -> (u8,) {
8,964✔
1966
        (*self as u8,)
8,964✔
1967
    }
8,964✔
1968
}
1969

1970
// enum for QPY replay
1971
#[pyclass(sequence, module = "qiskit._accelerate.circuit")]
1972
#[derive(Clone, Debug)]
1973
pub struct OPReplay {
1974
    pub op: OpCode,
1975
    pub lhs: Option<ParameterValueType>,
1976
    pub rhs: Option<ParameterValueType>,
1977
}
1978

1979
#[pymethods]
×
1980
impl OPReplay {
1981
    #[new]
1982
    pub fn py_new(
×
1983
        op: OpCode,
×
1984
        lhs: Option<ParameterValueType>,
×
1985
        rhs: Option<ParameterValueType>,
×
1986
    ) -> OPReplay {
×
1987
        OPReplay { op, lhs, rhs }
×
1988
    }
×
1989

1990
    #[getter]
1991
    fn op(&self) -> OpCode {
1,194✔
1992
        self.op
1,194✔
1993
    }
1,194✔
1994

1995
    #[getter]
1996
    fn lhs(&self) -> Option<ParameterValueType> {
630✔
1997
        self.lhs.clone()
630✔
1998
    }
630✔
1999

2000
    #[getter]
2001
    fn rhs(&self) -> Option<ParameterValueType> {
630✔
2002
        self.rhs.clone()
630✔
2003
    }
630✔
2004

2005
    fn __getnewargs__(
8,964✔
2006
        &self,
8,964✔
2007
    ) -> (
8,964✔
2008
        OpCode,
8,964✔
2009
        Option<ParameterValueType>,
8,964✔
2010
        Option<ParameterValueType>,
8,964✔
2011
    ) {
8,964✔
2012
        (self.op, self.lhs.clone(), self.rhs.clone())
8,964✔
2013
    }
8,964✔
2014
}
2015

2016
/// Internal helper. Extract one part of the expression tree, keeping the name map up to date.
2017
///
2018
/// Example: Given expr1 + expr2, each being [PyParameterExpression], we need the ability to
2019
/// extract one of the expressions with the proper name map.
2020
///
2021
/// Args:
2022
///     - joint_parameter_expr: The full expression, e.g. expr1 + expr2.
2023
///     - sub_expr: The sub expression, on whose symbols we restrict the name map.
2024
fn filter_name_map(
18,190✔
2025
    sub_expr: &SymbolExpr,
18,190✔
2026
    name_map: &HashMap<String, Symbol>,
18,190✔
2027
) -> ParameterExpression {
18,190✔
2028
    let sub_symbols: HashSet<&Symbol> = sub_expr.iter_symbols().collect();
18,190✔
2029
    let restricted_name_map: HashMap<String, Symbol> = name_map
18,190✔
2030
        .iter()
18,190✔
2031
        .filter(|(_, symbol)| sub_symbols.contains(*symbol))
23,318✔
2032
        .map(|(name, symbol)| (name.clone(), symbol.clone()))
18,190✔
2033
        .collect();
18,190✔
2034

2035
    ParameterExpression {
18,190✔
2036
        expr: sub_expr.clone(),
18,190✔
2037
        name_map: restricted_name_map,
18,190✔
2038
    }
18,190✔
2039
}
18,190✔
2040

2041
pub fn qpy_replay(
26,376✔
2042
    expr: &ParameterExpression,
26,376✔
2043
    name_map: &HashMap<String, Symbol>,
26,376✔
2044
    replay: &mut Vec<OPReplay>,
26,376✔
2045
) {
26,376✔
2046
    match &expr.expr {
26,376✔
2047
        SymbolExpr::Value(_) | SymbolExpr::Symbol(_) => {
16,782✔
2048
            // nothing to do here, we only need to traverse instructions
16,782✔
2049
        }
16,782✔
2050
        SymbolExpr::Unary { op, expr } => {
998✔
2051
            let op = match op {
998✔
2052
                symbol_expr::UnaryOp::Abs => OpCode::ABS,
6✔
2053
                symbol_expr::UnaryOp::Acos => OpCode::ACOS,
6✔
2054
                symbol_expr::UnaryOp::Asin => OpCode::ASIN,
6✔
2055
                symbol_expr::UnaryOp::Atan => OpCode::ATAN,
6✔
2056
                symbol_expr::UnaryOp::Conj => OpCode::CONJ,
6✔
2057
                symbol_expr::UnaryOp::Cos => OpCode::COS,
6✔
2058
                symbol_expr::UnaryOp::Exp => OpCode::EXP,
6✔
2059
                symbol_expr::UnaryOp::Log => OpCode::LOG,
6✔
2060
                symbol_expr::UnaryOp::Neg => OpCode::MUL,
922✔
2061
                symbol_expr::UnaryOp::Sign => OpCode::SIGN,
4✔
2062
                symbol_expr::UnaryOp::Sin => OpCode::SIN,
18✔
2063
                symbol_expr::UnaryOp::Tan => OpCode::TAN,
6✔
2064
            };
2065
            // TODO filter shouldn't be necessary for unary ops
2066
            let lhs = filter_name_map(expr, name_map);
998✔
2067

2068
            // recurse on the instruction
2069
            qpy_replay(&lhs, name_map, replay);
998✔
2070

2071
            let lhs_value = ParameterValueType::extract_from_expr(expr);
998✔
2072

2073
            // MUL is special: we implement ``neg`` as multiplication by -1
2074
            if let OpCode::MUL = &op {
998✔
2075
                replay.push(OPReplay {
922✔
2076
                    op,
922✔
2077
                    lhs: lhs_value,
922✔
2078
                    rhs: Some(ParameterValueType::Int(-1)),
922✔
2079
                });
922✔
2080
            } else {
922✔
2081
                replay.push(OPReplay {
76✔
2082
                    op,
76✔
2083
                    lhs: lhs_value,
76✔
2084
                    rhs: None,
76✔
2085
                });
76✔
2086
            }
76✔
2087
        }
2088
        SymbolExpr::Binary { op, lhs, rhs } => {
8,596✔
2089
            let lhs_value = ParameterValueType::extract_from_expr(lhs);
8,596✔
2090
            let rhs_value = ParameterValueType::extract_from_expr(rhs);
8,596✔
2091

2092
            // recurse on the parameter expressions
2093
            let lhs = filter_name_map(lhs, name_map);
8,596✔
2094
            let rhs = filter_name_map(rhs, name_map);
8,596✔
2095
            qpy_replay(&lhs, name_map, replay);
8,596✔
2096
            qpy_replay(&rhs, name_map, replay);
8,596✔
2097

2098
            // add the expression to the replay
2099
            match lhs_value {
8,342✔
2100
                None
2101
                | Some(ParameterValueType::Parameter(_))
2102
                | Some(ParameterValueType::VectorElement(_)) => {
2103
                    let op = match op {
1,278✔
2104
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
744✔
2105
                        symbol_expr::BinaryOp::Sub => OpCode::SUB,
416✔
2106
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
86✔
2107
                        symbol_expr::BinaryOp::Div => OpCode::DIV,
12✔
2108
                        symbol_expr::BinaryOp::Pow => OpCode::POW,
20✔
2109
                    };
2110
                    replay.push(OPReplay {
1,278✔
2111
                        op,
1,278✔
2112
                        lhs: lhs_value,
1,278✔
2113
                        rhs: rhs_value,
1,278✔
2114
                    });
1,278✔
2115
                }
2116
                _ => {
2117
                    let op = match op {
7,318✔
2118
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
578✔
2119
                        symbol_expr::BinaryOp::Sub => OpCode::RSUB,
120✔
2120
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
6,612✔
2121
                        symbol_expr::BinaryOp::Div => OpCode::RDIV,
4✔
2122
                        symbol_expr::BinaryOp::Pow => OpCode::RPOW,
4✔
2123
                    };
2124
                    if let OpCode::ADD | OpCode::MUL = op {
7,318✔
2125
                        replay.push(OPReplay {
7,190✔
2126
                            op,
7,190✔
2127
                            lhs: lhs_value,
7,190✔
2128
                            rhs: rhs_value,
7,190✔
2129
                        });
7,190✔
2130
                    } else {
7,190✔
2131
                        // this covers RSUB, RDIV, RPOW, hence we swap lhs and rhs
128✔
2132
                        replay.push(OPReplay {
128✔
2133
                            op,
128✔
2134
                            lhs: rhs_value,
128✔
2135
                            rhs: lhs_value,
128✔
2136
                        });
128✔
2137
                    }
128✔
2138
                }
2139
            }
2140
        }
2141
    }
2142
}
26,376✔
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