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

Qiskit / qiskit / 21430682342

28 Jan 2026 08:23AM UTC coverage: 87.961% (-0.02%) from 87.984%
21430682342

Pull #15277

github

web-flow
Merge dcc1b31af into 95c22e0ec
Pull Request #15277: Combine Python types in PackedInstructions

132 of 180 new or added lines in 22 files covered. (73.33%)

24 existing lines in 7 files now uncovered.

100004 of 113691 relevant lines covered (87.96%)

1161040.61 hits per line

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

87.17
/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 hashbrown::hash_map::Entry;
14
use hashbrown::{HashMap, HashSet};
15
use num_complex::Complex64;
16
use pyo3::exceptions::{PyRuntimeError, PyTypeError, PyValueError, PyZeroDivisionError};
17
use pyo3::types::{IntoPyDict, PyComplex, PyFloat, PyInt, PyNotImplemented, PySet, PyString};
18
use std::sync::Arc;
19
use thiserror::Error;
20
use uuid::Uuid;
21

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

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

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

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

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

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

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

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

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

107
impl Eq for ParameterExpression {}
108

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

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

134
impl ParameterExpression {
135
    pub fn qpy_replay(&self) -> Vec<OPReplay> {
184✔
136
        let mut replay = Vec::new();
184✔
137
        qpy_replay(self, &self.name_map, &mut replay);
184✔
138
        replay
184✔
139
    }
184✔
140
}
141
// This needs to be implemented manually, because PyO3 does not provide built-in
142
// conversions for the subclasses of ParameterExpression in Python. Specifically
143
// the Python classes Parameter and ParameterVector are subclasses of
144
// ParameterExpression and the default trait impl would not handle the specialization
145
// there.
146
impl<'py> IntoPyObject<'py> for ParameterExpression {
147
    type Target = PyParameterExpression;
148
    type Output = Bound<'py, Self::Target>;
149
    type Error = PyErr;
150

151
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
10✔
152
        let expr = PyParameterExpression::from(self.clone());
10✔
153
        expr.into_pyobject(py)
10✔
154
    }
10✔
155
}
156

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

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

179
    /// Construct from a [Symbol].
180
    pub fn from_symbol(symbol: Symbol) -> Self {
78,011✔
181
        Self {
78,011✔
182
            expr: SymbolExpr::Symbol(Arc::new(symbol.clone())),
78,011✔
183
            name_map: [(symbol.repr(false), symbol)].into(),
78,011✔
184
        }
78,011✔
185
    }
78,011✔
186

187
    /// Try casting to a [Symbol].
188
    ///
189
    /// This only succeeds if the underlying expression is, in fact, only a symbol.
190
    pub fn try_to_symbol(&self) -> Result<Symbol, ParameterError> {
274,820✔
191
        if let SymbolExpr::Symbol(symbol) = &self.expr {
274,820✔
192
            Ok(symbol.as_ref().clone())
264,712✔
193
        } else {
194
            Err(ParameterError::NotASymbol)
10,108✔
195
        }
196
    }
274,820✔
197

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

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

225
        match self.expr.eval(true) {
9,578,264✔
226
            Some(value) => {
413,004✔
227
                // we try to restrict complex to real, if possible
228
                if let Value::Complex(c) = value {
413,004✔
229
                    if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON).contains(&c.im)
39,938✔
230
                    {
231
                        return Ok(Value::Real(c.re));
×
232
                    }
39,938✔
233
                }
373,066✔
234
                Ok(value)
413,004✔
235
            }
236
            None => {
237
                let free_symbols = self.expr.iter_symbols().cloned().collect();
9,165,260✔
238
                Err(ParameterError::UnboundParameters(free_symbols))
9,165,260✔
239
            }
240
        }
241
    }
9,652,034✔
242

243
    /// Construct from a [SymbolExpr].
244
    ///
245
    /// This populates the name map with the symbols in the expression.
246
    pub fn from_symbol_expr(expr: SymbolExpr) -> Self {
4,767,522✔
247
        let name_map = expr.name_map();
4,767,522✔
248
        Self { expr, name_map }
4,767,522✔
249
    }
4,767,522✔
250

251
    /// Initialize from an f64.
252
    pub fn from_f64(value: f64) -> Self {
11,892✔
253
        Self {
11,892✔
254
            expr: SymbolExpr::Value(Value::Real(value)),
11,892✔
255
            name_map: HashMap::new(),
11,892✔
256
        }
11,892✔
257
    }
11,892✔
258

259
    /// Load from a sequence of [OPReplay]s. Used in serialization.
260
    pub fn from_qpy(
180✔
261
        replay: &[OPReplay],
180✔
262
        subs_operations: Option<Vec<(usize, HashMap<Symbol, ParameterExpression>)>>,
180✔
263
    ) -> Result<Self, ParameterError> {
180✔
264
        // the stack contains the latest lhs and rhs values
265
        let mut stack: Vec<ParameterExpression> = Vec::new();
180✔
266
        let subs_operations = subs_operations.unwrap_or_default();
180✔
267
        let mut current_sub_operation = subs_operations.len(); // we avoid using a queue since we only make one pass anyway
180✔
268
        for (i, inst) in replay.iter().enumerate() {
388✔
269
            let OPReplay { op, lhs, rhs } = inst;
388✔
270

271
            // put the values on the stack, if they exist
272
            if let Some(value) = lhs {
388✔
273
                stack.push(value.clone().into());
274✔
274
            }
274✔
275
            if let Some(value) = rhs {
388✔
276
                stack.push(value.clone().into());
270✔
277
            }
270✔
278

279
            // if we need two operands, pop rhs from the stack
280
            let rhs = if BINARY_OPS.contains(op) {
388✔
281
                Some(stack.pop().expect("Pop from empty stack"))
364✔
282
            } else {
283
                None
24✔
284
            };
285

286
            // pop lhs from the stack, this we always need
287
            let lhs = stack.pop().expect("Pop from empty stack");
388✔
288

289
            // apply the operation and put the result onto the stack for the next replay
290
            let result: ParameterExpression = match op {
388✔
291
                OpCode::ADD => lhs.add(&rhs.unwrap())?,
104✔
292
                OpCode::MUL => lhs.mul(&rhs.unwrap())?,
180✔
293
                OpCode::SUB => lhs.sub(&rhs.unwrap())?,
8✔
294
                OpCode::RSUB => rhs.unwrap().sub(&lhs)?,
48✔
295
                OpCode::POW => lhs.pow(&rhs.unwrap())?,
8✔
296
                OpCode::RPOW => rhs.unwrap().pow(&lhs)?,
4✔
297
                OpCode::DIV => lhs.div(&rhs.unwrap())?,
8✔
298
                OpCode::RDIV => rhs.unwrap().div(&lhs)?,
4✔
299
                OpCode::ABS => lhs.abs(),
2✔
300
                OpCode::SIN => lhs.sin(),
6✔
301
                OpCode::ASIN => lhs.asin(),
2✔
302
                OpCode::COS => lhs.cos(),
2✔
303
                OpCode::ACOS => lhs.acos(),
2✔
304
                OpCode::TAN => lhs.tan(),
2✔
305
                OpCode::ATAN => lhs.atan(),
2✔
306
                OpCode::CONJ => lhs.conjugate(),
2✔
307
                OpCode::LOG => lhs.log(),
2✔
308
                OpCode::EXP => lhs.exp(),
2✔
309
                OpCode::SIGN => lhs.sign(),
×
310
                OpCode::GRAD | OpCode::SUBSTITUTE => {
311
                    panic!("GRAD and SUBSTITUTE are not supported.")
×
312
                }
313
            };
314
            stack.push(result);
388✔
315
            //now check whether any substitutions need to be applied at this stage
316
            while current_sub_operation > 0 && subs_operations[current_sub_operation - 1].0 == i + 1
388✔
317
            {
318
                if let Some(exp) = stack.pop() {
×
319
                    let sub_exp = exp.subs(&subs_operations[current_sub_operation - 1].1, false)?;
×
320
                    stack.push(sub_exp);
×
321
                }
×
322
                current_sub_operation -= 1;
×
323
            }
324
        }
325

326
        // once we're done, just return the last element in the stack
327
        Ok(stack
180✔
328
            .pop()
180✔
329
            .expect("Invalid QPY replay encountered during deserialization: empty OPReplay."))
180✔
330
    }
180✔
331

332
    pub fn iter_symbols(&self) -> impl Iterator<Item = &Symbol> + '_ {
462,842✔
333
        self.name_map.values()
462,842✔
334
    }
462,842✔
335

336
    /// Get the number of [Symbol]s in the expression.
337
    pub fn num_symbols(&self) -> usize {
6✔
338
        self.name_map.len()
6✔
339
    }
6✔
340

341
    /// Whether the expression represents a complex number. None if cannot be determined.
342
    pub fn is_complex(&self) -> Option<bool> {
23,198✔
343
        self.expr.is_complex()
23,198✔
344
    }
23,198✔
345

346
    /// Whether the expression represents a int. None if cannot be determined.
347
    pub fn is_int(&self) -> Option<bool> {
85,722✔
348
        self.expr.is_int()
85,722✔
349
    }
85,722✔
350

351
    /// Add an expression; ``self + rhs``.
352
    pub fn add(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,391,388✔
353
        let name_map = self.merged_name_map(rhs)?;
2,391,388✔
354
        Ok(Self {
2,391,386✔
355
            expr: &self.expr + &rhs.expr,
2,391,386✔
356
            name_map,
2,391,386✔
357
        })
2,391,386✔
358
    }
2,391,388✔
359

360
    /// Multiply with an expression; ``self * rhs``.
361
    pub fn mul(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,415,424✔
362
        let name_map = self.merged_name_map(rhs)?;
2,415,424✔
363
        Ok(Self {
2,415,422✔
364
            expr: &self.expr * &rhs.expr,
2,415,422✔
365
            name_map,
2,415,422✔
366
        })
2,415,422✔
367
    }
2,415,424✔
368

369
    /// Subtract another expression; ``self - rhs``.
