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

Qiskit / qiskit / 25391719935

05 May 2026 04:59PM UTC coverage: 87.575% (+0.001%) from 87.574%
25391719935

push

github

web-flow
Add: Implement rust-native error for `RegisterData`. (#16133)

- The following commit adds a rust native error to `RegisterData` to avoid requirement of `PyErr` when using this structure.

6 of 9 new or added lines in 1 file covered. (66.67%)

10 existing lines in 3 files now uncovered.

106700 of 121839 relevant lines covered (87.57%)

964768.62 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

108
impl Eq for ParameterExpression {}
109

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

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

135
impl ParameterExpression {
136
    pub fn qpy_replay(&self) -> Vec<OPReplay> {
8,922✔
137
        let mut replay = Vec::new();
8,922✔
138
        let mut unused: IndexSet<_, 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,654✔
238
        Self {
85,654✔
239
            expr: SymbolExpr::Symbol(Arc::new(symbol.clone())),
85,654✔
240
            name_map: [(symbol.repr(false), symbol)].into(),
85,654✔
241
        }
85,654✔
242
    }
85,654✔
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,220✔
248
        self.try_to_symbol_ref().cloned()
277,220✔
249
    }
277,220✔
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,220✔
255
        match &self.expr {
266,662✔
256
            SymbolExpr::Symbol(sym) if self.name_map.len() == 1 => Ok(sym.as_ref()),
266,662✔
257
            _ => Err(ParameterError::NotASymbol),
10,584✔
258
        }
259
    }
277,220✔
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,860✔
272
        if strict && !self.name_map.is_empty() {
6,417,860✔
273
            let free_symbols = self.expr.iter_symbols().cloned().collect();
74,764✔
274
            return Err(ParameterError::UnboundParameters(free_symbols));
74,764✔
275
        }
6,343,096✔
276

277
        match self.expr.eval(true) {
6,343,096✔
278
            Some(value) => {
413,372✔
279
                // we try to restrict complex to real, if possible
280
                if let Value::Complex(c) = value {
413,372✔
281
                    if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON).contains(&c.im)
42,130✔
282
                    {
283
                        return Ok(Value::Real(c.re));
×
284
                    }
42,130✔
285
                }
371,242✔
286
                Ok(value)
413,372✔
287
            }
288
            None => {
289
                let free_symbols = self.expr.iter_symbols().cloned().collect();
5,929,724✔
290
                Err(ParameterError::UnboundParameters(free_symbols))
5,929,724✔
291
            }
292
        }
293
    }
6,417,860✔
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,096✔
299
        let name_map = expr.name_map();
4,792,096✔
300
        Self { expr, name_map }
4,792,096✔
301
    }
4,792,096✔
302

303
    /// Initialize from an f64.
304
    pub fn from_f64(value: f64) -> Self {
12,411✔
305
        Self {
12,411✔
306
            expr: SymbolExpr::Value(Value::Real(value)),
12,411✔
307
            name_map: HashMap::new(),
12,411✔
308
        }
12,411✔
309
    }
12,411✔
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,976✔
369
        self.name_map.values()
466,976✔
370
    }
466,976✔
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,222✔
389
        let name_map = self.merged_name_map(rhs)?;
2,394,222✔
390
        Ok(Self {
2,394,220✔
391
            expr: &self.expr + &rhs.expr,
2,394,220✔
392
            name_map,
2,394,220✔
393
        })
2,394,220✔
394
    }
2,394,222✔
395

396
    /// Multiply with an expression; ``self * rhs``.
397
    pub fn mul(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,418,005✔
398
        let name_map = self.merged_name_map(rhs)?;
2,418,005✔
399
        Ok(Self {
2,418,003✔
400
            expr: &self.expr * &rhs.expr,
2,418,003✔
401
            name_map,
2,418,003✔
402
        })
2,418,003✔
403
    }
2,418,005✔
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 {
×
526
        Self {
×
527
            expr: -&self.expr,
×
528
            name_map: self.name_map.clone(),
×
529
        }
×
530
    }
