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

Qiskit / qiskit / 23548889554

25 Mar 2026 03:22PM UTC coverage: 87.191% (-0.04%) from 87.235%
23548889554

Pull #15836

github

web-flow
Merge 4df6d9082 into 3e8032600
Pull Request #15836: Use `np.bitwise_count` in `BitArray.bitcount`

1 of 5 new or added lines in 1 file covered. (20.0%)

1085 existing lines in 41 files now uncovered.

103626 of 118850 relevant lines covered (87.19%)

1001189.12 hits per line

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

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

13
use hashbrown::hash_map::Entry;
14
use hashbrown::{HashMap, HashSet};
15
use 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,484✔
64
        match value {
3,484✔
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()),
588✔
75
            ParameterError::InvalidValue => PyValueError::new_err(value.to_string()),
40✔
76
            _ => PyRuntimeError::new_err(value.to_string()),
4✔
77
        }
78
    }
3,484✔
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,466✔
103
        self.expr.eq(&other.expr)
1,466✔
104
    }
1,466✔
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,591✔
121
        write!(f, "{}", {
336,591✔
122
            if let SymbolExpr::Symbol(s) = &self.expr {
336,591✔
123
                s.repr(false)
192,531✔
124
            } else {
125
                match self.expr.eval(true) {
144,060✔
126
                    Some(e) => e.to_string(),
184✔
127
                    None => self.expr.to_string(),
143,876✔
128
                }
129
            }
130
        })
131
    }
336,591✔
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,652✔
176
        Self { expr, name_map }
2,509,652✔
177
    }
2,509,652✔
178

179
    /// Construct from a [Symbol].
180
    pub fn from_symbol(symbol: Symbol) -> Self {
78,160✔
181
        Self {
78,160✔
182
            expr: SymbolExpr::Symbol(Arc::new(symbol.clone())),
78,160✔
183
            name_map: [(symbol.repr(false), symbol)].into(),
78,160✔
184
        }
78,160✔
185
    }
78,160✔
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> {
276,158✔
191
        if let SymbolExpr::Symbol(symbol) = &self.expr {
276,158✔
192
            Ok(symbol.as_ref().clone())
265,814✔
193
        } else {
194
            Err(ParameterError::NotASymbol)
10,344✔
195
        }
196
    }
276,158✔
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> {
6,404,006✔
220
        if strict && !self.name_map.is_empty() {
6,404,006✔
221
            let free_symbols = self.expr.iter_symbols().cloned().collect();
74,542✔
222
            return Err(ParameterError::UnboundParameters(free_symbols));
74,542✔
223
        }
6,329,464✔
224

225
        match self.expr.eval(true) {
6,329,464✔
226
            Some(value) => {
413,346✔
227
                // we try to restrict complex to real, if possible
228
                if let Value::Complex(c) = value {
413,346✔
229
                    if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON).contains(&c.im)
41,038✔
230
                    {
231
                        return Ok(Value::Real(c.re));
×
232
                    }
41,038✔
233
                }
372,308✔
234
                Ok(value)
413,346✔
235
            }
236
            None => {
237
                let free_symbols = self.expr.iter_symbols().cloned().collect();
5,916,118✔
238
                Err(ParameterError::UnboundParameters(free_symbols))
5,916,118✔
239
            }
240
        }
241
    }
6,404,006✔
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,769,502✔
247
        let name_map = expr.name_map();
4,769,502✔
248
        Self { expr, name_map }
4,769,502✔
249
    }
4,769,502✔
250

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

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

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

279
            // if we need two operands, pop rhs from the stack
280
            let rhs = if BINARY_OPS.contains(op) {
434✔
281
                Some(stack.pop().expect("Pop from empty stack"))
410✔
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");
434✔
288

289
            // apply the operation and put the result onto the stack for the next replay
290
            let result: ParameterExpression = match op {
434✔
291
                OpCode::ADD => lhs.add(&rhs.unwrap())?,
134✔
292
                OpCode::MUL => lhs.mul(&rhs.unwrap())?,
196✔
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);
434✔
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
434✔
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
226✔
328
            .pop()
226✔
329
            .expect("Invalid QPY replay encountered during deserialization: empty OPReplay."))