370
    pub fn sub(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
24,124✔
371
        let name_map = self.merged_name_map(rhs)?;
24,124✔
372
        Ok(Self {
24,122✔
373
            expr: &self.expr - &rhs.expr,
24,122✔
374
            name_map,
24,122✔
375
        })
24,122✔
376
    }
24,124✔
377

378
    /// Divide by another expression; ``self / rhs``.
379
    pub fn div(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
13,956✔
380
        if rhs.expr.is_zero() {
13,956✔
381
            return Err(ParameterError::ZeroDivisionError);
1,012✔
382
        }
12,944✔
383

384
        let name_map = self.merged_name_map(rhs)?;
12,944✔
385
        Ok(Self {
12,942✔
386
            expr: &self.expr / &rhs.expr,
12,942✔
387
            name_map,
12,942✔
388
        })
12,942✔
389
    }
13,956✔
390

391
    /// Raise this expression to a power; ``self ^ rhs``.
392
    pub fn pow(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
3,016✔
393
        let name_map = self.merged_name_map(rhs)?;
3,016✔
394
        Ok(Self {
3,016✔
395
            expr: self.expr.pow(&rhs.expr),
3,016✔
396
            name_map,
3,016✔
397
        })
3,016✔
398
    }
3,016✔
399

400
    /// Apply the sine to this expression; ``sin(self)``.
401
    pub fn sin(&self) -> Self {
362✔
402
        Self {
362✔
403
            expr: self.expr.sin(),
362✔
404
            name_map: self.name_map.clone(),
362✔
405
        }
362✔
406
    }
362✔
407

408
    /// Apply the cosine to this expression; ``cos(self)``.
409
    pub fn cos(&self) -> Self {
352✔
410
        Self {
352✔
411
            expr: self.expr.cos(),
352✔
412
            name_map: self.name_map.clone(),
352✔
413
        }
352✔
414
    }
352✔
415

416
    /// Apply the tangent to this expression; ``tan(self)``.
417
    pub fn tan(&self) -> Self {
352✔
418
        Self {
352✔
419
            expr: self.expr.tan(),
352✔
420
            name_map: self.name_map.clone(),
352✔
421
        }
352✔
422
    }
352✔
423

424
    /// Apply the arcsine to this expression; ``asin(self)``.
425
    pub fn asin(&self) -> Self {
100✔
426
        Self {
100✔
427
            expr: self.expr.asin(),
100✔
428
            name_map: self.name_map.clone(),
100✔
429
        }
100✔
430
    }
100✔
431

432
    /// Apply the arccosine to this expression; ``acos(self)``.
433
    pub fn acos(&self) -> Self {
100✔
434
        Self {
100✔
435
            expr: self.expr.acos(),
100✔
436
            name_map: self.name_map.clone(),
100✔
437
        }
100✔
438
    }
100✔
439

440
    /// Apply the arctangent to this expression; ``atan(self)``.
441
    pub fn atan(&self) -> Self {
100✔
442
        Self {
100✔
443
            expr: self.expr.atan(),
100✔
444
            name_map: self.name_map.clone(),
100✔
445
        }
100✔
446
    }
100✔
447

448
    /// Exponentiate this expression; ``exp(self)``.
449
    pub fn exp(&self) -> Self {
350✔
450
        Self {
350✔
451
            expr: self.expr.exp(),
350✔
452
            name_map: self.name_map.clone(),
350✔
453
        }
350✔
454
    }
350✔
455

456
    /// Take the (natural) logarithm of this expression; ``log(self)``.
457
    pub fn log(&self) -> Self {
266✔
458
        Self {
266✔
459
            expr: self.expr.log(),
266✔
460
            name_map: self.name_map.clone(),
266✔
461
        }
266✔
462
    }
266✔
463

464
    /// Take the absolute value of this expression; ``|self|``.
465
    pub fn abs(&self) -> Self {
374✔
466
        Self {
374✔
467
            expr: self.expr.abs(),
374✔
468
            name_map: self.name_map.clone(),
374✔
469
        }
374✔
470
    }
374✔
471

472
    /// Return the sign of this expression; ``sign(self)``.
473
    pub fn sign(&self) -> Self {
10✔
474
        Self {
10✔
475
            expr: self.expr.sign(),
10✔
476
            name_map: self.name_map.clone(),
10✔
477
        }
10✔
478
    }
10✔
479

480
    /// Complex conjugate the expression.
481
    pub fn conjugate(&self) -> Self {
1,832✔
482
        Self {
1,832✔
483
            expr: self.expr.conjugate(),
1,832✔
484
            name_map: self.name_map.clone(),
1,832✔
485
        }
1,832✔
486
    }
1,832✔
487

488
    /// negate the expression.
489
    pub fn neg(&self) -> Self {
×
490
        Self {
×
491
            expr: -&self.expr,
×
492
            name_map: self.name_map.clone(),
×
493
        }
×
494
    }
×
495

496
    /// Compute the derivative of the expression with respect to the provided symbol.
497
    ///
498
    /// Note that this keeps the name map unchanged. Meaning that computing the derivative
499
    /// of ``x`` will yield ``1`` but the expression still owns the symbol ``x``. This is
500
    /// done such that we can still bind the value ``x`` in an automated process.
501
    pub fn derivative(&self, param: &Symbol) -> Result<Self, ParameterError> {
74✔
502
        Ok(Self {
503
            expr: self
74✔
504
                .expr
74✔
505
                .derivative(param)
74✔
506
                .map_err(ParameterError::DerivativeNotSupported)?,
74✔
507
            name_map: self.name_map.clone(),
70✔
508
        })
509
    }
74✔
510

511
    /// Substitute symbols with [ParameterExpression]s.
512
    ///
513
    /// # Arguments
514
    ///
515
    /// * map - A hashmap with [Symbol] keys and [ParameterExpression]s to replace these
516
    ///   symbols with.
517
    /// * allow_unknown_parameters - If `false`, returns an error if any symbol in the
518
    ///   hashmap is not present in the expression. If `true`, unknown symbols are ignored.
519
    ///   Setting to `true` is slightly faster as it does not involve additional checks.
520
    ///
521
    /// # Returns
522
    ///
523
    /// * `Ok(Self)` - A parameter expression with the substituted expressions.
524
    /// * `Err(ParameterError)` - An error if the subtitution failed.
525
    pub fn subs(
241,970✔
526
        &self,
241,970✔
527
        map: &HashMap<Symbol, Self>,
241,970✔
528
        allow_unknown_parameters: bool,
241,970✔
529
    ) -> Result<Self, ParameterError> {
241,970✔
530
        // Build the outgoing name map. In the process we check for any duplicates.
531
        let mut name_map: HashMap<String, Symbol> = HashMap::new();
241,970✔
532
        let mut symbol_map: HashMap<Symbol, SymbolExpr> = HashMap::new();
241,970✔
533

534
        // If we don't allow for unknown parameters, check if there are any.
535
        if !allow_unknown_parameters {
241,970✔
536
            let existing: HashSet<&Symbol> = self.name_map.values().collect();
36,726✔
537
            let to_replace: HashSet<&Symbol> = map.keys().collect();
36,726✔
538
            let mut difference = to_replace.difference(&existing).peekable();
36,726✔
539

540
            if difference.peek().is_some() {
36,726✔
541
                let different_symbols = difference.map(|s| (**s).clone()).collect();
2✔
542
                return Err(ParameterError::UnknownParameters(different_symbols));
2✔
543
            }
36,724✔
544
        }
205,244✔
545

546
        for (name, symbol) in self.name_map.iter() {
251,258✔
547
            // check if the symbol will get replaced
548
            if let Some(replacement) = map.get(symbol) {
251,258✔
549
                // If yes, update the name_map. This also checks for duplicates.
550
                for (replacement_name, replacement_symbol) in replacement.name_map.iter() {
39,216✔
551
                    if let Some(duplicate) = name_map.get(replacement_name) {
39,216✔
552
                        // If a symbol with the same name already exists, check whether it is
553
                        // the same symbol (fine) or a different symbol with the same name (conflict)!
554
                        if duplicate != replacement_symbol {
30✔
555
                            return Err(ParameterError::NameConflict);
2✔
556
                        }
28✔
557
                    } else {
39,186✔
558
                        // SAFETY: We know the key does not exist yet.
39,186✔
559
                        unsafe {
39,186✔
560
                            name_map.insert_unique_unchecked(
39,186✔
561
                                replacement_name.clone(),
39,186✔
562
                                replacement_symbol.clone(),
39,186✔
563
                            )
39,186✔
564
                        };
39,186✔
565
                    }
39,186✔
566
                }
567

568
                // If we got until here, there were no duplicates, so we are safe to
569
                // add this symbol to the internal replacement map.
570
                symbol_map.insert(symbol.clone(), replacement.expr.clone());
38,110✔
571
            } else {
572
                // no replacement for this symbol, carry on
573
                match name_map.entry(name.clone()) {
213,146✔
574
                    Entry::Occupied(duplicate) => {
24✔
575
                        if duplicate.get() != symbol {
24✔
UNCOV
576
                            return Err(ParameterError::NameConflict);
×
577
                        }
24✔
578
                    }
579
                    Entry::Vacant(e) => {
213,122✔
580
                        e.insert(symbol.clone());
213,122✔
581
                    }
213,122✔
582
                }
583
            }
584
        }
585

586
        let res = self.expr.subs(&symbol_map);
241,966✔
587
        Ok(Self {
241,966✔
588
            expr: res,
241,966✔
589
            name_map,
241,966✔
590
        })
241,966✔
591
    }
241,970✔
592

593
    /// Bind symbols to values.
594
    ///
595
    /// # Arguments
596
    ///
597
    /// * map - A hashmap with [Symbol] keys and [Value]s to replace these
598
    ///   symbols with.
599
    /// * allow_unknown_parameter - If `false`, returns an error if any symbol in the
600
    ///   hashmap is not present in the expression. If `true`, unknown symbols are ignored.
601
    ///   Setting to `true` is slightly faster as it does not involve additional checks.
602
    ///
603
    /// # Returns
604
    ///
605
    /// * `Ok(Self)` - A parameter expression with the bound symbols.
606
    /// * `Err(ParameterError)` - An error if binding failed.