×
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,974✔
562
        &self,
241,974✔
563
        map: &HashMap<Symbol, Self>,
241,974✔
564
        allow_unknown_parameters: bool,
241,974✔
565
    ) -> Result<Self, ParameterError> {
241,974✔
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,974✔
568
        let mut symbol_map: HashMap<Symbol, SymbolExpr> = HashMap::new();
241,974✔
569

570
        // If we don't allow for unknown parameters, check if there are any.
571
        if !allow_unknown_parameters {
241,974✔
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,096✔
581

582
        for (name, symbol) in self.name_map.iter() {
251,214✔
583
            // check if the symbol will get replaced
584
            if let Some(replacement) = map.get(symbol) {
251,214✔
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 {
34✔
591
                            return Err(ParameterError::NameConflict);
2✔
592
                        }
32✔
593
                    } else {
39,334✔
594
                        // SAFETY: We know the key does not exist yet.
39,334✔
595
                        unsafe {
39,334✔
596
                            name_map.insert_unique_unchecked(
39,334✔
597
                                replacement_name.clone(),
39,334✔
598
                                replacement_symbol.clone(),
39,334✔
599
                            )
39,334✔
600
                        };
39,334✔
601
                    }
39,334✔
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,262✔
607
            } else {
608
                // no replacement for this symbol, carry on
609
                match name_map.entry(name.clone()) {
212,950✔
610
                    Entry::Occupied(duplicate) => {
44✔
611
                        if duplicate.get() != symbol {
44✔
UNCOV
612
                            return Err(ParameterError::NameConflict);
×
613
                        }
44✔
614
                    }
615
                    Entry::Vacant(e) => {
212,906✔
616
                        e.insert(symbol.clone());
212,906✔
617
                    }
212,906✔
618
                }
619
            }
620
        }
621

622
        let res = self.expr.subs(&symbol_map);
241,970✔
623
        Ok(Self {
241,970✔
624
            expr: res,
241,970✔
625
            name_map,
241,970✔
626
        })
241,970✔
627
    }
241,974✔
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,796✔
644
        &self,
414,796✔
645
        map: &HashMap<&Symbol, Value>,
414,796✔
646
        allow_unknown_parameters: bool,
414,796✔
647
    ) -> Result<Self, ParameterError> {
414,796✔
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,796✔
650

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

656
            if difference.peek().is_some() {
209,612✔
657
                let different_symbols = difference.map(|s| (**s).clone()).collect();
×
658
                return Err(ParameterError::UnknownParameters(different_symbols));
×
659
            }
209,612✔
660
        }
205,184✔
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,796✔
664
        let bound = match bound_expr.eval(true) {
414,796✔
665
            Some(v) => match &v {
412,070✔
666
                Value::Real(r) => {
344,626✔
667
                    if r.is_infinite() {
344,626✔
668
                        Err(ParameterError::BindingInf)
1,828✔
669
                    } else if r.is_nan() {
342,798✔
670
                        Err(ParameterError::BindingNaN)
×
671
                    } else {
672
                        Ok(SymbolExpr::Value(v))
342,798✔
673
                    }
674
                }
675
                Value::Int(_) => Ok(SymbolExpr::Value(v)),
23,784✔
676
                Value::Complex(c) => {
43,660✔
677
                    if c.re.is_infinite() || c.im.is_infinite() {
43,660✔
678
                        Err(ParameterError::BindingInf)
×
679
                    } else if c.re.is_nan() || c.im.is_nan() {
43,660✔
680
                        Err(ParameterError::BindingNaN)
×
681
                    } else if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON)
43,660✔
682
                        .contains(&c.im)
43,660✔
683
                    {
684
                        Ok(SymbolExpr::Value(Value::Real(c.re)))
1,728✔
685
                    } else {
686
                        Ok(SymbolExpr::Value(v))
41,932✔
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,968✔
695
            .name_map
412,968✔
696
            .iter()
412,968✔
697
            .filter(|(_, symbol)| !bind_symbols.contains(symbol))
4,711,972✔
698
            .map(|(name, symbol)| (name.clone(), symbol.clone()))
412,968✔
699
            .collect();
412,968✔
700

701
        Ok(Self {
412,968✔
702
            expr: bound,
412,968✔
703
            name_map: bound_name_map,
412,968✔
704
        })
412,968✔
705
    }
414,796✔
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,533✔
718
        let mut merged = self.name_map.clone();
4,852,533✔
719
        for (name, param) in other.name_map.iter() {
7,055,217✔
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,525✔
733
    }
4,852,533✔
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,314,916✔
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,510✔
768
        Self { inner: value }
12,365,510✔
769
    }
12,365,510✔
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,982✔
787
        if let Ok(i) = ob.cast::<PyInt>() {
4,967,982✔
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,356✔
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,968✔
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,612✔
806
            Ok(ParameterExpression::from_symbol(element.borrow().symbol.clone()).into())
57,010✔
807
        } else if let Ok(parameter) = ob.cast::<PyParameter>() {
2,401,602✔
808
            Ok(ParameterExpression::from_symbol(parameter.borrow().symbol.clone()).into())
15,188✔
809
        } else {
810
            ob.extract::<PyParameterExpression>().map_err(Into::into)
2,386,414✔
811
        }
812
    }