226✔
330
    }
226✔
331

332
    pub fn iter_symbols(&self) -> impl Iterator<Item = &Symbol> + '_ {
464,815✔
333
        self.name_map.values()
464,815✔
334
    }
464,815✔
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,248✔
343
        self.expr.is_complex()
23,248✔
344
    }
23,248✔
345

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

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

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

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

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

384
        let name_map = self.merged_name_map(rhs)?;
12,998✔
385
        Ok(Self {
12,996✔
386
            expr: &self.expr / &rhs.expr,
12,996✔
387
            name_map,
12,996✔
388
        })
12,996✔
389
    }
14,010✔
390

391
    /// Raise this expression to a power; ``self ^ rhs``.
392
    pub fn pow(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
3,022✔
393
        let name_map = self.merged_name_map(rhs)?;
3,022✔
394
        Ok(Self {
3,022✔
395
            expr: self.expr.pow(&rhs.expr),
3,022✔
396
            name_map,
3,022✔
397
        })
3,022✔
398
    }
3,022✔
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 substitution failed.
525
    pub fn subs(
241,960✔
526
        &self,
241,960✔
527
        map: &HashMap<Symbol, Self>,
241,960✔
528
        allow_unknown_parameters: bool,
241,960✔
529
    ) -> Result<Self, ParameterError> {
241,960✔
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,960✔
532
        let mut symbol_map: HashMap<Symbol, SymbolExpr> = HashMap::new();
241,960✔
533

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

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

546
        for (name, symbol) in self.name_map.iter() {
251,186✔
547
            // check if the symbol will get replaced
548
            if let Some(replacement) = map.get(symbol) {
251,186✔
549
                // If yes, update the name_map. This also checks for duplicates.
550
                for (replacement_name, replacement_symbol) in replacement.name_map.iter() {
39,354✔
551
                    if let Some(duplicate) = name_map.get(replacement_name) {
39,354✔
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 {
22✔
555
                            return Err(ParameterError::NameConflict);
2✔
556
                        }
20✔
557
                    } else {
39,332✔
558
                        // SAFETY: We know the key does not exist yet.
39,332✔
559
                        unsafe {
39,332✔
560
                            name_map.insert_unique_unchecked(
39,332✔
561
                                replacement_name.clone(),
39,332✔
562
                                replacement_symbol.clone(),
39,332✔
563
                            )
39,332✔
564
                        };
39,332✔
565
                    }
39,332✔
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,248✔
571
            } else {
572
                // no replacement for this symbol, carry on
573
                match name_map.entry(name.clone()) {
212,936✔
574
                    Entry::Occupied(duplicate) => {
42✔
575
                        if duplicate.get() != symbol {
42✔
UNCOV
576
                            return Err(ParameterError::NameConflict);
×
577
                        }
42✔
578
                    }
579
                    Entry::Vacant(e) => {
212,894✔
580
                        e.insert(symbol.clone());
212,894✔
581
                    }
212,894✔
582
                }
583
            }
584
        }
585

586
        let res = self.expr.subs(&symbol_map);
241,956✔
587
        Ok(Self {
241,956✔
588
            expr: res,
241,956✔
589
            name_map,
241,956✔
590
        })
241,956✔
591
    }
241,960✔
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,602✔
608
        &self,
414,602✔
609
        map: &HashMap<&Symbol, Value>,
414,602✔
610
        allow_unknown_parameters: bool,
414,602✔
611
    ) -> Result<Self, ParameterError> {
414,602✔
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,602✔
614

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

620
            if difference.peek().is_some() {
209,418✔
621
                let different_symbols = difference.map(|s| (**s).clone()).collect();
×
622
                return Err(ParameterError::UnknownParameters(different_symbols));
×
623
            }
209,418✔
624
        }