607
    pub fn bind(
414,364✔
608
        &self,
414,364✔
609
        map: &HashMap<&Symbol, Value>,
414,364✔
610
        allow_unknown_parameters: bool,
414,364✔
611
    ) -> Result<Self, ParameterError> {
414,364✔
612
        // The set of symbols we will bind. Used twice, hence pre-computed here.
613
        let bind_symbols: HashSet<&Symbol> = map.keys().cloned().collect();
414,364✔
614

615
        // If we don't allow for unknown parameters, check if there are any.
616
        if !allow_unknown_parameters {
414,364✔
617
            let existing: HashSet<&Symbol> = self.name_map.values().collect();
209,032✔
618
            let mut difference = bind_symbols.difference(&existing).peekable();
209,032✔
619

620
            if difference.peek().is_some() {
209,032✔
621
                let different_symbols = difference.map(|s| (**s).clone()).collect();
×
622
                return Err(ParameterError::UnknownParameters(different_symbols));
×
623
            }
209,032✔
624
        }
205,332✔
625

626
        // bind the symbol expression and then check the outcome for inf/nan, or numeric values
627
        let bound_expr = self.expr.bind(map);
414,364✔
628
        let bound = match bound_expr.eval(true) {
414,364✔
629
            Some(v) => match &v {
411,662✔
630
                Value::Real(r) => {
343,854✔
631
                    if r.is_infinite() {
343,854✔
632
                        Err(ParameterError::BindingInf)
1,828✔
633
                    } else if r.is_nan() {
342,026✔
634
                        Err(ParameterError::BindingNaN)
×
635
                    } else {
636
                        Ok(SymbolExpr::Value(v))
342,026✔
637
                    }
638
                }
639
                Value::Int(_) => Ok(SymbolExpr::Value(v)),
26,340✔
640
                Value::Complex(c) => {
41,468✔
641
                    if c.re.is_infinite() || c.im.is_infinite() {
41,468✔
642
                        Err(ParameterError::BindingInf)
×
643
                    } else if c.re.is_nan() || c.im.is_nan() {
41,468✔
644
                        Err(ParameterError::BindingNaN)
×
645
                    } else if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON)
41,468✔
646
                        .contains(&c.im)
41,468✔
647
                    {
648
                        Ok(SymbolExpr::Value(Value::Real(c.re)))
1,728✔
649
                    } else {
650
                        Ok(SymbolExpr::Value(v))
39,740✔
651
                    }
652
                }
653
            },
654
            None => Ok(bound_expr),
2,702✔
655
        }?;
1,828✔
656

657
        // update the name map by removing the bound parameters
658
        let bound_name_map: HashMap<String, Symbol> = self
412,536✔
659
            .name_map
412,536✔
660
            .iter()
412,536✔
661
            .filter(|(_, symbol)| !bind_symbols.contains(symbol))
4,711,584✔
662
            .map(|(name, symbol)| (name.clone(), symbol.clone()))
412,536✔
663
            .collect();
412,536✔
664

665
        Ok(Self {
412,536✔
666
            expr: bound,
412,536✔
667
            name_map: bound_name_map,
412,536✔
668
        })
412,536✔
669
    }
414,364✔
670

671
    /// Merge name maps.
672
    ///
673
    /// # Arguments
674
    ///
675
    /// * `other` - The other parameter expression whose symbols we add to self.
676
    ///
677
    /// # Returns
678
    ///
679
    /// * `Ok(HashMap<String, Symbol>)` - The merged name map.
680
    /// * `Err(ParameterError)` - An error if there was a name conflict.
681
    fn merged_name_map(&self, other: &Self) -> Result<HashMap<String, Symbol>, ParameterError> {
4,846,896✔
682
        let mut merged = self.name_map.clone();
4,846,896✔
683
        for (name, param) in other.name_map.iter() {
7,052,148✔
684
            match merged.get(name) {
7,051,068✔
685
                Some(existing_param) => {
3,990,500✔
686
                    if param != existing_param {
3,990,500✔
687
                        return Err(ParameterError::NameConflict);
8✔
688
                    }
3,990,492✔
689
                }
690
                None => {
3,060,568✔
691
                    // SAFETY: We ensured the key is unique
3,060,568✔
692
                    let _ = unsafe { merged.insert_unique_unchecked(name.clone(), param.clone()) };
3,060,568✔
693
                }
3,060,568✔
694
            }
695
        }
696
        Ok(merged)
4,846,888✔
697
    }
4,846,896✔
698
}
699

700
/// A parameter expression.
701
///
702
/// This is backed by Qiskit's symbolic expression engine and a cache
703
/// for the parameters inside the expression.
704
#[pyclass(
705
    subclass,
706
    module = "qiskit._accelerate.circuit",
707
    name = "ParameterExpression"
708
)]
709
#[derive(Clone, Debug)]
710
pub struct PyParameterExpression {
711
    pub inner: ParameterExpression,
712
}
713

714
impl Default for PyParameterExpression {
715
    /// The default constructor returns zero.
716
    fn default() -> Self {
×
717
        Self {
×
718
            inner: ParameterExpression::default(),
×
719
        }
×
720
    }
×
721
}
722

723
impl fmt::Display for PyParameterExpression {
724
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336,578✔
725
        self.inner.fmt(f)
336,578✔
726
    }
336,578✔
727
}
728

729
impl From<ParameterExpression> for PyParameterExpression {
730
    fn from(value: ParameterExpression) -> Self {
12,336,110✔
731
        Self { inner: value }
12,336,110✔
732
    }
12,336,110✔
733
}
734

735
impl PyParameterExpression {
736
    /// Attempt to extract a `PyParameterExpression` from a bound `PyAny`.
737
    ///
738
    /// This will try to coerce to the strictest data type:
739
    /// Int - Real - Complex - PyParameterVectorElement - PyParameter - PyParameterExpression.
740
    ///
741
    /// # Arguments:
742
    ///
743
    /// * ob - The bound `PyAny` to extract from.
744
    ///
745
    /// # Returns
746
    ///
747
    /// * `Ok(Self)` - The extracted expression.
748
    /// * `Err(PyResult)` - An error if extraction to all above types failed.
749
    pub fn extract_coerce(ob: Borrowed<PyAny>) -> PyResult<Self> {
4,960,326✔
750
        if let Ok(i) = ob.cast::<PyInt>() {
4,960,326✔
751
            Ok(ParameterExpression::new(
134,184✔
752
                SymbolExpr::Value(Value::from(i.extract::<i64>()?)),
134,184✔
753
                HashMap::new(),
134,184✔
754
            )
755
            .into())
134,184✔
756
        } else if let Ok(r) = ob.cast::<PyFloat>() {
4,826,142✔
757
            let r: f64 = r.extract()?;
13,386✔
758
            if r.is_infinite() || r.is_nan() {
13,386✔
759
                return Err(ParameterError::InvalidValue.into());
40✔
760
            }
13,346✔
761
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(r)), HashMap::new()).into())
13,346✔
762
        } else if let Ok(c) = ob.cast::<PyComplex>() {
4,812,756✔
763
            let c: Complex64 = c.extract()?;
2,361,356✔
764
            if c.is_infinite() || c.is_nan() {
2,361,356✔
765
                return Err(ParameterError::InvalidValue.into());
×
766
            }
2,361,356✔
767
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(c)), HashMap::new()).into())
2,361,356✔
768
        } else if let Ok(element) = ob.cast::<PyParameterVectorElement>() {
2,451,400✔
769
            Ok(ParameterExpression::from_symbol(element.borrow().symbol.clone()).into())
50,858✔
770
        } else if let Ok(parameter) = ob.cast::<PyParameter>() {
2,400,542✔
771
            Ok(ParameterExpression::from_symbol(parameter.borrow().symbol.clone()).into())
14,358✔
772
        } else {
773
            ob.extract::<PyParameterExpression>().map_err(Into::into)
2,386,184✔
774
        }
775
    }
4,960,326✔
776

777
    pub fn coerce_into_py(&self, py: Python) -> PyResult<Py<PyAny>> {
33,132✔
778
        if let Ok(value) = self.inner.try_to_value(true) {
33,132✔
779
            match value {
14✔
780
                Value::Int(i) => Ok(PyInt::new(py, i).unbind().into_any()),
×
781
                Value::Real(r) => Ok(PyFloat::new(py, r).unbind().into_any()),
14✔
782
                Value::Complex(c) => Ok(PyComplex::from_complex_bound(py, c).unbind().into_any()),
×
783
            }
784
        } else if let Ok(symbol) = self.inner.try_to_symbol() {
33,118✔
785
            if symbol.index.is_some() {
23,192✔
786
                Ok(Py::new(py, PyParameterVectorElement::from_symbol(symbol))?.into_any())
5,936✔
787
            } else {
788
                Ok(Py::new(py, PyParameter::from_symbol(symbol))?.into_any())
17,256✔
789
            }
790
        } else {
791
            self.clone().into_py_any(py)
9,926✔
792
        }
793
    }
33,132✔
794
}
795

