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

Qiskit / qiskit / 25723423515

12 May 2026 08:40AM UTC coverage: 87.644% (+0.03%) from 87.619%
25723423515

push

github

web-flow
Fix: Use generic errors in the variable mapper and expr. (#16132)

While in the past we relied on `PyResult` to handle all of the errors as python exceptions, we can now remove some of the dependencies in parts of our system.
After some evaluation, it turns out we can use generic errors in the `VariableMapper` and `Expr` which frees us from needing python in order to use these components.

This was discovered during efforts to separate `DAGCircuit`'s error messages from always using `PyErr`.

24 of 25 new or added lines in 2 files covered. (96.0%)

4 existing lines in 3 files now uncovered.

107054 of 122147 relevant lines covered (87.64%)

960869.21 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1378
    pub fn __complex__(&self) -> PyResult<Complex64> {
79,876✔
1379
        match self.inner.try_to_value(false)? {
79,876✔
1380
            Value::Complex(c) => Ok(c),
23,248✔
1381
            Value::Real(r) => Ok(Complex64::new(r, 0.)),
40,954✔
1382
            Value::Int(i) => Ok(Complex64::new(i as f64, 0.)),
13,652✔
1383
        }
1384
    }
79,876✔
1385

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1729
        py_parameter.add_subclass(py_element)
4,564,476✔
1730
    }
4,564,476✔
1731
}
1732

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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