205,184✔
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,602✔
628
        let bound = match bound_expr.eval(true) {
414,602✔
629
            Some(v) => match &v {
411,876✔
630
                Value::Real(r) => {
346,794✔
631
                    if r.is_infinite() {
346,794✔
632
                        Err(ParameterError::BindingInf)
1,828✔
633
                    } else if r.is_nan() {
344,966✔
634
                        Err(ParameterError::BindingNaN)
×
635
                    } else {
636
                        Ok(SymbolExpr::Value(v))
344,966✔
637
                    }
638
                }
639
                Value::Int(_) => Ok(SymbolExpr::Value(v)),
22,514✔
640
                Value::Complex(c) => {
42,568✔
641
                    if c.re.is_infinite() || c.im.is_infinite() {
42,568✔
642
                        Err(ParameterError::BindingInf)
×
643
                    } else if c.re.is_nan() || c.im.is_nan() {
42,568✔
644
                        Err(ParameterError::BindingNaN)
×
645
                    } else if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON)
42,568✔
646
                        .contains(&c.im)
42,568✔
647
                    {
648
                        Ok(SymbolExpr::Value(Value::Real(c.re)))
1,728✔
649
                    } else {
650
                        Ok(SymbolExpr::Value(v))
40,840✔
651
                    }
652
                }
653
            },
654
            None => Ok(bound_expr),
2,726✔
655
        }?;
1,828✔
656

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

665
        Ok(Self {
412,774✔
666
            expr: bound,
412,774✔
667
            name_map: bound_name_map,
412,774✔
668
        })
412,774✔
669
    }
414,602✔
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,847,440✔
682
        let mut merged = self.name_map.clone();
4,847,440✔
683
        for (name, param) in other.name_map.iter() {
7,052,454✔
684
            match merged.get(name) {
7,051,254✔
685
                Some(existing_param) => {
3,990,536✔
686
                    if param != existing_param {
3,990,536✔
687
                        return Err(ParameterError::NameConflict);
8✔
688
                    }
3,990,528✔
689
                }
690
                None => {
3,060,718✔
691
                    // SAFETY: We ensured the key is unique
3,060,718✔
692
                    let _ = unsafe { merged.insert_unique_unchecked(name.clone(), param.clone()) };
3,060,718✔
693
                }
3,060,718✔
694
            }
695
        }
696
        Ok(merged)
4,847,432✔
697
    }
4,847,440✔
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
    from_py_object
1,282,904✔
709
)]
710
#[derive(Clone, Debug)]
711
pub struct PyParameterExpression {
712
    pub inner: ParameterExpression,
713
}
714

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1342
    pub fn __complex__(&self) -> PyResult<Complex64> {
77,856✔
1343
        match self.inner.try_to_value(false)? {
77,856✔
1344
            Value::Complex(c) => Ok(c),
26,376✔
1345
            Value::Real(r) => Ok(Complex64::new(r, 0.)),
40,900✔
1346
            Value::Int(i) => Ok(Complex64::new(i as f64, 0.)),
10,578✔
1347
        }
1348
    }
77,856✔
1349

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

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

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

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

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

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

1465
impl Hash for PyParameter {
1466
    fn hash<H: Hasher>(&self, state: &mut H) {
4,556,392✔
1467
        self.symbol.hash(state);
4,556,392✔
1468
    }
4,556,392✔
1469
}
1470

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

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

1484
        Ok(Py::new(py, (self, py_expr))?.into_bound(py))
9,536✔
1485
    }
9,536✔
1486
}
1487

1488
impl PyParameter {
1489
    /// Get a Python class initialization from a symbol.
1490
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
4,673,274✔
1491
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
4,673,274✔
1492

1493
        let py_parameter = Self { symbol };
4,673,274✔
1494
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
4,673,274✔
1495

1496
        PyClassInitializer::from(py_expr).add_subclass(py_parameter)
4,673,274✔
1497
    }