796
#[pymethods]
×
797
impl PyParameterExpression {
798
    /// This is a **strictly internal** constructor and **should not be used**.
799
    /// It is subject to arbitrary change in between Qiskit versions and cannot be relied on.
800
    /// Parameter expressions should always be constructed from applying operations on
801
    /// parameters, or by loading via QPY.
802
    ///
803
    /// The input values are allowed to be None for pickling purposes.
804
    #[new]
805
    #[pyo3(signature = (name_map=None, expr=None))]
806
    pub fn py_new(
70✔
807
        name_map: Option<HashMap<String, PyParameter>>,
70✔
808
        expr: Option<String>,
70✔
809
    ) -> PyResult<Self> {
70✔
810
        match (name_map, expr) {
70✔
811
            (None, None) => Ok(Self::default()),
×
812
            (Some(name_map), Some(expr)) => {
70✔
813
                // We first parse the expression and then update the symbols with the ones
814
                // the user provided. The replacement relies on the names to match.
815
                // This is hacky and we likely want a more reliably conversion from a SymPy object,
816
                // if we decide we want to continue supporting this.
817
                let expr = parse_expression(&expr)
70✔
818
                    .map_err(|_| PyRuntimeError::new_err("Failed parsing input expression"))?;
70✔
819
                let symbol_map: HashMap<String, Symbol> = name_map
70✔
820
                    .iter()
70✔
821
                    .map(|(string, param)| (string.clone(), param.symbol.clone()))
70✔
822
                    .collect();
70✔
823

824
                let replaced_expr = symbol_expr::replace_symbol(&expr, &symbol_map);
70✔
825

826
                let inner = ParameterExpression::new(replaced_expr, symbol_map);
70✔
827
                Ok(Self { inner })
70✔
828
            }
829
            _ => Err(PyValueError::new_err(
×
830
                "Pass either both a name_map and expr, or neither",
×
831
            )),
×
832
        }
833
    }
70✔
834

835
    #[allow(non_snake_case)]
836
    #[staticmethod]
837
    pub fn _Value(value: &Bound<PyAny>) -> PyResult<Self> {
34✔
838
        Self::extract_coerce(value.as_borrowed())
34✔
839
    }
34✔
840

841
    /// Check if the expression corresponds to a plain symbol.
842
    ///
843
    /// Returns:
844
    ///     ``True`` is this expression corresponds to a symbol, ``False`` otherwise.
845
    pub fn is_symbol(&self) -> bool {
480✔
846
        matches!(self.inner.expr, SymbolExpr::Symbol(_))
480✔
847
    }
480✔
848

849
    /// Cast this expression to a numeric value.
850
    ///
851
    /// Args:
852
    ///     strict: If ``True`` (default) this function raises an error if there are any
853
    ///         unbound symbols in the expression. If ``False``, this allows casting
854
    ///         if the expression represents a numeric value, regardless of unbound symbols.
855
    ///         For example ``(0 * Parameter("x"))`` is 0 but has the symbol ``x`` present.
856
    #[pyo3(signature = (strict=true))]
857
    pub fn numeric(&self, py: Python, strict: bool) -> PyResult<Py<PyAny>> {
47,702✔
858
        match self.inner.try_to_value(strict)? {
47,702✔
859
            Value::Real(r) => r.into_py_any(py),
21,302✔
860
            Value::Int(i) => i.into_py_any(py),
11,792✔
861
            Value::Complex(c) => c.into_py_any(py),
14,488✔
862
        }
863
    }
47,702✔
864

865
    /// Return a SymPy equivalent of this expression.
866
    ///
867
    /// Returns:
868
    ///     A SymPy equivalent of this expression.
869
    pub fn sympify(&self, py: Python) -> PyResult<Py<PyAny>> {
480✔
870
        let py_sympify = SYMPIFY_PARAMETER_EXPRESSION.get(py);
480✔
871
        py_sympify.call1(py, (self.clone(),))
480✔
872
    }
480✔
873

874
    /// The number of unbound parameters in the expression.
875
    ///
876
    /// This is equivalent to ``len(expr.parameters)`` but does not involve the overhead of creating
877
    /// a set and counting its length.
878
    #[getter]
879
    pub fn num_parameters(&self) -> usize {
6✔
880
        self.inner.num_symbols()
6✔
881
    }
6✔
882

883
    /// Get the parameters present in the expression.
884
    ///
885
    /// .. note::
886
    ///
887
    ///     Qiskit guarantees equality (via ``==``) of parameters retrieved from an expression
888
    ///     with the original :class:`.Parameter` objects used to create this expression,
889
    ///     but does **not guarantee** ``is`` comparisons to succeed.
890
    ///
891
    #[getter]
892
    pub fn parameters<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PySet>> {
135,744✔
893
        let py_parameters: Vec<Py<PyAny>> = self
135,744✔
894
            .inner
135,744✔
895
            .iter_symbols()
135,744✔
896
            .map(|symbol| {
4,563,774✔
897
                if symbol.is_vector_element() {
4,563,774✔
898
                    Ok(
899
                        Py::new(py, PyParameterVectorElement::from_symbol(symbol.clone()))?
4,497,290✔
900
                            .into_any(),
4,497,290✔
901
                    )
902
                } else {
903
                    Ok(Py::new(py, PyParameter::from_symbol(symbol.clone()))?.into_any())
66,484✔
904
                }
905
            })
4,563,774✔
906
            .collect::<PyResult<_>>()?;
135,744✔
907
        PySet::new(py, py_parameters)
135,744✔
908
    }
135,744✔
909

910
    /// Sine of the expression.
911
    #[inline]
912
    #[pyo3(name = "sin")]
913
    pub fn py_sin(&self) -> Self {
356✔
914
        self.inner.sin().into()
356✔
915
    }
356✔
916

917
    /// Cosine of the expression.
918
    #[inline]
919
    #[pyo3(name = "cos")]
920
    pub fn py_cos(&self) -> Self {
350✔
921
        self.inner.cos().into()
350✔
922
    }
350✔
923

924
    /// Tangent of the expression.
925
    #[inline]
926
    #[pyo3(name = "tan")]
927
    pub fn py_tan(&self) -> Self {
350✔
928
        self.inner.tan().into()
350✔
929
    }
350✔
930

931
    /// Arcsine of the expression.
932
    #[inline]
933
    pub fn arcsin(&self) -> Self {
98✔
934
        self.inner.asin().into()
98✔
935
    }
98✔
936

937
    /// Arccosine of the expression.
938
    #[inline]
939
    pub fn arccos(&self) -> Self {
98✔
940
        self.inner.acos().into()
98✔
941
    }
98✔
942

943
    /// Arctangent of the expression.
944
    #[inline]
945
    pub fn arctan(&self) -> Self {
98✔
946
        self.inner.atan().into()
98✔
947
    }
98✔
948

949
    /// Exponentiate the expression.
950
    #[inline]
951
    #[pyo3(name = "exp")]
952
    pub fn py_exp(&self) -> Self {
348✔
953
        self.inner.exp().into()
348✔
954
    }
348✔
955

956
    /// Take the natural logarithm of the expression.
957
    #[inline]
958
    #[pyo3(name = "log")]
959
    pub fn py_log(&self) -> Self {
264✔
960
        self.inner.log().into()
264✔
961
    }
264✔
962

963
    /// Take the absolute value of the expression.
964
    #[inline]
965
    #[pyo3(name = "abs")]
966
    pub fn py_abs(&self) -> Self {
10✔
967
        self.inner.abs().into()
10✔
968
    }
10✔
969

970
    /// Return the sign of the expression.
971
    #[inline]
972
    #[pyo3(name = "sign")]
973
    pub fn py_sign(&self) -> Self {
10✔
974
        self.inner.sign().into()
10✔
975
    }
10✔
976

977
    /// Return the complex conjugate of the expression.
978
    #[inline]
979
    #[pyo3(name = "conjugate")]
980
    pub fn py_conjugate(&self) -> Self {
1,830✔
981
        self.inner.conjugate().into()
1,830✔
982
    }
1,830✔
983

984
    /// Check whether the expression represents a real number.
985
    ///
986
    /// Note that this will return ``None`` if there are unbound parameters, in which case
987
    /// it cannot be determined whether the expression is real.
988
    #[inline]
989
    #[pyo3(name = "is_real")]
990
    pub fn py_is_real(&self) -> Option<bool> {
270✔
991
        self.inner.expr.is_real()
270✔
992
    }
270✔
993

994
    /// Return derivative of this expression with respect to the input parameter.
995
    ///
996
    /// Args:
997
    ///     param: The parameter with respect to which the derivative is calculated.
998
    ///
999
    /// Returns:
1000
    ///     The derivative as either a constant numeric value or a symbolic
1001
    ///     :class:`.ParameterExpression`.
1002
    pub fn gradient(&self, param: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
74✔
1003
        let symbol = symbol_from_py_parameter(param)?;
74✔
1004
        let d_expr = self.inner.derivative(&symbol)?;
74✔
1005

1006
        // try converting to value and return as built-in numeric type
1007
        match d_expr.try_to_value(false) {
70✔
1008
            Ok(val) => match val {
20✔
1009
                Value::Real(r) => r.into_py_any(param.py()),
18✔
1010
                Value::Int(i) => i.into_py_any(param.py()),
2✔
1011
                Value::Complex(c) => c.into_py_any(param.py()),
×
1012
            },
1013
            Err(_) => PyParameterExpression::from(d_expr).into_py_any(param.py()),
50✔
1014
        }
1015
    }
74✔
1016

1017
    /// Return all values in this equation.
1018
    pub fn _values(&self, py: Python) -> PyResult<Vec<Py<PyAny>>> {
78✔
1019
        self.inner
78✔
1020
            .expr
78✔
1021
            .values()
78✔
1022
            .iter()
78✔
1023
            .map(|val| match val {
78✔
1024
                Value::Real(r) => r.into_py_any(py),
34✔
1025
                Value::Int(i) => i.into_py_any(py),
10✔
1026
                Value::Complex(c) => c.into_py_any(py),
×
1027
            })
44✔
1028
            .collect()
78✔
1029
    }
78✔
1030

1031
    /// Returns a new expression with replacement parameters.
1032
    ///
1033
    /// Args:
1034
    ///     parameter_map: Mapping from :class:`.Parameter`\ s in ``self`` to the
1035
    ///         :class:`.ParameterExpression` instances with which they should be replaced.
1036
    ///     allow_unknown_parameters: If ``False``, raises an error if ``parameter_map``
1037
    ///         contains :class:`.Parameter`\ s in the keys outside those present in the expression.
1038
    ///         If ``True``, any such parameters are simply ignored.
1039
    ///
1040
    /// Raises:
1041
    ///     CircuitError:
1042
    ///         - If parameter_map contains parameters outside those in self.
1043
    ///         - If the replacement parameters in ``parameter_map`` would result in
1044
    ///           a name conflict in the generated expression.
1045
    ///
1046
    /// Returns:
1047
    ///     A new expression with the specified parameters replaced.
1048
    #[pyo3(name = "subs")]
1049
    #[pyo3(signature = (parameter_map, allow_unknown_parameters=false))]
1050
    pub fn py_subs(
228✔
1051
        &self,
228✔
1052
        parameter_map: HashMap<PyParameter, Self>,
228✔
1053
        allow_unknown_parameters: bool,
228✔
1054
    ) -> PyResult<Self> {
228✔
1055
        // reduce the map to a HashMap<Symbol, ParameterExpression>
1056
        let map = parameter_map
228✔
1057
            .into_iter()
228✔
1058
            .map(|(param, expr)| Ok((param.symbol, expr.inner)))
232✔
1059
            .collect::<PyResult<_>>()?;
228✔
1060

1061
        // apply to the inner expression
1062
        match self.inner.subs(&map, allow_unknown_parameters) {
228✔
1063
            Ok(subbed) => Ok(subbed.into()),
224✔
1064
            Err(e) => Err(e.into()),
4✔
1065
        }
1066
    }
228✔
1067

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

1103
        // apply to the inner expression
1104
        match self.inner.bind(&map, allow_unknown_parameters) {
127,050✔
1105
            Ok(bound) => Ok(bound.into()),
125,222✔
1106
            Err(e) => Err(e.into()),
1,828✔
1107
        }
1108
    }