4,967,982✔
813

814
    pub fn coerce_into_py(&self, py: Python) -> PyResult<Py<PyAny>> {
33,888✔
815
        if let Ok(value) = self.inner.try_to_value(true) {
33,888✔
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,874✔
822
            if symbol.index.is_some() {
23,528✔
823
                Ok(Py::new(py, PyParameterVectorElement::from_symbol(symbol))?.into_any())
5,940✔
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,888✔
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() {
6✔
1170
            let py_parameter = symbol.clone().into_pyobject(values.py())?;
6✔
1171
            partial_map.insert(symbol, values.get_item(py_parameter)?.extract()?);
6✔
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,900✔
1218
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
39,900✔
1219
            match rhs.inner.expr {
39,886✔
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,194✔
1225
            }
1226
        } else {
1227
            Ok(false)
14✔
1228
        }
1229
    }
39,900✔
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),
27,468✔
1381
            Value::Real(r) => Ok(Complex64::new(r, 0.)),
38,504✔
1382
            Value::Int(i) => Ok(Complex64::new(i as f64, 0.)),
11,882✔
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,842✔
1391
        match self.inner.try_to_value(false) {
5,924,842✔
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,840✔
1403
                self.inner.expr.string_id().hash(&mut hasher);
5,924,840✔
1404
                Ok(hasher.finish())
5,924,840✔
1405
            }
1406
        }
1407
    }
5,924,842✔
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,306✔
1496
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
4,689,306✔
1497

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

1501
        PyClassInitializer::from(py_expr).add_subclass(py_parameter)
4,689,306✔
1502
    }
4,689,306✔
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,454✔
1524
        py: Python<'_>,
88,454✔
1525
        name: String,
88,454✔
1526
        uuid: Option<Py<PyAny>>,
88,454✔
1527
    ) -> PyResult<PyClassInitializer<Self>> {
88,454✔
1528
        let uuid = uuid_from_py(py, uuid)?;
88,454✔
1529
        let symbol = Symbol::new(name.as_str(), uuid, None);
88,454✔
1530
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
88,454✔
1531

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

1535
        Ok(PyClassInitializer::from(py_expr).add_subclass(py_parameter))
88,454✔
1536
    }
88,454✔
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,788✔
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,788✔
1584
    }
156,788✔
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,472✔
1724
        let py_element = Self {
4,564,472✔
1725
            symbol: symbol.clone(),
4,564,472✔
1726
        };
4,564,472✔
1727
        let py_parameter = PyParameter::from_symbol(symbol);
4,564,472✔
1728

1729
        py_parameter.add_subclass(py_element)
4,564,472✔
1730
    }
4,564,472✔
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,628✔
1832
    if let Some(val) = uuid {
122,628✔
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,196✔
1846
    }
1847
}
122,628✔
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