4,673,274✔
1498

1499
    /// Get a reference to the underlying symbol.
1500
    pub fn symbol(&self) -> &Symbol {
4,557,138✔
1501
        &self.symbol
4,557,138✔
1502
    }
4,557,138✔
1503
}
1504

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

1527
        let py_parameter = Self { symbol };
86,044✔
1528
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
86,044✔
1529

1530
        Ok(PyClassInitializer::from(py_expr).add_subclass(py_parameter))
86,044✔
1531
    }
86,044✔
1532

1533
    /// Returns the name of the :class:`.Parameter`.
1534
    #[getter]
1535
    fn name<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
21,322✔
1536
        PyString::new(py, &self.symbol.repr(false))
21,322✔
1537
    }
21,322✔
1538

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

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

1554
    pub fn __getnewargs__(&self) -> (String, u128) {
25,224✔
1555
        (self.symbol.repr(false), self.symbol.uuid.as_u128())
25,224✔
1556
    }
25,224✔
1557

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

1562
    pub fn __setstate__(&mut self, state: (String, u128)) {
16✔
1563
        let name = state.0.as_str();
16✔
1564
        let uuid = Uuid::from_u128(state.1);
16✔
1565
        let symbol = Symbol::new(name, Some(uuid), None);
16✔
1566
        self.symbol = symbol;
16✔
1567
    }
16✔
1568

1569
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1570
        // Parameter is immutable. Note that this **cannot** be deferred to the parent class
1571
        // since PyO3 would then always return the parent type.
1572
        slf
×
1573
    }
×
1574

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

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

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

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

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

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

1699
impl<'py> IntoPyObject<'py> for PyParameterVectorElement {
1700
    type Target = PyParameterVectorElement;
1701
    type Output = Bound<'py, Self::Target>;
1702
    type Error = PyErr;
1703

1704
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
10✔
1705
        let symbol = &self.symbol;
10✔
1706
        let py_param = PyParameter::from_symbol(symbol.clone());
10✔
1707
        let py_element = py_param.add_subclass(self);
10✔
1708

1709
        Ok(Py::new(py, py_element)?.into_bound(py))
10✔
1710
    }
10✔
1711
}
1712

1713
impl PyParameterVectorElement {
1714
    pub fn symbol(&self) -> &Symbol {
16,798✔
1715
        &self.symbol
16,798✔
1716
    }
16,798✔
1717

1718
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
4,556,124✔
1719
        let py_element = Self {
4,556,124✔
1720
            symbol: symbol.clone(),
4,556,124✔
1721
        };
4,556,124✔
1722
        let py_parameter = PyParameter::from_symbol(symbol);
4,556,124✔
1723

1724
        py_parameter.add_subclass(py_element)
4,556,124✔
1725
    }
4,556,124✔
1726
}
1727

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

1741
        let symbol = Symbol::py_new(
27,622✔
1742
            &vector_name,
27,622✔
1743
            Some(uuid.as_u128()),
27,622✔
1744
            Some(index),
27,622✔
1745
            Some(vector.clone_ref(py)),
27,622✔
1746
        )?;
×
1747

1748
        let py_parameter = PyParameter::from_symbol(symbol.clone());
27,622✔
1749
        let py_element = Self { symbol };
27,622✔
1750

1751
        Ok(py_parameter.add_subclass(py_element))
27,622✔
1752
    }
27,622✔
1753

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

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

1773
    pub fn __getstate__(&self, py: Python) -> PyResult<(Py<PyAny>, u32, Option<Py<PyAny>>)> {
8✔
1774
        self.__getnewargs__(py)
8✔
1775
    }
8✔
1776

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

1790
    /// Get the index of this element in the parent vector.
1791
    #[getter]
1792
    pub fn index(&self) -> u32 {
12✔
1793
        self.symbol
12✔
1794
            .index
12✔
1795
            .expect("A vector element should have an index")
12✔
1796
    }