127,050✔
1109

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

1144
    /// Assign one parameter to a value, which can either be numeric or another parameter
1145
    /// expression.
1146
    ///
1147
    /// Args:
1148
    ///     parameter: A parameter in this expression whose value will be updated.
1149
    ///     value: The new value to bind to.
1150
    ///
1151
    /// Returns:
1152
    ///     A new expression parameterized by any parameters which were not bound by assignment.
1153
    #[pyo3(name = "assign")]
1154
    pub fn py_assign(&self, parameter: PyParameter, value: &Bound<PyAny>) -> PyResult<Self> {
174✔
1155
        if let Ok(expr) = value.cast::<Self>() {
174✔
1156
            let map = [(parameter, expr.borrow().clone())].into_iter().collect();
150✔
1157
            self.py_subs(map, false)
150✔
1158
        } else if value.extract::<Value>().is_ok() {
24✔
1159
            let map = [(parameter, value.clone())].into_iter().collect();
24✔
1160
            self.py_bind(map, false)
24✔
1161
        } else {
1162
            Err(PyValueError::new_err(
×
1163
                "Unexpected value in assign: {replacement:?}",
×
1164
            ))
×
1165
        }
1166
    }
174✔
1167

1168
    #[inline]
1169
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1170
        // ParameterExpression is immutable.
1171
        slf
×
1172
    }
×
1173

1174
    #[inline]
1175
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
1,588✔
1176
        // Everything a ParameterExpression contains is immutable.
1177
        slf
1,588✔
1178
    }
1,588✔
1179

1180
    pub fn __eq__(&self, rhs: &Bound<PyAny>) -> PyResult<bool> {
33,462✔
1181
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
33,462✔
1182
            match rhs.inner.expr {
33,450✔
1183
                SymbolExpr::Value(v) => match self.inner.try_to_value(false) {
2,732✔
1184
                    Ok(e) => Ok(e == v),
2,518✔
1185
                    Err(_) => Ok(false),
214✔
1186
                },
1187
                _ => Ok(self.inner.expr == rhs.inner.expr),
30,718✔
1188
            }
1189
        } else {
1190
            Ok(false)
12✔
1191
        }
1192
    }
33,462✔
1193

1194
    #[inline]
1195
    pub fn __abs__(&self) -> Self {
362✔
1196
        self.inner.abs().into()
362✔
1197
    }
362✔
1198

1199
    #[inline]
1200
    pub fn __pos__(&self) -> Self {
4✔
1201
        self.clone()
4✔
1202
    }
4✔
1203

1204
    pub fn __neg__(&self) -> Self {
664✔
1205
        Self {
664✔
1206
            inner: ParameterExpression::new(-&self.inner.expr, self.inner.name_map.clone()),
664✔
1207
        }
664✔
1208
    }
664✔
1209

1210
    pub fn __add__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
2,318,296✔
1211
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
2,318,296✔
1212
            Ok(self.inner.add(&rhs.inner)?.into())
2,318,284✔
1213
        } else {
1214
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1215
                "Unsupported data type for __add__",
12✔
1216
            ))
12✔
1217
        }
1218
    }
2,318,296✔
1219

1220
    pub fn __radd__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
63,928✔
1221
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
63,928✔
1222
            Ok(lhs.inner.add(&self.inner)?.into())
63,916✔
1223
        } else {
1224
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1225
                "Unsupported data type for __radd__",
12✔
1226
            ))
12✔
1227
        }
1228
    }
63,928✔
1229

1230
    pub fn __sub__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
18,454✔
1231
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
18,454✔
1232
            Ok(self.inner.sub(&rhs.inner)?.into())
18,442✔
1233
        } else {
1234
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1235
                "Unsupported data type for __sub__",
12✔
1236
            ))
12✔
1237
        }
1238
    }
18,454✔
1239

1240
    pub fn __rsub__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
4,318✔
1241
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
4,318✔
1242
            Ok(lhs.inner.sub(&self.inner)?.into())
4,306✔
1243
        } else {
1244
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1245
                "Unsupported data type for __rsub__",
12✔
1246
            ))
12✔
1247
        }
1248
    }
4,318✔
1249

1250
    pub fn __mul__<'py>(&self, rhs: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
2,408,012✔
1251
        let py = rhs.py();
2,408,012✔
1252
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
2,408,012✔
1253
            match self.inner.mul(&rhs.inner) {
2,400,652✔
1254
                Ok(result) => PyParameterExpression::from(result).into_bound_py_any(py),
2,400,650✔
1255
                Err(e) => Err(PyErr::from(e)),
2✔
1256
            }
1257
        } else {
1258
            PyNotImplemented::get(py).into_bound_py_any(py)
7,360✔
1259
        }
1260
    }
2,408,012✔
1261

1262
    pub fn __rmul__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
12,034✔
1263
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
12,034✔
1264
            Ok(lhs.inner.mul(&self.inner)?.into())
12,022✔
1265
        } else {
1266
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1267
                "Unsupported data type for __rmul__",
12✔
1268
            ))
12✔
1269
        }
1270
    }
12,034✔
1271

1272
    pub fn __truediv__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
8,582✔
1273
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
8,582✔
1274
            Ok(self.inner.div(&rhs.inner)?.into())
8,570✔
1275
        } else {
1276
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1277
                "Unsupported data type for __truediv__",
12✔
1278
            ))
12✔
1279
        }
1280
    }
8,582✔
1281

1282
    pub fn __rtruediv__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
4,066✔
1283
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
4,066✔
1284
            Ok(lhs.inner.div(&self.inner)?.into())
4,054✔
1285
        } else {
1286
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1287
                "Unsupported data type for __rtruediv__",
12✔
1288
            ))
12✔
1289
        }
1290
    }
4,066✔
1291

1292
    pub fn __pow__(&self, rhs: &Bound<PyAny>, _modulo: Option<i32>) -> PyResult<Self> {
1,990✔
1293
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
1,990✔
1294
            Ok(self.inner.pow(&rhs.inner)?.into())
1,978✔
1295
        } else {
1296
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1297
                "Unsupported data type for __pow__",
12✔
1298
            ))
12✔
1299
        }
1300
    }
1,990✔
1301

1302
    pub fn __rpow__(&self, lhs: &Bound<PyAny>, _modulo: Option<i32>) -> PyResult<Self> {
1,038✔
1303
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
1,038✔
1304
            Ok(lhs.inner.pow(&self.inner)?.into())
1,026✔
1305
        } else {
1306
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1307
                "Unsupported data type for __rpow__",
12✔
1308
            ))
12✔
1309
        }
1310
    }
1,038✔
1311

1312
    pub fn __int__(&self) -> PyResult<i64> {
176✔
1313
        match self.inner.try_to_value(false)? {
176✔
1314
            Value::Complex(_) => Err(PyTypeError::new_err(
42✔
1315
                "Cannot cast complex parameter to int.",
42✔
1316
            )),
42✔
1317
            Value::Real(r) => {
90✔
1318
                let rounded = r.floor();
90✔
1319
                Ok(rounded as i64)
90✔
1320
            }
1321
            Value::Int(i) => Ok(i),
42✔
1322
        }
1323
    }
176✔
1324

1325
    pub fn __float__(&self) -> PyResult<f64> {
668✔
1326
        match self.inner.try_to_value(false)? {
668✔
1327
            Value::Complex(c) => {
44✔
1328
                if c.im.abs() > SYMEXPR_EPSILON {
44✔
1329
                    Err(PyTypeError::new_err(
44✔
1330
                        "Could not cast complex parameter expression to float.",
44✔
1331
                    ))
44✔
1332
                } else {
1333
                    Ok(c.re)
×
1334
                }
1335
            }
1336
            Value::Real(r) => Ok(r),
114✔
1337
            Value::Int(i) => Ok(i as f64),
56✔
1338
        }
1339
    }
668✔
1340

1341
    pub fn __complex__(&self) -> PyResult<Complex64> {
77,856✔
1342
        match self.inner.try_to_value(false)? {
77,856✔
1343
            Value::Complex(c) => Ok(c),
25,276✔
1344
            Value::Real(r) => Ok(Complex64::new(r, 0.)),
38,100✔
1345
            Value::Int(i) => Ok(Complex64::new(i as f64, 0.)),
14,478✔
1346
        }
1347
    }
77,856✔
1348

1349
    pub fn __str__(&self) -> String {
336,578✔
1350
        self.to_string()
336,578✔
1351
    }
336,578✔
1352

1353
    pub fn __hash__(&self, py: Python) -> PyResult<u64> {
9,164,428✔
1354
        match self.inner.try_to_value(false) {
9,164,428✔
1355
            // if a value, we promise to match the hash of the raw value!
1356
            Ok(value) => {
2✔
1357
                let py_hash = BUILTIN_HASH.get_bound(py);
2✔
1358
                match value {
2✔
1359
                    Value::Complex(c) => py_hash.call1((c,))?.extract::<u64>(),
×
1360
                    Value::Real(r) => py_hash.call1((r,))?.extract::<u64>(),
2✔
1361
                    Value::Int(i) => py_hash.call1((i,))?.extract::<u64>(),
×
1362
                }
1363
            }
1364
            Err(_) => {
1365
                let mut hasher = DefaultHasher::new();
9,164,426✔
1366
                self.inner.expr.string_id().hash(&mut hasher);
9,164,426✔
1367
                Ok(hasher.finish())
9,164,426✔
1368
            }
1369
        }
1370
    }
