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

Qiskit / qiskit / 25175928579

30 Apr 2026 04:03PM UTC coverage: 87.579% (-0.01%) from 87.59%
25175928579

push

github

web-flow
Add Performance category to the changelog and release notes (#16065)

* Add Performance category

* Reorder the template to be fixes -> perf -> features -> ...

* Add more documentation to the features template

106274 of 121347 relevant lines covered (87.58%)

965323.04 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

108
impl Eq for ParameterExpression {}
109

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

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

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

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

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

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

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

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

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

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

276
        match self.expr.eval(true) {
6,343,268✔
277
            Some(value) => {
413,542✔
278
                // we try to restrict complex to real, if possible
279
                if let Value::Complex(c) = value {
413,542✔
280
                    if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON).contains(&c.im)
38,964✔
281
                    {
282
                        return Ok(Value::Real(c.re));
×
283
                    }
38,964✔
284
                }
374,578✔
285
                Ok(value)
413,542✔
286
            }
287
            None => {
288
                let free_symbols = self.expr.iter_symbols().cloned().collect();
5,929,726✔
289
                Err(ParameterError::UnboundParameters(free_symbols))
5,929,726✔
290
            }
291
        }
292
    }
6,418,032✔
293

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

419
        let name_map = self.merged_name_map(rhs)?;
13,038✔
420
        Ok(Self {
13,036✔
421
            expr: &self.expr / &rhs.expr,
13,036✔
422
            name_map,
13,036✔
423
        })
13,036✔
424
    }
14,050✔
425

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1377
    pub fn __complex__(&self) -> PyResult<Complex64> {
79,876✔
1378
        match self.inner.try_to_value(false)? {
79,876✔
1379
            Value::Complex(c) => Ok(c),
24,302✔
1380
            Value::Real(r) => Ok(Complex64::new(r, 0.)),
39,572✔
1381
            Value::Int(i) => Ok(Complex64::new(i as f64, 0.)),
13,980✔
1382
        }
1383
    }
79,876✔
1384

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

© 2026 Coveralls, Inc