12✔
1797

1798
    /// Get the parent vector instance.
1799
    #[getter]
1800
    pub fn vector(&self) -> Py<PyAny> {
20✔
1801
        self.symbol
20✔
1802
            .clone()
20✔
1803
            .vector
20✔
1804
            .expect("A vector element should have a vector")
20✔
1805
    }
20✔
1806

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

1814
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1815
        // ParameterVectorElement is immutable.
1816
        slf
×
1817
    }
×
1818

1819
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
10,988✔
1820
        // Everything a ParameterVectorElement contains is immutable.
1821
        slf
10,988✔
1822
    }
10,988✔
1823
}
1824

1825
/// Try to extract a Uuid from a Python object, which could be a Python UUID or int.
1826
fn uuid_from_py(py: Python<'_>, uuid: Option<Py<PyAny>>) -> PyResult<Option<Uuid>> {
113,666✔
1827
    if let Some(val) = uuid {
113,666✔
1828
        // construct from u128
1829
        let as_u128 = if let Ok(as_u128) = val.extract::<u128>(py) {
27,668✔
1830
            as_u128
16✔
1831
        // construct from Python UUID type
1832
        } else if val.bind(py).is_exact_instance(UUID.get_bound(py)) {
27,652✔
1833
            val.getattr(py, "int")?.extract::<u128>(py)?
27,652✔
1834
        // invalid format
1835
        } else {
1836
            return Err(PyTypeError::new_err("not a UUID!"));
×
1837
        };
1838
        Ok(Some(Uuid::from_u128(as_u128)))
27,668✔
1839
    } else {
1840
        Ok(None)
85,998✔
1841
    }
1842
}
113,666✔
1843

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

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

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

1874
impl ParameterValueType {
1875
    fn extract_from_expr(expr: &SymbolExpr) -> Option<ParameterValueType> {
19,022✔
1876
        if let Some(value) = expr.eval(true) {
19,022✔
1877
            match value {
7,780✔
1878
                Value::Int(i) => Some(ParameterValueType::Int(i)),
120✔
1879
                Value::Real(r) => Some(ParameterValueType::Float(r)),
7,652✔
1880
                Value::Complex(c) => Some(ParameterValueType::Complex(c)),
8✔
1881
            }
1882
        } else if let SymbolExpr::Symbol(symbol) = expr {
11,242✔
1883
            match symbol.index {
9,828✔
1884
                None => {
1885
                    let param = PyParameter {
9,652✔
1886
                        symbol: symbol.as_ref().clone(),
9,652✔
1887
                    };
9,652✔
1888
                    Some(ParameterValueType::Parameter(param))
9,652✔
1889
                }
1890
                Some(_) => {
1891
                    let param = PyParameterVectorElement {
176✔
1892
                        symbol: symbol.as_ref().clone(),
176✔
1893
                    };
176✔
1894
                    Some(ParameterValueType::VectorElement(param))
176✔
1895
                }
1896
            }
1897
        } else {
1898
            // ParameterExpressions have the value None, as they must be constructed
1899
            None
1,414✔
1900
        }
1901
    }
19,022✔
1902
}
1903

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

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

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

1964
unsafe impl ::bytemuck::CheckedBitPattern for OpCode {
1965
    type Bits = u8;
1966

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

1973
unsafe impl ::bytemuck::NoUninit for OpCode {}
1974

1975
impl OpCode {
1976
    pub fn from_u8(value: u8) -> PyResult<OpCode> {
434✔
1977
        Ok(bytemuck::checked::cast::<u8, OpCode>(value))
434✔
1978
    }
434✔
1979
}
1980

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

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

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

2002
    fn __getnewargs__(&self) -> (u8,) {
144✔
2003
        (*self as u8,)
144✔
2004
    }
144✔
2005
}
2006

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

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

2027
    #[getter]