9,164,428✔
1371

1372
    fn __getstate__(&self) -> PyResult<(Vec<OPReplay>, Option<ParameterValueType>)> {
8,316✔
1373
        // We distinguish in two cases:
1374
        //  (a) This is indeed an expression which can be rebuild from the QPY replay. This means
1375
        //      the replay is *not empty* and it contains all symbols.
1376
        //  (b) This expression is in fact only a Value or a Symbol. In this case, the QPY replay
1377
        //      will be empty and we instead pass a `ParameterValueType` for reconstruction.
1378
        let qpy = self._qpy_replay()?;
8,316✔
1379
        if !qpy.is_empty() {
8,316✔
1380
            Ok((qpy, None))
8,316✔
1381
        } else {
1382
            let value = ParameterValueType::extract_from_expr(&self.inner.expr);
×
1383
            if value.is_none() {
×
1384
                Err(PyValueError::new_err(format!(
×
1385
                    "Failed to serialize the parameter expression: {self:?}"
×
1386
                )))
×
1387
            } else {
1388
                Ok((qpy, value))
×
1389
            }
1390
        }
1391
    }
8,316✔
1392

1393
    fn __setstate__(&mut self, state: (Vec<OPReplay>, Option<ParameterValueType>)) -> PyResult<()> {
×
1394
        // if there a replay, load from the replay
1395
        if !state.0.is_empty() {
×
1396
            let from_qpy = ParameterExpression::from_qpy(&state.0, None)?;
×
1397
            self.inner = from_qpy;
×
1398
        // otherwise, load from the ParameterValueType
1399
        } else if let Some(value) = state.1 {
×
1400
            let expr = ParameterExpression::from(value);
×
1401
            self.inner = expr;
×
1402
        } else {
×
1403
            return Err(PyValueError::new_err(
×
1404
                "Failed to read QPY replay or extract value.",
×
1405
            ));
×
1406
        }
1407
        Ok(())
×
1408
    }
×
1409

1410
    #[getter]
1411
    fn _qpy_replay(&self) -> PyResult<Vec<OPReplay>> {
8,434✔
1412
        let mut replay = Vec::new();
8,434✔
1413
        qpy_replay(&self.inner, &self.inner.name_map, &mut replay);
8,434✔
1414
        Ok(replay)
8,434✔
1415
    }
8,434✔
1416
}
1417

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

1457
impl Hash for PyParameter {
1458
    fn hash<H: Hasher>(&self, state: &mut H) {
4,556,382✔
1459
        self.symbol.hash(state);
4,556,382✔
1460
    }
4,556,382✔
1461
}
1462

1463
// This needs to be implemented manually, since PyO3 does not provide this conversion
1464
// for subclasses.
1465
impl<'py> IntoPyObject<'py> for PyParameter {
1466
    type Target = PyParameter;
1467
    type Output = Bound<'py, Self::Target>;
1468
    type Error = PyErr;
1469

1470
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
9,450✔
1471
        let symbol = &self.symbol;
9,450✔
1472
        let symbol_expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
9,450✔
1473
        let expr = ParameterExpression::from_symbol_expr(symbol_expr);
9,450✔
1474
        let py_expr = PyParameterExpression::from(expr);
9,450✔
1475

1476
        Ok(Py::new(py, (self, py_expr))?.into_bound(py))
9,450✔
1477
    }
9,450✔
1478
}
1479

1480
impl PyParameter {
1481
    /// Get a Python class initialization from a symbol.
1482
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
4,672,648✔
1483
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
4,672,648✔
1484

1485
        let py_parameter = Self { symbol };
4,672,648✔
1486
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
4,672,648✔
1487

1488
        PyClassInitializer::from(py_expr).add_subclass(py_parameter)
4,672,648✔
1489
    }
4,672,648✔
1490

1491
    /// Get a reference to the underlying symbol.
1492
    pub fn symbol(&self) -> &Symbol {
4,557,122✔
1493
        &self.symbol
4,557,122✔
1494
    }
4,557,122✔
1495
}
1496

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

1519
        let py_parameter = Self { symbol };
84,868✔
1520
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
84,868✔
1521

1522
        Ok(PyClassInitializer::from(py_expr).add_subclass(py_parameter))
84,868✔
1523
    }
84,868✔
1524

1525
    /// Returns the name of the :class:`.Parameter`.
1526
    #[getter]
1527
    fn name<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
21,218✔
1528
        PyString::new(py, &self.symbol.repr(false))
21,218✔
1529
    }
21,218✔
1530

1531
    /// Returns the :class:`~uuid.UUID` of the :class:`Parameter`.
1532
    ///
1533
    /// In advanced use cases, this property can be passed to the
1534
    /// :class:`.Parameter` constructor to produce an instance that compares
1535
    /// equal to another instance.
1536
    #[getter]
1537
    fn uuid(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
48✔
1538
        uuid_to_py(py, self.symbol.uuid)
48✔
1539
    }
48✔
1540

1541
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
64✔
1542
        let str = format!("Parameter({})", self.symbol.repr(false),);
64✔
1543
        PyString::new(py, str.as_str())
64✔
1544
    }
64✔
1545

1546
    pub fn __getnewargs__(&self) -> (String, u128) {
25,008✔
1547
        (self.symbol.repr(false), self.symbol.uuid.as_u128())
25,008✔
1548
    }
25,008✔
1549

1550
    pub fn __getstate__(&self) -> (String, u128) {
25,008✔
1551
        (self.symbol.repr(false), self.symbol.uuid.as_u128())
25,008✔
1552
    }
25,008✔
1553

1554
    pub fn __setstate__(&mut self, state: (String, u128)) {
16✔
1555
        let name = state.0.as_str();
16✔
1556
        let uuid = Uuid::from_u128(state.1);
16✔
1557
        let symbol = Symbol::new(name, Some(uuid), None);
16✔
1558
        self.symbol = symbol;
16✔
1559
    }
16✔
1560

1561
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1562
        // Parameter is immutable. Note that this **cannot** be deferred to the parent class
1563
        // since PyO3 would then always return the parent type.
1564
        slf
×
1565
    }
×
1566

1567
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
151,016✔
1568
        // Everything inside a Parameter is immutable. Note that this **cannot** be deferred to the
1569
        // parent class since PyO3 would then always return the parent type.
1570
        slf
151,016✔
1571
    }
151,016✔
1572

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

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

1643
    #[pyo3(name = "bind_all")]
1644
    #[pyo3(signature = (values, *))]
1645
    pub fn py_bind_all<'py>(
4✔
1646
        slf_: Bound<'py, Self>,
4✔
1647
        values: Bound<'py, PyAny>,
4✔
1648
    ) -> PyResult<Bound<'py, PyAny>> {
4✔
1649
        values.get_item(slf_)
4✔
1650
    }
4✔
1651

1652
    #[pyo3(name = "assign")]
1653
    pub fn py_assign<'py>(
408✔
1654
        &self,
408✔
1655
        py: Python<'py>,
408✔
1656
        parameter: PyParameter,
408✔
1657
        value: &Bound<'py, PyAny>,
408✔
1658
    ) -> PyResult<Bound<'py, PyAny>> {
408✔
1659
        if value.cast::<PyParameterExpression>().is_ok() {
408✔
1660
            let map = [(parameter, value.clone())].into_iter().collect();
408✔
1661
            self.py_subs(py, map, false)
408✔
1662
        } else if value.extract::<Value>().is_ok() {
×
1663
            let map = [(parameter, value.clone())].into_iter().collect();
×
1664
            self.py_bind(py, map, false)
×
1665
        } else {
1666
            Err(PyValueError::new_err(
×
1667
                "Unexpected value in assign: {replacement:?}",
×
1668
            ))
×
1669
        }
1670
    }
408✔
1671
}
1672

1673
/// An element of a :class:`.ParameterVector`.
1674
///
1675
/// .. note::
1676
///     There is very little reason to ever construct this class directly.  Objects of this type are
1677
///     automatically constructed efficiently as part of creating a :class:`.ParameterVector`.
1678
#[pyclass(sequence, subclass, module="qiskit._accelerate.circuit", extends=PyParameter, name="ParameterVectorElement")]
1679
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
1680
pub struct PyParameterVectorElement {
1681
    pub symbol: Symbol,
1682
}
1683

1684
impl<'py> IntoPyObject<'py> for PyParameterVectorElement {
1685
    type Target = PyParameterVectorElement;
1686
    type Output = Bound<'py, Self::Target>;
1687
    type Error = PyErr;
1688

1689
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
10✔
1690
        let symbol = &self.symbol;
10✔
1691
        let py_param = PyParameter::from_symbol(symbol.clone());
10✔
1692
        let py_element = py_param.add_subclass(self);
10✔
1693

1694
        Ok(Py::new(py, py_element)?.into_bound(py))
10✔
1695
    }
10✔
1696
}
1697

1698
impl PyParameterVectorElement {
1699
    pub fn symbol(&self) -> &Symbol {
16,798✔
1700
        &self.symbol
16,798✔
1701
    }
16,798✔
1702

1703
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
4,556,124✔
1704
        let py_element = Self {
4,556,124✔
1705
            symbol: symbol.clone(),
4,556,124✔
1706
        };
4,556,124✔
1707
        let py_parameter = PyParameter::from_symbol(symbol);
4,556,124✔
1708

1709
        py_parameter.add_subclass(py_element)
4,556,124✔
1710
    }
4,556,124✔
1711
}
1712

1713
#[pymethods]
×
1714
impl PyParameterVectorElement {
1715
    #[new]
1716
    #[pyo3(signature = (vector, index, uuid=None))]
1717
    pub fn py_new(
27,622✔
1718
        py: Python<'_>,
27,622✔
1719
        vector: Py<PyAny>,
27,622✔
1720
        index: u32,
27,622✔
1721
        uuid: Option<Py<PyAny>>,
27,622✔
1722
    ) -> PyResult<PyClassInitializer<Self>> {
27,622✔
1723
        let vector_name = vector.getattr(py, "name")?.extract::<String>(py)?;
27,622✔
1724
        let uuid = uuid_from_py(py, uuid)?.unwrap_or(Uuid::new_v4());
27,622✔
1725

1726
        let symbol = Symbol::py_new(
27,622✔
1727
            &vector_name,
27,622✔
1728
            Some(uuid.as_u128()),
27,622✔
1729
            Some(index),
27,622✔
1730
            Some(vector.clone_ref(py)),
27,622✔
1731
        )?;
×
1732

1733
        let py_parameter = PyParameter::from_symbol(symbol.clone());
27,622✔
1734
        let py_element = Self { symbol };
27,622✔
1735

1736
        Ok(py_parameter.add_subclass(py_element))
27,622✔
1737
    }
27,622✔
1738

1739
    pub fn __getnewargs__(&self, py: Python) -> PyResult<(Py<PyAny>, u32, Option<Py<PyAny>>)> {
8✔
1740
        let vector = self
8✔
1741
            .symbol
8✔
1742
            .vector
8✔
1743
            .clone()
8✔
1744
            .expect("vector element should have a vector");
8✔
1745
        let index = self
8✔
1746
            .symbol
8✔
1747
            .index
8✔
1748
            .expect("vector element should have an index");
8✔
1749
        let uuid = uuid_to_py(py, self.symbol.uuid)?;
8✔
1750
        Ok((vector, index, Some(uuid)))
8✔
1751
    }
8✔
1752

1753
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
48✔
1754
        let str = format!("ParameterVectorElement({})", self.symbol.repr(false),);
48✔
1755
        PyString::new(py, str.as_str())
48✔
1756
    }
48✔
1757

1758
    pub fn __getstate__(&self, py: Python) -> PyResult<(Py<PyAny>, u32, Option<Py<PyAny>>)> {
8✔
1759
        self.__getnewargs__(py)
8✔
1760
    }
8✔
1761

1762
    pub fn __setstate__(
×
1763
        &mut self,
×
1764
        py: Python,
×
1765
        state: (Py<PyAny>, u32, Option<Py<PyAny>>),
×
1766
    ) -> PyResult<()> {
×
1767
        let vector = state.0;
×
1768
        let index = state.1;
×
1769
        let vector_name = vector.getattr(py, "name")?.extract::<String>(py)?;
×
1770
        let uuid = uuid_from_py(py, state.2)?.map(|id| id.as_u128());
×
1771
        self.symbol = Symbol::py_new(&vector_name, uuid, Some(index), Some(vector))?;
×
1772
        Ok(())
×
1773
    }
×
1774

1775
    /// Get the index of this element in the parent vector.
1776
    #[getter]
1777
    pub fn index(&self) -> u32 {
12✔
1778
        self.symbol
12✔
1779
            .index
12✔
1780
            .expect("A vector element should have an index")
12✔
1781
    }
12✔
1782

1783
    /// Get the parent vector instance.
1784
    #[getter]
1785
    pub fn vector(&self) -> Py<PyAny> {
20✔
1786
        self.symbol
20✔
1787
            .clone()
20✔
1788
            .vector
20✔
1789
            .expect("A vector element should have a vector")
20✔
1790
    }
20✔
1791

1792
    /// For backward compatibility only. This should not be used and we ought to update those
1793
    /// usages!
1794
    #[getter]
1795
    pub fn _vector(&self) -> Py<PyAny> {
×
1796
        self.vector()
×
1797
    }
×
1798

1799
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1800
        // ParameterVectorElement is immutable.
1801
        slf
×
1802
    }
×
1803

1804
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
10,988✔
1805
        // Everything a ParameterVectorElement contains is immutable.
1806
        slf
10,988✔
1807
    }
10,988✔
1808
}
1809

1810
/// Try to extract a Uuid from a Python object, which could be a Python UUID or int.
1811
fn uuid_from_py(py: Python<'_>, uuid: Option<Py<PyAny>>) -> PyResult<Option<Uuid>> {
112,490✔
1812
    if let Some(val) = uuid {
112,490✔
1813
        // construct from u128
1814
        let as_u128 = if let Ok(as_u128) = val.extract::<u128>(py) {
27,674✔
1815
            as_u128
16✔
1816
        // construct from Python UUID type
1817
        } else if val.bind(py).is_exact_instance(UUID.get_bound(py)) {
27,658✔
1818
            val.getattr(py, "int")?.extract::<u128>(py)?
27,658✔
1819
        // invalid format
1820
        } else {
1821
            return Err(PyTypeError::new_err("not a UUID!"));
×
1822
        };
1823
        Ok(Some(Uuid::from_u128(as_u128)))
27,674✔
1824
    } else {
1825
        Ok(None)
84,816✔
1826
    }
1827
}
112,490✔
1828

1829
/// Convert a Rust Uuid object to a Python UUID object.
1830
fn uuid_to_py(py: Python<'_>, uuid: Uuid) -> PyResult<Py<PyAny>> {
56✔
1831
    let uuid = uuid.as_u128();
56✔
1832
    let kwargs = [("int", uuid)].into_py_dict(py)?;
56✔
1833
    Ok(UUID.get_bound(py).call((), Some(&kwargs))?.unbind())
56✔
1834
}
56✔
1835

1836
/// Extract a [Symbol] for a Python object, which could either be a Parameter or a
1837
/// ParameterVectorElement.
1838
fn symbol_from_py_parameter(param: &Bound<'_, PyAny>) -> PyResult<Symbol> {
74✔
1839
    if let Ok(element) = param.extract::<PyParameterVectorElement>() {
74✔
1840
        Ok(element.symbol.clone())
×
1841
    } else if let Ok(parameter) = param.extract::<PyParameter>() {
74✔
1842
        Ok(parameter.symbol.clone())
74✔
1843
    } else {
1844
        Err(PyValueError::new_err("Could not extract parameter"))
×
1845
    }
1846
}
74✔
1847

1848
/// A singular parameter value used for QPY serialization. This covers anything
1849
/// but a [PyParameterExpression], which is represented by [None] in the serialization.
1850
#[derive(IntoPyObject, FromPyObject, Clone, Debug)]
1851
pub enum ParameterValueType {
1852
    Int(i64),
1853
    Float(f64),
1854
    Complex(Complex64),
1855
    Parameter(PyParameter),
1856
    VectorElement(PyParameterVectorElement),
1857
}
1858

1859
impl ParameterValueType {
1860
    fn extract_from_expr(expr: &SymbolExpr) -> Option<ParameterValueType> {
18,910✔
1861
        if let Some(value) = expr.eval(true) {
18,910✔
1862
            match value {
7,760✔
1863
                Value::Int(i) => Some(ParameterValueType::Int(i)),
100✔
1864
                Value::Real(r) => Some(ParameterValueType::Float(r)),
7,652✔
1865
                Value::Complex(c) => Some(ParameterValueType::Complex(c)),
8✔
1866
            }
1867
        } else if let SymbolExpr::Symbol(symbol) = expr {
11,150✔
1868
            match symbol.index {
9,742✔
1869
                None => {
1870
                    let param = PyParameter {
9,566✔
1871
                        symbol: symbol.as_ref().clone(),
9,566✔
1872
                    };
9,566✔
1873
                    Some(ParameterValueType::Parameter(param))
9,566✔
1874
                }
1875
                Some(_) => {
1876
                    let param = PyParameterVectorElement {
176✔
1877
                        symbol: symbol.as_ref().clone(),
176✔
1878
                    };
176✔
1879
                    Some(ParameterValueType::VectorElement(param))
176✔
1880
                }
1881
            }
1882
        } else {
1883
            // ParameterExpressions have the value None, as they must be constructed
1884
            None
1,408✔
1885
        }
1886
    }
18,910✔
1887
}
1888

1889
impl From<ParameterValueType> for ParameterExpression {
1890
    fn from(value: ParameterValueType) -> Self {
544✔
1891
        match value {
544✔
1892
            ParameterValueType::Parameter(param) => {
280✔
1893
                let expr = SymbolExpr::Symbol(Arc::new(param.symbol));
280✔
1894
                Self::from_symbol_expr(expr)
280✔
1895
            }
1896
            ParameterValueType::VectorElement(param) => {
×
1897
                let expr = SymbolExpr::Symbol(Arc::new(param.symbol));
×
1898
                Self::from_symbol_expr(expr)
×
1899
            }
1900
            ParameterValueType::Int(i) => {
44✔
1901
                let expr = SymbolExpr::Value(Value::Int(i));
44✔
1902
                Self::from_symbol_expr(expr)
44✔
1903
            }
1904
            ParameterValueType::Float(f) => {
220✔
1905
                let expr = SymbolExpr::Value(Value::Real(f));
220✔
1906
                Self::from_symbol_expr(expr)
220✔
1907
            }
1908
            ParameterValueType::Complex(c) => {
×
1909
                let expr = SymbolExpr::Value(Value::Complex(c));
×
1910
                Self::from_symbol_expr(expr)
×
1911
            }
1912
        }
1913
    }
544✔
1914
}
1915

1916
#[pyclass(module = "qiskit._accelerate.circuit")]
×
1917
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
1918
#[repr(u8)]
1919
pub enum OpCode {
1920
    ADD = 0,
1921
    SUB = 1,
1922
    MUL = 2,
1923
    DIV = 3,
1924
    POW = 4,
1925
    SIN = 5,
1926
    COS = 6,
1927
    TAN = 7,
1928
    ASIN = 8,
1929
    ACOS = 9,
1930
    EXP = 10,
1931
    LOG = 11,
1932
    SIGN = 12,
1933
    GRAD = 13, // for backward compatibility, unused in Rust's ParameterExpression
1934
    CONJ = 14,
1935
    SUBSTITUTE = 15, // for backward compatibility, unused in Rust's ParameterExpression
1936
    ABS = 16,
1937
    ATAN = 17,
1938
    RSUB = 18,
1939
    RDIV = 19,
1940
    RPOW = 20,
1941
}
1942

1943
impl From<OpCode> for u8 {
1944
    fn from(value: OpCode) -> Self {
×
1945
        value as u8
×
1946
    }
×
1947
}
1948