2028
    fn op(&self) -> OpCode {
1,100✔
2029
        self.op
1,100✔
2030
    }
1,100✔
2031

2032
    #[getter]
2033
    fn lhs(&self) -> Option<ParameterValueType> {
294✔
2034
        self.lhs.clone()
294✔
2035
    }
294✔
2036

2037
    #[getter]
2038
    fn rhs(&self) -> Option<ParameterValueType> {
294✔
2039
        self.rhs.clone()
294✔
2040
    }
294✔
2041

2042
    fn __getnewargs__(
9,396✔
2043
        &self,
9,396✔
2044
    ) -> (
9,396✔
2045
        OpCode,
9,396✔
2046
        Option<ParameterValueType>,
9,396✔
2047
        Option<ParameterValueType>,
9,396✔
2048
    ) {
9,396✔
2049
        (self.op, self.lhs.clone(), self.rhs.clone())
9,396✔
2050
    }
9,396✔
2051
}
2052

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

2072
    ParameterExpression {
19,022✔
2073
        expr: sub_expr.clone(),
19,022✔
2074
        name_map: restricted_name_map,
19,022✔
2075
    }
19,022✔
2076
}
19,022✔
2077

2078
pub fn qpy_replay(
27,690✔
2079
    expr: &ParameterExpression,
27,690✔
2080
    name_map: &HashMap<String, Symbol>,
27,690✔
2081
    replay: &mut Vec<OPReplay>,
27,690✔
2082
) {
27,690✔
2083
    match &expr.expr {
27,690✔
2084
        SymbolExpr::Value(_) | SymbolExpr::Symbol(_) => {
17,608✔
2085
            // nothing to do here, we only need to traverse instructions
17,608✔
2086
        }
17,608✔
2087
        SymbolExpr::Unary { op, expr } => {
1,142✔
2088
            let op = match op {
1,142✔
2089
                symbol_expr::UnaryOp::Abs => OpCode::ABS,
6✔
2090
                symbol_expr::UnaryOp::Acos => OpCode::ACOS,
6✔
2091
                symbol_expr::UnaryOp::Asin => OpCode::ASIN,
6✔
2092
                symbol_expr::UnaryOp::Atan => OpCode::ATAN,
6✔
2093
                symbol_expr::UnaryOp::Conj => OpCode::CONJ,
6✔
2094
                symbol_expr::UnaryOp::Cos => OpCode::COS,
6✔
2095
                symbol_expr::UnaryOp::Exp => OpCode::EXP,
6✔
2096
                symbol_expr::UnaryOp::Log => OpCode::LOG,
6✔
2097
                symbol_expr::UnaryOp::Neg => OpCode::MUL,
1,066✔
2098
                symbol_expr::UnaryOp::Sign => OpCode::SIGN,
4✔
2099
                symbol_expr::UnaryOp::Sin => OpCode::SIN,
18✔
2100
                symbol_expr::UnaryOp::Tan => OpCode::TAN,
6✔
2101
            };
2102
            // TODO filter shouldn't be necessary for unary ops
2103
            let lhs = filter_name_map(expr, name_map);
1,142✔
2104

2105
            // recurse on the instruction
2106
            qpy_replay(&lhs, name_map, replay);
1,142✔
2107

2108
            let lhs_value = ParameterValueType::extract_from_expr(expr);
1,142✔
2109

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

2129
            // recurse on the parameter expressions
2130
            let lhs = filter_name_map(lhs, name_map);
8,940✔
2131
            let rhs = filter_name_map(rhs, name_map);
8,940✔
2132
            qpy_replay(&lhs, name_map, replay);
8,940✔
2133
            qpy_replay(&rhs, name_map, replay);
8,940✔
2134

2135
            // add the expression to the replay
2136
            match lhs_value {
8,686✔
2137
                Some(ParameterValueType::Parameter(_))
2138
                | Some(ParameterValueType::VectorElement(_)) => {
2139
                    // For non-commutative operations (SUB, DIV, POW): if LHS is a Parameter and RHS is
2140
                    // an expression (None), we need to use reverse operations (RSUB, RDIV, RPOW)
2141
                    let op = match op {
1,060✔
2142
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
744✔
2143
                        symbol_expr::BinaryOp::Sub => {
2144
                            // If RHS is None (an expression), use RSUB
2145
                            if rhs_value.is_none() {
288✔
2146
                                OpCode::RSUB
×
2147
                            } else {
2148
                                OpCode::SUB
288✔
2149
                            }
2150
                        }
2151
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
4✔
2152
                        symbol_expr::BinaryOp::Div => {
2153
                            // If RHS is None (an expression), use RDIV
2154
                            if rhs_value.is_none() {
×
2155
                                OpCode::RDIV
×
2156
                            } else {
2157
                                OpCode::DIV
×
2158
                            }
2159
                        }
2160
                        symbol_expr::BinaryOp::Pow => {
2161
                            // If RHS is None (an expression), use RPOW
2162
                            if rhs_value.is_none() {
24✔
2163
                                OpCode::RPOW
6✔
2164
                            } else {
2165
                                OpCode::POW
18✔
2166
                            }
2167
                        }
2168
                    };
2169
                    if op == OpCode::RPOW || op == OpCode::RDIV || op == OpCode::RSUB {
1,060✔
2170
                        // For reverse operations, swap lhs and rhs (Python's sympify will swap again to get correct order)
6✔
2171
                        replay.push(OPReplay {
6✔
2172
                            op,
6✔
2173
                            lhs: rhs_value,
6✔
2174
                            rhs: lhs_value,
6✔
2175
                        });
6✔
2176
                    } else {
1,054✔
2177
                        replay.push(OPReplay {
1,054✔
2178
                            op,
1,054✔
2179
                            lhs: lhs_value,
1,054✔
2180
                            rhs: rhs_value,
1,054✔
2181
                        });
1,054✔
2182
                    }
1,054✔
2183
                }
2184
                None => {
2185
                    // When LHS is an expression (None), use normal operations
2186
                    let op = match op {
254✔
2187
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
30✔
2188
                        symbol_expr::BinaryOp::Sub => OpCode::SUB,
128✔
2189
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
82✔
2190
                        symbol_expr::BinaryOp::Div => OpCode::DIV,
12✔
2191
                        symbol_expr::BinaryOp::Pow => OpCode::POW,
2✔
2192
                    };
2193
                    replay.push(OPReplay {
254✔
2194
                        op,
254✔
2195
                        lhs: lhs_value,
254✔
2196
                        rhs: rhs_value,
254✔
2197
                    });
254✔
2198
                }
2199
                _ => {
2200
                    let op = match op {
7,626✔
2201
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
582✔
2202
                        symbol_expr::BinaryOp::Sub => OpCode::RSUB,
122✔
2203
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
6,912✔
2204
                        symbol_expr::BinaryOp::Div => OpCode::RDIV,
6✔
2205
                        symbol_expr::BinaryOp::Pow => OpCode::RPOW,
4✔
2206
                    };
2207
                    if let OpCode::ADD | OpCode::MUL = op {
7,626✔
2208
                        replay.push(OPReplay {
7,494✔
2209
                            op,
7,494✔
2210
                            lhs: lhs_value,
7,494✔
2211
                            rhs: rhs_value,
7,494✔
2212
                        });
7,494✔
2213
                    } else {
7,494✔
2214
                        // this covers RSUB, RDIV, RPOW, hence we swap lhs and rhs
132✔
2215
                        replay.push(OPReplay {
132✔
2216
                            op,
132✔
2217
                            lhs: rhs_value,
132✔
2218
                            rhs: lhs_value,
132✔
2219
                        });
132✔
2220
                    }
132✔
2221
                }
2222
            }
2223
        }
2224
    }
2225
}
27,690✔
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