1949
unsafe impl ::bytemuck::CheckedBitPattern for OpCode {
1950
    type Bits = u8;
1951

1952
    #[inline(always)]
1953
    fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
388✔
1954
        *bits <= 20
388✔
1955
    }
388✔
1956
}
1957

1958
unsafe impl ::bytemuck::NoUninit for OpCode {}
1959

1960
impl OpCode {
1961
    pub fn from_u8(value: u8) -> PyResult<OpCode> {
388✔
1962
        Ok(bytemuck::checked::cast::<u8, OpCode>(value))
388✔
1963
    }
388✔
1964
}
1965

1966
#[pymethods]
×
1967
impl OpCode {
1968
    #[new]
1969
    fn py_new(value: u8) -> PyResult<Self> {
×
1970
        let code: OpCode = ::bytemuck::checked::try_cast(value)
×
1971
            .map_err(|_| ParameterError::InvalidU8ToOpCode(value))?;
×
1972
        Ok(code)
×
1973
    }
×
1974

1975
    fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
560✔
1976
        if let Ok(code) = other.cast::<OpCode>() {
560✔
1977
            *code.borrow() == *self
560✔
1978
        } else {
1979
            false
×
1980
        }
1981
    }
560✔
1982

1983
    fn __hash__(&self) -> u8 {
2,088✔
1984
        *self as u8
2,088✔
1985
    }
2,088✔
1986

1987
    fn __getnewargs__(&self) -> (u8,) {
9,396✔
1988
        (*self as u8,)
9,396✔
1989
    }
9,396✔
1990
}
1991

1992
// enum for QPY replay
1993
#[pyclass(sequence, module = "qiskit._accelerate.circuit")]
1994
#[derive(Clone, Debug)]
1995
pub struct OPReplay {
1996
    pub op: OpCode,
1997
    pub lhs: Option<ParameterValueType>,
1998
    pub rhs: Option<ParameterValueType>,
1999
}
2000

2001
#[pymethods]
×
2002
impl OPReplay {
2003
    #[new]
2004
    pub fn py_new(
×
2005
        op: OpCode,
×
2006
        lhs: Option<ParameterValueType>,
×
2007
        rhs: Option<ParameterValueType>,
×
2008
    ) -> OPReplay {
×
2009
        OPReplay { op, lhs, rhs }
×
2010
    }
×
2011

2012
    #[getter]
2013
    fn op(&self) -> OpCode {
802✔
2014
        self.op
802✔
2015
    }
802✔
2016

2017
    #[getter]
2018
    fn lhs(&self) -> Option<ParameterValueType> {
238✔
2019
        self.lhs.clone()
238✔
2020
    }
238✔
2021

2022
    #[getter]
2023
    fn rhs(&self) -> Option<ParameterValueType> {
238✔
2024
        self.rhs.clone()
238✔
2025
    }
238✔
2026

2027
    fn __getnewargs__(
9,396✔
2028
        &self,
9,396✔
2029
    ) -> (
9,396✔
2030
        OpCode,
9,396✔
2031
        Option<ParameterValueType>,
9,396✔
2032
        Option<ParameterValueType>,
9,396✔
2033
    ) {
9,396✔
2034
        (self.op, self.lhs.clone(), self.rhs.clone())
9,396✔
2035
    }
9,396✔
2036
}
2037

2038
/// Internal helper. Extract one part of the expression tree, keeping the name map up to date.
2039
///
2040
/// Example: Given expr1 + expr2, each being [PyParameterExpression], we need the ability to
2041
/// extract one of the expressions with the proper name map.
2042
///
2043
/// Args:
2044
///     - joint_parameter_expr: The full expression, e.g. expr1 + expr2.
2045
///     - sub_expr: The sub expression, on whose symbols we restrict the name map.
2046
fn filter_name_map(
18,910✔
2047
    sub_expr: &SymbolExpr,
18,910✔
2048
    name_map: &HashMap<String, Symbol>,
18,910✔
2049
) -> ParameterExpression {
18,910✔
2050
    let sub_symbols: HashSet<&Symbol> = sub_expr.iter_symbols().collect();
18,910✔
2051
    let restricted_name_map: HashMap<String, Symbol> = name_map
18,910✔
2052
        .iter()
18,910✔
2053
        .filter(|(_, symbol)| sub_symbols.contains(*symbol))
24,038✔
2054
        .map(|(name, symbol)| (name.clone(), symbol.clone()))
18,910✔
2055
        .collect();
18,910✔
2056

2057
    ParameterExpression {
18,910✔
2058
        expr: sub_expr.clone(),
18,910✔
2059
        name_map: restricted_name_map,
18,910✔
2060
    }
18,910✔
2061
}
18,910✔
2062

2063
pub fn qpy_replay(
27,528✔
2064
    expr: &ParameterExpression,
27,528✔
2065
    name_map: &HashMap<String, Symbol>,
27,528✔
2066
    replay: &mut Vec<OPReplay>,
27,528✔
2067
) {
27,528✔
2068
    match &expr.expr {
27,528✔
2069
        SymbolExpr::Value(_) | SymbolExpr::Symbol(_) => {
17,502✔
2070
            // nothing to do here, we only need to traverse instructions
17,502✔
2071
        }
17,502✔
2072
        SymbolExpr::Unary { op, expr } => {
1,142✔
2073
            let op = match op {
1,142✔
2074
                symbol_expr::UnaryOp::Abs => OpCode::ABS,
6✔
2075
                symbol_expr::UnaryOp::Acos => OpCode::ACOS,
6✔
2076
                symbol_expr::UnaryOp::Asin => OpCode::ASIN,
6✔
2077
                symbol_expr::UnaryOp::Atan => OpCode::ATAN,
6✔
2078
                symbol_expr::UnaryOp::Conj => OpCode::CONJ,
6✔
2079
                symbol_expr::UnaryOp::Cos => OpCode::COS,
6✔
2080
                symbol_expr::UnaryOp::Exp => OpCode::EXP,
6✔
2081
                symbol_expr::UnaryOp::Log => OpCode::LOG,
6✔
2082
                symbol_expr::UnaryOp::Neg => OpCode::MUL,
1,066✔
2083
                symbol_expr::UnaryOp::Sign => OpCode::SIGN,
4✔
2084
                symbol_expr::UnaryOp::Sin => OpCode::SIN,
18✔
2085
                symbol_expr::UnaryOp::Tan => OpCode::TAN,
6✔
2086
            };
2087
            // TODO filter shouldn't be necessary for unary ops
2088
            let lhs = filter_name_map(expr, name_map);
1,142✔
2089

2090
            // recurse on the instruction
2091
            qpy_replay(&lhs, name_map, replay);
1,142✔
2092

2093
            let lhs_value = ParameterValueType::extract_from_expr(expr);
1,142✔
2094

2095
            // MUL is special: we implement ``neg`` as multiplication by -1
2096
            if let OpCode::MUL = &op {
1,142✔
2097
                replay.push(OPReplay {
1,066✔
2098
                    op,
1,066✔
2099
                    lhs: lhs_value,
1,066✔
2100
                    rhs: Some(ParameterValueType::Int(-1)),
1,066✔
2101
                });
1,066✔
2102
            } else {
1,066✔
2103
                replay.push(OPReplay {
76✔
2104
                    op,
76✔
2105
                    lhs: lhs_value,
76✔
2106
                    rhs: None,
76✔
2107
                });
76✔
2108
            }
76✔
2109
        }
2110
        SymbolExpr::Binary { op, lhs, rhs } => {
8,884✔
2111
            let lhs_value = ParameterValueType::extract_from_expr(lhs);
8,884✔
2112
            let rhs_value = ParameterValueType::extract_from_expr(rhs);
8,884✔
2113

2114
            // recurse on the parameter expressions
2115
            let lhs = filter_name_map(lhs, name_map);
8,884✔
2116
            let rhs = filter_name_map(rhs, name_map);
8,884✔
2117
            qpy_replay(&lhs, name_map, replay);
8,884✔
2118
            qpy_replay(&rhs, name_map, replay);
8,884✔
2119

2120
            // add the expression to the replay
2121
            match lhs_value {
8,630✔
2122
                None
2123
                | Some(ParameterValueType::Parameter(_))
2124
                | Some(ParameterValueType::VectorElement(_)) => {
2125
                    let op = match op {
1,278✔
2126
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
744✔
2127
                        symbol_expr::BinaryOp::Sub => OpCode::SUB,
416✔
2128
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
86✔
2129
                        symbol_expr::BinaryOp::Div => OpCode::DIV,
12✔
2130
                        symbol_expr::BinaryOp::Pow => OpCode::POW,
20✔
2131
                    };
2132
                    replay.push(OPReplay {
1,278✔
2133
                        op,
1,278✔
2134
                        lhs: lhs_value,
1,278✔
2135
                        rhs: rhs_value,
1,278✔
2136
                    });
1,278✔
2137
                }
2138
                _ => {
2139
                    let op = match op {
7,606✔
2140
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
578✔
2141
                        symbol_expr::BinaryOp::Sub => OpCode::RSUB,
120✔
2142
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
6,900✔
2143
                        symbol_expr::BinaryOp::Div => OpCode::RDIV,
4✔
2144
                        symbol_expr::BinaryOp::Pow => OpCode::RPOW,
4✔
2145
                    };
2146
                    if let OpCode::ADD | OpCode::MUL = op {
7,606✔
2147
                        replay.push(OPReplay {
7,478✔
2148
                            op,
7,478✔
2149
                            lhs: lhs_value,
7,478✔
2150
                            rhs: rhs_value,
7,478✔
2151
                        });
7,478✔
2152
                    } else {
7,478✔
2153
                        // this covers RSUB, RDIV, RPOW, hence we swap lhs and rhs
128✔
2154
                        replay.push(OPReplay {
128✔
2155
                            op,
128✔
2156
                            lhs: rhs_value,
128✔
2157
                            rhs: lhs_value,
128✔
2158
                        });
128✔
2159
                    }
128✔
2160
                }
2161
            }
2162
        }
2163
    }
2164
}
27,528✔
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