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

Qiskit / qiskit / 17209712632

25 Aug 2025 01:05PM UTC coverage: 88.37% (-0.08%) from 88.447%
17209712632

Pull #14659

github

web-flow
Merge e952c84f5 into c7d95a914
Pull Request #14659: Make `BasisTranslator` rust-native.

346 of 471 new or added lines in 5 files covered. (73.46%)

33 existing lines in 6 files now uncovered.

89871 of 101698 relevant lines covered (88.37%)

489685.54 hits per line

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

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

13
use std::sync::Arc;
14

15
use hashbrown::hash_map::Entry;
16
use hashbrown::{HashMap, HashSet};
17
use num_complex::Complex64;
18
use pyo3::exceptions::{PyRuntimeError, PyTypeError, PyValueError, PyZeroDivisionError};
19
use pyo3::types::{IntoPyDict, PyComplex, PyFloat, PyInt, PyNotImplemented, PySet, PyString};
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::prelude::*;
28
use pyo3::IntoPyObjectExt;
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::{Symbol, Value, SYMEXPR_EPSILON};
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 {
3,470✔
65
        match value {
3,470✔
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()),
574✔
76
            ParameterError::InvalidValue => PyValueError::new_err(value.to_string()),
40✔
77
            _ => PyRuntimeError::new_err(value.to_string()),
4✔
78
        }
79
    }
3,470✔
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 {
17,832✔
104
        self.expr.eq(&other.expr)
17,832✔
105
    }
17,832✔
106
}
107

108
impl Eq for ParameterExpression {}
109

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

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

135
// This needs to be implemented manually, because PyO3 does not provide built-in
136
// conversions for the subclasses of ParameterExpression in Python. Specifically
137
// the Python classes Parameter and ParameterVector are subclasses of
138
// ParameterExpression and the default trait impl would not handle the specialization
139
// there.
140
impl<'py> IntoPyObject<'py> for ParameterExpression {
141
    type Target = PyParameterExpression;
142
    type Output = Bound<'py, Self::Target>;
143
    type Error = PyErr;
144

UNCOV
145
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
×
UNCOV
146
        let expr = PyParameterExpression::from(self.clone());
×
UNCOV
147
        expr.into_pyobject(py)
×
UNCOV
148
    }
×
149
}
150

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

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

173
    /// Construct from a [Symbol].
174
    pub fn from_symbol(symbol: Symbol) -> Self {
75,398✔
175
        Self {
75,398✔
176
            expr: SymbolExpr::Symbol(Arc::new(symbol.clone())),
75,398✔
177
            name_map: [(symbol.repr(false), symbol)].into(),
75,398✔
178
        }
75,398✔
179
    }
75,398✔
180

181
    /// Try casting to a [Symbol].
182
    ///
183
    /// This only succeeds if the underlying expression is, in fact, only a symbol.
184
    pub fn try_to_symbol(&self) -> Result<Symbol, ParameterError> {
234,064✔
185
        if let SymbolExpr::Symbol(symbol) = &self.expr {
234,064✔
186
            Ok(symbol.as_ref().clone())
226,248✔
187
        } else {
188
            Err(ParameterError::NotASymbol)
7,816✔
189
        }
190
    }
234,064✔
191

192
    /// Try casting to a [Value].
193
    ///
194
    /// Attempt to evaluate the expression recursively and return a [Value] if fully bound.
195
    ///
196
    /// # Arguments
197
    ///
198
    /// * strict - If ``true``, only allow returning a value if all symbols are bound. If
199
    ///   ``false``, allow casting expressions to values, even though symbols might still exist.
200
    ///   For example, ``0 * x`` will return ``0`` for ``strict=false`` and otherwise return
201
    ///   an error.
202
    pub fn try_to_value(&self, strict: bool) -> Result<Value, ParameterError> {
9,624,048✔
203
        if strict && !self.name_map.is_empty() {
9,624,048✔
204
            let free_symbols = self.expr.iter_symbols().cloned().collect();
66,922✔
205
            return Err(ParameterError::UnboundParameters(free_symbols));
66,922✔
206
        }
9,557,126✔
207

208
        match self.expr.eval(true) {
9,557,126✔
209
            Some(value) => {
391,632✔
210
                // we try to restrict complex to real, if possible
211
                if let Value::Complex(c) = value {
391,632✔
212
                    if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON).contains(&c.im)
37,824✔
213
                    {
214
                        return Ok(Value::Real(c.re));
×
215
                    }
37,824✔
216
                }
353,808✔
217
                Ok(value)
391,632✔
218
            }
219
            None => {
220
                let free_symbols = self.expr.iter_symbols().cloned().collect();
9,165,494✔
221
                Err(ParameterError::UnboundParameters(free_symbols))
9,165,494✔
222
            }
223
        }
224
    }
9,624,048✔
225

226
    /// Construct from a [SymbolExpr].
227
    ///
228
    /// This populates the name map with the symbols in the expression.
229
    pub fn from_symbol_expr(expr: SymbolExpr) -> Self {
4,758,242✔
230
        let name_map = expr.name_map();
4,758,242✔
231
        Self { expr, name_map }
4,758,242✔
232
    }
4,758,242✔
233

234
    /// Initialize from an f64.
235
    pub fn from_f64(value: f64) -> Self {
23,734✔
236
        Self {
23,734✔
237
            expr: SymbolExpr::Value(Value::Real(value)),
23,734✔
238
            name_map: HashMap::new(),
23,734✔
239
        }
23,734✔
240
    }
23,734✔
241

242
    /// Load from a sequence of [OPReplay]s. Used in serialization.
243
    pub fn from_qpy(replay: &[OPReplay]) -> Result<Self, ParameterError> {
×
244
        // the stack contains the latest lhs and rhs values
245
        let mut stack: Vec<ParameterExpression> = Vec::new();
×
246

247
        for inst in replay.iter() {
×
248
            let OPReplay { op, lhs, rhs } = inst;
×
249

250
            // put the values on the stack, if they exist
251
            if let Some(value) = lhs {
×
252
                stack.push(value.clone().into());
×
253
            }
×
254
            if let Some(value) = rhs {
×
255
                stack.push(value.clone().into());
×
256
            }
×
257

258
            // if we need two operands, pop rhs from the stack
259
            let rhs = if BINARY_OPS.contains(op) {
×
260
                Some(stack.pop().expect("Pop from empty stack"))
×
261
            } else {
262
                None
×
263
            };
264

265
            // pop lhs from the stack, this we always need
266
            let lhs = stack.pop().expect("Pop from empty stack");
×
267

268
            // apply the operation and put the result onto the stack for the next replay
269
            let result: ParameterExpression = match op {
×
270
                OpCode::ADD => lhs.add(&rhs.unwrap())?,
×
271
                OpCode::MUL => lhs.mul(&rhs.unwrap())?,
×
272
                OpCode::SUB => lhs.sub(&rhs.unwrap())?,
×
273
                OpCode::RSUB => rhs.unwrap().sub(&lhs)?,
×
274
                OpCode::POW => lhs.pow(&rhs.unwrap())?,
×
275
                OpCode::RPOW => rhs.unwrap().pow(&lhs)?,
×
276
                OpCode::DIV => lhs.div(&rhs.unwrap())?,
×
277
                OpCode::RDIV => rhs.unwrap().div(&lhs)?,
×
278
                OpCode::ABS => lhs.abs(),
×
279
                OpCode::SIN => lhs.sin(),
×
280
                OpCode::ASIN => lhs.asin(),
×
281
                OpCode::COS => lhs.cos(),
×
282
                OpCode::ACOS => lhs.acos(),
×
283
                OpCode::TAN => lhs.tan(),
×
284
                OpCode::ATAN => lhs.atan(),
×
285
                OpCode::CONJ => lhs.conjugate(),
×
286
                OpCode::LOG => lhs.log(),
×
287
                OpCode::EXP => lhs.exp(),
×
288
                OpCode::SIGN => lhs.sign(),
×
289
                OpCode::GRAD | OpCode::SUBSTITUTE => {
290
                    panic!("GRAD and SUBSTITUTE are not supported.")
×
291
                }
292
            };
293
            stack.push(result);
×
294
        }
295

296
        // once we're done, just return the last element in the stack
297
        Ok(stack
×
298
            .pop()
×
299
            .expect("Invalid QPY replay encountered during deserialization: empty OPReplay."))
×
300
    }
×
301

302
    pub fn iter_symbols(&self) -> impl Iterator<Item = &Symbol> + '_ {
278,638✔
303
        self.name_map.values()
278,638✔
304
    }
278,638✔
305

306
    /// Get the number of [Symbol]s in the expression.
NEW
307
    pub fn num_symbols(&self) -> usize {
×
NEW
308
        self.name_map.len()
×
NEW
309
    }
×
310

311
    /// Whether the expression represents a complex number. None if cannot be determined.
312
    pub fn is_complex(&self) -> Option<bool> {
23,008✔
313
        self.expr.is_complex()
23,008✔
314
    }
23,008✔
315

316
    /// Whether the expression represents a int. None if cannot be determined.
317
    pub fn is_int(&self) -> Option<bool> {
85,542✔
318
        self.expr.is_int()
85,542✔
319
    }
85,542✔
320

321
    /// Add an expression; ``self + rhs``.
322
    pub fn add(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,390,028✔
323
        let name_map = self.merged_name_map(rhs)?;
2,390,028✔
324
        Ok(Self {
2,390,026✔
325
            expr: &self.expr + &rhs.expr,
2,390,026✔
326
            name_map,
2,390,026✔
327
        })
2,390,026✔
328
    }
2,390,028✔
329

330
    /// Multiply with an expression; ``self * rhs``.
331
    pub fn mul(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,413,822✔
332
        let name_map = self.merged_name_map(rhs)?;
2,413,822✔
333
        Ok(Self {
2,413,820✔
334
            expr: &self.expr * &rhs.expr,
2,413,820✔
335
            name_map,
2,413,820✔
336
        })
2,413,820✔
337
    }
2,413,822✔
338

339
    /// Subtract another expression; ``self - rhs``.
340
    pub fn sub(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
22,858✔
341
        let name_map = self.merged_name_map(rhs)?;
22,858✔
342
        Ok(Self {
22,856✔
343
            expr: &self.expr - &rhs.expr,
22,856✔
344
            name_map,
22,856✔
345
        })
22,856✔
346
    }
22,858✔
347

348
    /// Divide by another expression; ``self / rhs``.
349
    pub fn div(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
12,634✔
350
        if rhs.expr.is_zero() {
12,634✔
351
            return Err(ParameterError::ZeroDivisionError);
1,012✔
352
        }
11,622✔
353

354
        let name_map = self.merged_name_map(rhs)?;
11,622✔
355
        Ok(Self {
11,620✔
356
            expr: &self.expr / &rhs.expr,
11,620✔
357
            name_map,
11,620✔
358
        })
11,620✔
359
    }
12,634✔
360

361
    /// Raise this expression to a power; ``self ^ rhs``.
362
    pub fn pow(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
3,016✔
363
        let name_map = self.merged_name_map(rhs)?;
3,016✔
364
        Ok(Self {
3,016✔
365
            expr: self.expr.pow(&rhs.expr),
3,016✔
366
            name_map,
3,016✔
367
        })
3,016✔
368
    }
3,016✔
369

370
    /// Apply the sine to this expression; ``sin(self)``.
371
    pub fn sin(&self) -> Self {
362✔
372
        Self {
362✔
373
            expr: self.expr.sin(),
362✔
374
            name_map: self.name_map.clone(),
362✔
375
        }
362✔
376
    }
362✔
377

378
    /// Apply the cosine to this expression; ``cos(self)``.
379
    pub fn cos(&self) -> Self {
352✔
380
        Self {
352✔
381
            expr: self.expr.cos(),
352✔
382
            name_map: self.name_map.clone(),
352✔
383
        }
352✔
384
    }
352✔
385

386
    /// Apply the tangent to this expression; ``tan(self)``.
387
    pub fn tan(&self) -> Self {
352✔
388
        Self {
352✔
389
            expr: self.expr.tan(),
352✔
390
            name_map: self.name_map.clone(),
352✔
391
        }
352✔
392
    }
352✔
393

394
    /// Apply the arcsine to this expression; ``asin(self)``.
395
    pub fn asin(&self) -> Self {
100✔
396
        Self {
100✔
397
            expr: self.expr.asin(),
100✔
398
            name_map: self.name_map.clone(),
100✔
399
        }
100✔
400
    }
100✔
401

402
    /// Apply the arccosine to this expression; ``acos(self)``.
403
    pub fn acos(&self) -> Self {
100✔
404
        Self {
100✔
405
            expr: self.expr.acos(),
100✔
406
            name_map: self.name_map.clone(),
100✔
407
        }
100✔
408
    }
100✔
409

410
    /// Apply the arctangent to this expression; ``atan(self)``.
411
    pub fn atan(&self) -> Self {
100✔
412
        Self {
100✔
413
            expr: self.expr.atan(),
100✔
414
            name_map: self.name_map.clone(),
100✔
415
        }
100✔
416
    }
100✔
417

418
    /// Exponentiate this expression; ``exp(self)``.
419
    pub fn exp(&self) -> Self {
350✔
420
        Self {
350✔
421
            expr: self.expr.exp(),
350✔
422
            name_map: self.name_map.clone(),
350✔
423
        }
350✔
424
    }
350✔
425

426
    /// Take the (natural) logarithm of this expression; ``log(self)``.
427
    pub fn log(&self) -> Self {
266✔
428
        Self {
266✔
429
            expr: self.expr.log(),
266✔
430
            name_map: self.name_map.clone(),
266✔
431
        }
266✔
432
    }
266✔
433

434
    /// Take the absolute value of this expression; ``|self|``.
435
    pub fn abs(&self) -> Self {
374✔
436
        Self {
374✔
437
            expr: self.expr.abs(),
374✔
438
            name_map: self.name_map.clone(),
374✔
439
        }
374✔
440
    }
374✔
441

442
    /// Return the sign of this expression; ``sign(self)``.
443
    pub fn sign(&self) -> Self {
10✔
444
        Self {
10✔
445
            expr: self.expr.sign(),
10✔
446
            name_map: self.name_map.clone(),
10✔
447
        }
10✔
448
    }
10✔
449

450
    /// Complex conjugate the expression.
451
    pub fn conjugate(&self) -> Self {
1,832✔
452
        Self {
1,832✔
453
            expr: self.expr.conjugate(),
1,832✔
454
            name_map: self.name_map.clone(),
1,832✔
455
        }
1,832✔
456
    }
1,832✔
457

458
    /// Compute the derivative of the expression with respect to the provided symbol.
459
    ///
460
    /// Note that this keeps the name map unchanged. Meaning that computing the derivative
461
    /// of ``x`` will yield ``1`` but the expression still owns the symbol ``x``. This is
462
    /// done such that we can still bind the value ``x`` in an automated process.
463
    pub fn derivative(&self, param: &Symbol) -> Result<Self, ParameterError> {
60✔
464
        Ok(Self {
465
            expr: self
60✔
466
                .expr
60✔
467
                .derivative(param)
60✔
468
                .map_err(ParameterError::DerivativeNotSupported)?,
60✔
469
            name_map: self.name_map.clone(),
56✔
470
        })
471
    }
60✔
472

473
    /// Substitute symbols with [ParameterExpression]s.
474
    ///
475
    /// # Arguments
476
    ///
477
    /// * map - A hashmap with [Symbol] keys and [ParameterExpression]s to replace these
478
    ///   symbols with.
479
    /// * allow_unknown_parameters - If `false`, returns an error if any symbol in the
480
    ///   hashmap is not present in the expression. If `true`, unknown symbols are ignored.
481
    ///   Setting to `true` is slightly faster as it does not involve additional checks.
482
    ///
483
    /// # Returns
484
    ///
485
    /// * `Ok(Self)` - A parameter expression with the substituted expressions.
486
    /// * `Err(ParameterError)` - An error if the subtitution failed.
487
    pub fn subs(
224,986✔
488
        &self,
224,986✔
489
        map: &HashMap<Symbol, Self>,
224,986✔
490
        allow_unknown_parameters: bool,
224,986✔
491
    ) -> Result<Self, ParameterError> {
224,986✔
492
        // Build the outgoing name map. In the process we check for any duplicates.
493
        let mut name_map: HashMap<String, Symbol> = HashMap::new();
224,986✔
494
        let mut symbol_map: HashMap<Symbol, SymbolExpr> = HashMap::new();
224,986✔
495

496
        // If we don't allow for unknown parameters, check if there are any.
497
        if !allow_unknown_parameters {
224,986✔
498
            let existing: HashSet<&Symbol> = self.name_map.values().collect();
36,008✔
499
            let to_replace: HashSet<&Symbol> = map.keys().collect();
36,008✔
500
            let mut difference = to_replace.difference(&existing).peekable();
36,008✔
501

502
            if difference.peek().is_some() {
36,008✔
503
                let different_symbols = difference.map(|s| (**s).clone()).collect();
2✔
504
                return Err(ParameterError::UnknownParameters(different_symbols));
2✔
505
            }
36,006✔
506
        }
188,978✔
507

508
        for (name, symbol) in self.name_map.iter() {
234,274✔
509
            // check if the symbol will get replaced
510
            if let Some(replacement) = map.get(symbol) {
234,274✔
511
                // If yes, update the name_map. This also checks for duplicates.
512
                for (replacement_name, replacement_symbol) in replacement.name_map.iter() {
38,498✔
513
                    if let Some(duplicate) = name_map.get(replacement_name) {
38,498✔
514
                        // If a symbol with the same name already exists, check whether it is
515
                        // the same symbol (fine) or a different symbol with the same name (conflict)!
516
                        if duplicate != replacement_symbol {
28✔
517
                            return Err(ParameterError::NameConflict);
×
518
                        }
28✔
519
                    } else {
38,470✔
520
                        // SAFETY: We know the key does not exist yet.
38,470✔
521
                        unsafe {
38,470✔
522
                            name_map.insert_unique_unchecked(
38,470✔
523
                                replacement_name.clone(),
38,470✔
524
                                replacement_symbol.clone(),
38,470✔
525
                            )
38,470✔
526
                        };
38,470✔
527
                    }
38,470✔
528
                }
529

530
                // If we got until here, there were no duplicates, so we are safe to
531
                // add this symbol to the internal replacement map.
532
                symbol_map.insert(symbol.clone(), replacement.expr.clone());
37,394✔
533
            } else {
534
                // no replacement for this symbol, carry on
535
                match name_map.entry(name.clone()) {
196,880✔
536
                    Entry::Occupied(duplicate) => {
26✔
537
                        if duplicate.get() != symbol {
26✔
538
                            return Err(ParameterError::NameConflict);
2✔
539
                        }
24✔
540
                    }
541
                    Entry::Vacant(e) => {
196,854✔
542
                        e.insert(symbol.clone());
196,854✔
543
                    }
196,854✔
544
                }
545
            }
546
        }
547

548
        let res = self.expr.subs(&symbol_map);
224,982✔
549
        Ok(Self {
224,982✔
550
            expr: res,
224,982✔
551
            name_map,
224,982✔
552
        })
224,982✔
553
    }
224,986✔
554

555
    /// Bind symbols to values.
556
    ///
557
    /// # Arguments
558
    ///
559
    /// * map - A hashmap with [Symbol] keys and [Value]s to replace these
560
    ///   symbols with.
561
    /// * allow_unknown_parameter - If `false`, returns an error if any symbol in the
562
    ///   hashmap is not present in the expression. If `true`, unknown symbols are ignored.
563
    ///   Setting to `true` is slightly faster as it does not involve additional checks.
564
    ///
565
    /// # Returns
566
    ///
567
    /// * `Ok(Self)` - A parameter expression with the bound symbols.
568
    /// * `Err(ParameterError)` - An error if binding failed.
569
    pub fn bind(
392,732✔
570
        &self,
392,732✔
571
        map: &HashMap<&Symbol, Value>,
392,732✔
572
        allow_unknown_parameters: bool,
392,732✔
573
    ) -> Result<Self, ParameterError> {
392,732✔
574
        // The set of symbols we will bind. Used twice, hence pre-computed here.
575
        let bind_symbols: HashSet<&Symbol> = map.keys().cloned().collect();
392,732✔
576

577
        // If we don't allow for unknown parameters, check if there are any.
578
        if !allow_unknown_parameters {
392,732✔
579
            let existing: HashSet<&Symbol> = self.name_map.values().collect();
203,654✔
580
            let mut difference = bind_symbols.difference(&existing).peekable();
203,654✔
581

582
            if difference.peek().is_some() {
203,654✔
583
                let different_symbols = difference.map(|s| (**s).clone()).collect();
×
584
                return Err(ParameterError::UnknownParameters(different_symbols));
×
585
            }
203,654✔
586
        }
189,078✔
587

588
        // bind the symbol expression and then check the outcome for inf/nan, or numeric values
589
        let bound_expr = self.expr.bind(map);
392,732✔
590
        let bound = match bound_expr.eval(true) {
392,732✔
591
            Some(v) => match &v {
390,110✔
592
                Value::Real(r) => {
323,716✔
593
                    if r.is_infinite() {
323,716✔
594
                        Err(ParameterError::BindingInf)
1,828✔
595
                    } else if r.is_nan() {
321,888✔
596
                        Err(ParameterError::BindingNaN)
×
597
                    } else {
598
                        Ok(SymbolExpr::Value(v))
321,888✔
599
                    }
600
                }
601
                Value::Int(_) => Ok(SymbolExpr::Value(v)),
27,040✔
602
                Value::Complex(c) => {
39,354✔
603
                    if c.re.is_infinite() || c.im.is_infinite() {
39,354✔
604
                        Err(ParameterError::BindingInf)
×
605
                    } else if c.re.is_nan() || c.im.is_nan() {
39,354✔
606
                        Err(ParameterError::BindingNaN)
×
607
                    } else if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON)
39,354✔
608
                        .contains(&c.im)
39,354✔
609
                    {
610
                        Ok(SymbolExpr::Value(Value::Real(c.re)))
1,728✔
611
                    } else {
612
                        Ok(SymbolExpr::Value(v))
37,626✔
613
                    }
614
                }
615
            },
616
            None => Ok(bound_expr),
2,622✔
617
        }?;
1,828✔
618

619
        // update the name map by removing the bound parameters
620
        let bound_name_map: HashMap<String, Symbol> = self
390,904✔
621
            .name_map
390,904✔
622
            .iter()
390,904✔
623
            .filter(|(_, symbol)| !bind_symbols.contains(symbol))
4,689,872✔
624
            .map(|(name, symbol)| (name.clone(), symbol.clone()))
390,904✔
625
            .collect();
390,904✔
626

627
        Ok(Self {
390,904✔
628
            expr: bound,
390,904✔
629
            name_map: bound_name_map,
390,904✔
630
        })
390,904✔
631
    }
392,732✔
632

633
    /// Merge name maps.
634
    ///
635
    /// # Arguments
636
    ///
637
    /// * `other` - The other parameter expression whose symbols we add to self.
638
    ///
639
    /// # Returns
640
    ///
641
    /// * `Ok(HashMap<String, Symbol>)` - The merged name map.
642
    /// * `Err(ParameterError)` - An error if there was a name conflict.
643
    fn merged_name_map(&self, other: &Self) -> Result<HashMap<String, Symbol>, ParameterError> {
4,841,346✔
644
        let mut merged = self.name_map.clone();
4,841,346✔
645
        for (name, param) in other.name_map.iter() {
7,049,700✔
646
            match merged.get(name) {
7,049,700✔
647
                Some(existing_param) => {
3,990,468✔
648
                    if param != existing_param {
3,990,468✔
649
                        return Err(ParameterError::NameConflict);
8✔
650
                    }
3,990,460✔
651
                }
652
                None => {
3,059,232✔
653
                    // SAFETY: We ensured the key is unique
3,059,232✔
654
                    let _ = unsafe { merged.insert_unique_unchecked(name.clone(), param.clone()) };
3,059,232✔
655
                }
3,059,232✔
656
            }
657
        }
658
        Ok(merged)
4,841,338✔
659
    }
4,841,346✔
660
}
661

662
/// A parameter expression.
663
///
664
/// This is backed by Qiskit's symbolic expression engine and a cache
665
/// for the parameters inside the expression.
666
#[pyclass(
667
    subclass,
668
    module = "qiskit._accelerate.circuit",
669
    name = "ParameterExpression"
670
)]
671
#[derive(Clone, Debug)]
672
pub struct PyParameterExpression {
673
    pub inner: ParameterExpression,
674
}
675

676
impl Default for PyParameterExpression {
677
    /// The default constructor returns zero.
678
    fn default() -> Self {
×
679
        Self {
×
680
            inner: ParameterExpression::default(),
×
681
        }
×
682
    }
×
683
}
684

685
impl fmt::Display for PyParameterExpression {
686
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336,786✔
687
        self.inner.fmt(f)
336,786✔
688
    }
336,786✔
689
}
690

691
impl From<ParameterExpression> for PyParameterExpression {
692
    fn from(value: ParameterExpression) -> Self {
12,321,468✔
693
        Self { inner: value }
12,321,468✔
694
    }
12,321,468✔
695
}
696

697
impl PyParameterExpression {
698
    /// Attempt to extract a `PyParameterExpression` from a bound `PyAny`.
699
    ///
700
    /// This will try to coerce to the strictest data type:
701
    /// Int - Real - Complex - PyParameterVectorElement - PyParameter - PyParameterExpression.
702
    ///
703
    /// # Arguments:
704
    ///
705
    /// * ob - The bound `PyAny` to extract from.
706
    ///
707
    /// # Returns
708
    ///
709
    /// * `Ok(Self)` - The extracted expression.
710
    /// * `Err(PyResult)` - An error if extraction to all above types failed.
711
    pub fn extract_coerce(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
4,960,298✔
712
        if let Ok(i) = ob.downcast::<PyInt>() {
4,960,298✔
713
            Ok(ParameterExpression::new(
134,146✔
714
                SymbolExpr::Value(Value::from(i.extract::<i64>()?)),
134,146✔
715
                HashMap::new(),
134,146✔
716
            )
717
            .into())
134,146✔
718
        } else if let Ok(r) = ob.downcast::<PyFloat>() {
4,826,152✔
719
            let r: f64 = r.extract()?;
13,554✔
720
            if r.is_infinite() || r.is_nan() {
13,554✔
721
                return Err(ParameterError::InvalidValue.into());
40✔
722
            }
13,514✔
723
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(r)), HashMap::new()).into())
13,514✔
724
        } else if let Ok(c) = ob.downcast::<PyComplex>() {
4,812,598✔
725
            let c: Complex64 = c.extract()?;
2,361,336✔
726
            if c.is_infinite() || c.is_nan() {
2,361,336✔
727
                return Err(ParameterError::InvalidValue.into());
×
728
            }
2,361,336✔
729
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(c)), HashMap::new()).into())
2,361,336✔
730
        } else if let Ok(element) = ob.downcast::<PyParameterVectorElement>() {
2,451,262✔
731
            Ok(ParameterExpression::from_symbol(element.borrow().symbol.clone()).into())
50,858✔
732
        } else if let Ok(parameter) = ob.downcast::<PyParameter>() {
2,400,404✔
733
            Ok(ParameterExpression::from_symbol(parameter.borrow().symbol.clone()).into())
14,146✔
734
        } else {
735
            ob.extract::<PyParameterExpression>()
2,386,258✔
736
        }
737
    }
4,960,298✔
738

739
    pub fn coerce_into_py(&self, py: Python) -> PyResult<PyObject> {
27,090✔
740
        if let Ok(value) = self.inner.try_to_value(true) {
27,090✔
741
            match value {
14✔
742
                Value::Int(i) => Ok(PyInt::new(py, i).unbind().into_any()),
×
743
                Value::Real(r) => Ok(PyFloat::new(py, r).unbind().into_any()),
14✔
744
                Value::Complex(c) => Ok(PyComplex::from_complex_bound(py, c).unbind().into_any()),
×
745
            }
746
        } else if let Ok(symbol) = self.inner.try_to_symbol() {
27,076✔
747
            if symbol.index.is_some() {
19,260✔
748
                Ok(Py::new(py, PyParameterVectorElement::from_symbol(symbol))?.into_any())
5,938✔
749
            } else {
750
                Ok(Py::new(py, PyParameter::from_symbol(symbol))?.into_any())
13,322✔
751
            }
752
        } else {
753
            self.clone().into_py_any(py)
7,816✔
754
        }
755
    }
27,090✔
756
}
757

758
#[pymethods]
×
759
impl PyParameterExpression {
760
    /// This is a **strictly internal** constructor and **should not be used**.
761
    /// It is subject to arbitrary change in between Qiskit versions and cannot be relied on.
762
    /// Parameter expressions should always be constructed from applying operations on
763
    /// parameters, or by loading via QPY.
764
    ///
765
    /// The input values are allowed to be None for pickling purposes.
766
    #[new]
767
    #[pyo3(signature = (name_map=None, expr=None))]
768
    pub fn py_new(
70✔
769
        name_map: Option<HashMap<String, PyParameter>>,
70✔
770
        expr: Option<String>,
70✔
771
    ) -> PyResult<Self> {
70✔
772
        match (name_map, expr) {
70✔
773
            (None, None) => Ok(Self::default()),
×
774
            (Some(name_map), Some(expr)) => {
70✔
775
                // We first parse the expression and then update the symbols with the ones
776
                // the user provided. The replacement relies on the names to match.
777
                // This is hacky and we likely want a more reliably conversion from a SymPy object,
778
                // if we decide we want to continue supporting this.
779
                let expr = parse_expression(&expr)
70✔
780
                    .map_err(|_| PyRuntimeError::new_err("Failed parsing input expression"))?;
70✔
781
                let symbol_map: HashMap<String, Symbol> = name_map
70✔
782
                    .iter()
70✔
783
                    .map(|(string, param)| (string.clone(), param.symbol.clone()))
70✔
784
                    .collect();
70✔
785

786
                let replaced_expr = symbol_expr::replace_symbol(&expr, &symbol_map);
70✔
787

788
                let inner = ParameterExpression::new(replaced_expr, symbol_map);
70✔
789
                Ok(Self { inner })
70✔
790
            }
791
            _ => Err(PyValueError::new_err(
×
792
                "Pass either both a name_map and expr, or neither",
×
793
            )),
×
794
        }
795
    }
70✔
796

797
    #[allow(non_snake_case)]
798
    #[staticmethod]
799
    pub fn _Value(value: &Bound<PyAny>) -> PyResult<Self> {
34✔
800
        Self::extract_coerce(value)
34✔
801
    }
34✔
802

803
    /// Check if the expression corresponds to a plain symbol.
804
    ///
805
    /// Returns:
806
    ///     ``True`` is this expression corresponds to a symbol, ``False`` otherwise.
807
    pub fn is_symbol(&self) -> bool {
480✔
808
        matches!(self.inner.expr, SymbolExpr::Symbol(_))
480✔
809
    }
480✔
810

811
    /// Cast this expression to a numeric value.
812
    ///
813
    /// Args:
814
    ///     strict: If ``True`` (default) this function raises an error if there are any
815
    ///         unbound symbols in the expression. If ``False``, this allows casting
816
    ///         if the expression represents a numeric value, regardless of unbound symbols.
817
    ///         For example ``(0 * Parameter("x"))`` is 0 but has the symbol ``x`` present.
818
    #[pyo3(signature = (strict=true))]
819
    pub fn numeric(&self, py: Python, strict: bool) -> PyResult<PyObject> {
47,724✔
820
        match self.inner.try_to_value(strict)? {
47,724✔
821
            Value::Real(r) => r.into_py_any(py),
21,326✔
822
            Value::Int(i) => i.into_py_any(py),
11,792✔
823
            Value::Complex(c) => c.into_py_any(py),
14,488✔
824
        }
825
    }
47,724✔
826

827
    /// Return a SymPy equivalent of this expression.
828
    ///
829
    /// Returns:
830
    ///     A SymPy equivalent of this expression.
831
    pub fn sympify(&self, py: Python) -> PyResult<PyObject> {
480✔
832
        let py_sympify = SYMPIFY_PARAMETER_EXPRESSION.get(py);
480✔
833
        py_sympify.call1(py, (self.clone(),))
480✔
834
    }
480✔
835

836
    /// Get the parameters present in the expression.
837
    ///
838
    /// .. note::
839
    ///
840
    ///     Qiskit guarantees equality (via ``==``) of parameters retrieved from an expression
841
    ///     with the original :class:`.Parameter` objects used to create this expression,
842
    ///     but does **not guarantee** ``is`` comparisons to succeed.
843
    ///
844
    #[getter]
845
    pub fn parameters<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PySet>> {
135,906✔
846
        let py_parameters: Vec<PyObject> = self
135,906✔
847
            .inner
135,906✔
848
            .name_map
135,906✔
849
            .values()
135,906✔
850
            .map(|symbol| {
4,564,008✔
851
                match (&symbol.index, &symbol.vector) {
4,564,008✔
852
                    // if index and vector is set, it is an element
853
                    (Some(_index), Some(_vector)) => Ok(Py::new(
4,497,456✔
854
                        py,
4,497,456✔
855
                        PyParameterVectorElement::from_symbol(symbol.clone()),
4,497,456✔
856
                    )?
×
857
                    .into_any()),
4,497,456✔
858
                    // else, a normal parameter
859
                    _ => Ok(Py::new(py, PyParameter::from_symbol(symbol.clone()))?.into_any()),
66,552✔
860
                }
861
            })
4,564,008✔
862
            .collect::<PyResult<_>>()?;
135,906✔
863
        PySet::new(py, py_parameters)
135,906✔
864
    }
135,906✔
865

866
    /// Sine of the expression.
867
    #[inline]
868
    #[pyo3(name = "sin")]
869
    pub fn py_sin(&self) -> Self {
362✔
870
        self.inner.sin().into()
362✔
871
    }
362✔
872

873
    /// Cosine of the expression.
874
    #[inline]
875
    #[pyo3(name = "cos")]
876
    pub fn py_cos(&self) -> Self {
352✔
877
        self.inner.cos().into()
352✔
878
    }
352✔
879

880
    /// Tangent of the expression.
881
    #[inline]
882
    #[pyo3(name = "tan")]
883
    pub fn py_tan(&self) -> Self {
352✔
884
        self.inner.tan().into()
352✔
885
    }
352✔
886

887
    /// Arcsine of the expression.
888
    #[inline]
889
    pub fn arcsin(&self) -> Self {
100✔
890
        self.inner.asin().into()
100✔
891
    }
100✔
892

893
    /// Arccosine of the expression.
894
    #[inline]
895
    pub fn arccos(&self) -> Self {
100✔
896
        self.inner.acos().into()
100✔
897
    }
100✔
898

899
    /// Arctangent of the expression.
900
    #[inline]
901
    pub fn arctan(&self) -> Self {
100✔
902
        self.inner.atan().into()
100✔
903
    }
100✔
904

905
    /// Exponentiate the expression.
906
    #[inline]
907
    #[pyo3(name = "exp")]
908
    pub fn py_exp(&self) -> Self {
350✔
909
        self.inner.exp().into()
350✔
910
    }
350✔
911

912
    /// Take the natural logarithm of the expression.
913
    #[inline]
914
    #[pyo3(name = "log")]
915
    pub fn py_log(&self) -> Self {
266✔
916
        self.inner.log().into()
266✔
917
    }
266✔
918

919
    /// Take the absolute value of the expression.
920
    #[inline]
921
    #[pyo3(name = "abs")]
922
    pub fn py_abs(&self) -> Self {
12✔
923
        self.inner.abs().into()
12✔
924
    }
12✔
925

926
    /// Return the sign of the expression.
927
    #[inline]
928
    #[pyo3(name = "sign")]
929
    pub fn py_sign(&self) -> Self {
10✔
930
        self.inner.sign().into()
10✔
931
    }
10✔
932

933
    /// Return the complex conjugate of the expression.
934
    #[inline]
935
    #[pyo3(name = "conjugate")]
936
    pub fn py_conjugate(&self) -> Self {
1,832✔
937
        self.inner.conjugate().into()
1,832✔
938
    }
1,832✔
939

940
    /// Check whether the expression represents a real number.
941
    ///
942
    /// Note that this will return ``None`` if there are unbound parameters, in which case
943
    /// it cannot be determined whether the expression is real.
944
    #[inline]
945
    #[pyo3(name = "is_real")]
946
    pub fn py_is_real(&self) -> Option<bool> {
270✔
947
        self.inner.expr.is_real()
270✔
948
    }
270✔
949

950
    /// Return derivative of this expression with respect to the input parameter.
951
    ///
952
    /// Args:
953
    ///     param: The parameter with respect to which the derivative is calculated.
954
    ///
955
    /// Returns:
956
    ///     The derivative.
957
    pub fn gradient(&self, param: &Bound<'_, PyAny>) -> PyResult<Self> {
60✔
958
        let symbol = symbol_from_py_parameter(param)?;
60✔
959
        let d_expr = self.inner.derivative(&symbol)?;
60✔
960
        Ok(d_expr.into())
56✔
961
    }
60✔
962

963
    /// Return all values in this equation.
964
    pub fn _values(&self, py: Python) -> PyResult<Vec<PyObject>> {
78✔
965
        self.inner
78✔
966
            .expr
78✔
967
            .values()
78✔
968
            .iter()
78✔
969
            .map(|val| match val {
78✔
970
                Value::Real(r) => r.into_py_any(py),
34✔
971
                Value::Int(i) => i.into_py_any(py),
10✔
972
                Value::Complex(c) => c.into_py_any(py),
×
973
            })
44✔
974
            .collect()
78✔
975
    }
78✔
976

977
    /// Returns a new expression with replacement parameters.
978
    ///
979
    /// Args:
980
    ///     parameter_map: Mapping from :class:`.Parameter`\ s in ``self`` to the
981
    ///         :class:`.ParameterExpression` instances with which they should be replaced.
982
    ///     allow_unknown_parameters: If ``False``, raises an error if ``parameter_map``
983
    ///         contains :class:`.Parameter`\ s in the keys outside those present in the expression.
984
    ///         If ``True``, any such parameters are simply ignored.
985
    ///
986
    /// Raises:
987
    ///     CircuitError:
988
    ///         - If parameter_map contains parameters outside those in self.
989
    ///         - If the replacement parameters in ``parameter_map`` would result in
990
    ///           a name conflict in the generated expression.
991
    ///
992
    /// Returns:
993
    ///     A new expression with the specified parameters replaced.
994
    #[pyo3(name = "subs")]
995
    #[pyo3(signature = (parameter_map, allow_unknown_parameters=false))]
996
    pub fn py_subs(
228✔
997
        &self,
228✔
998
        parameter_map: HashMap<PyParameter, Self>,
228✔
999
        allow_unknown_parameters: bool,
228✔
1000
    ) -> PyResult<Self> {
228✔
1001
        // reduce the map to a HashMap<Symbol, ParameterExpression>
1002
        let map = parameter_map
228✔
1003
            .into_iter()
228✔
1004
            .map(|(param, expr)| Ok((param.symbol, expr.inner)))
232✔
1005
            .collect::<PyResult<_>>()?;
228✔
1006

1007
        // apply to the inner expression
1008
        match self.inner.subs(&map, allow_unknown_parameters) {
228✔
1009
            Ok(subbed) => Ok(subbed.into()),
224✔
1010
            Err(e) => Err(e.into()),
4✔
1011
        }
1012
    }
228✔
1013

1014
    /// Binds the provided set of parameters to their corresponding values.
1015
    ///
1016
    /// Args:
1017
    ///     parameter_values: Mapping of :class:`.Parameter` instances to the numeric value to which
1018
    ///         they will be bound.
1019
    ///     allow_unknown_parameters: If ``False``, raises an error if ``parameter_values``
1020
    ///         contains :class:`.Parameter`\ s in the keys outside those present in the expression.
1021
    ///         If ``True``, any such parameters are simply ignored.
1022
    ///
1023
    /// Raises:
1024
    ///     CircuitError:
1025
    ///         - If parameter_values contains parameters outside those in self.
1026
    ///         - If a non-numeric value is passed in ``parameter_values``.
1027
    ///     ZeroDivisionError:
1028
    ///         - If binding the provided values requires division by zero.
1029
    ///
1030
    /// Returns:
1031
    ///     A new expression parameterized by any parameters which were not bound by
1032
    ///     ``parameter_values``.
1033
    #[pyo3(name = "bind")]
1034
    #[pyo3(signature = (parameter_values, allow_unknown_parameters=false))]
1035
    pub fn py_bind(
127,064✔
1036
        &self,
127,064✔
1037
        parameter_values: HashMap<PyParameter, Bound<PyAny>>,
127,064✔
1038
        allow_unknown_parameters: bool,
127,064✔
1039
    ) -> PyResult<Self> {
127,064✔
1040
        // reduce the map to a HashMap<Symbol, Value>
1041
        let map = parameter_values
127,064✔
1042
            .iter()
127,064✔
1043
            .map(|(param, value)| {
4,555,038✔
1044
                let value = value.extract()?;
4,555,038✔
1045
                Ok((param.symbol(), value))
4,555,038✔
1046
            })
4,555,038✔
1047
            .collect::<PyResult<_>>()?;
127,064✔
1048

1049
        // apply to the inner expression
1050
        match self.inner.bind(&map, allow_unknown_parameters) {
127,064✔
1051
            Ok(bound) => Ok(bound.into()),
125,236✔
1052
            Err(e) => Err(e.into()),
1,828✔
1053
        }
1054
    }
127,064✔
1055

1056
    /// Assign one parameter to a value, which can either be numeric or another parameter
1057
    /// expression.
1058
    ///
1059
    /// Args:
1060
    ///     parameter: A parameter in this expression whose value will be updated.
1061
    ///     value: The new value to bind to.
1062
    ///
1063
    /// Returns:
1064
    ///     A new expression parameterized by any parameters which were not bound by assignment.
1065
    #[pyo3(name = "assign")]
1066
    pub fn py_assign(&self, parameter: PyParameter, value: &Bound<PyAny>) -> PyResult<Self> {
174✔
1067
        if let Ok(expr) = value.downcast::<Self>() {
174✔
1068
            let map = [(parameter, expr.borrow().clone())].into_iter().collect();
150✔
1069
            self.py_subs(map, false)
150✔
1070
        } else if value.extract::<Value>().is_ok() {
24✔
1071
            let map = [(parameter, value.clone())].into_iter().collect();
24✔
1072
            self.py_bind(map, false)
24✔
1073
        } else {
1074
            Err(PyValueError::new_err(
×
1075
                "Unexpected value in assign: {replacement:?}",
×
1076
            ))
×
1077
        }
1078
    }
174✔
1079

1080
    #[inline]
1081
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1082
        // ParameterExpression is immutable.
1083
        slf
×
1084
    }
×
1085

1086
    #[inline]
1087
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
242✔
1088
        // Everything a ParameterExpression contains is immutable.
1089
        slf
242✔
1090
    }
242✔
1091

1092
    pub fn __eq__(&self, rhs: &Bound<PyAny>) -> PyResult<bool> {
33,376✔
1093
        if let Ok(rhs) = Self::extract_coerce(rhs) {
33,376✔
1094
            match rhs.inner.expr {
33,364✔
1095
                SymbolExpr::Value(v) => match self.inner.try_to_value(false) {
2,908✔
1096
                    Ok(e) => Ok(e == v),
2,708✔
1097
                    Err(_) => Ok(false),
200✔
1098
                },
1099
                _ => Ok(self.inner.expr == rhs.inner.expr),
30,456✔
1100
            }
1101
        } else {
1102
            Ok(false)
12✔
1103
        }
1104
    }
33,376✔
1105

1106
    #[inline]
1107
    pub fn __abs__(&self) -> Self {
362✔
1108
        self.inner.abs().into()
362✔
1109
    }
362✔
1110

1111
    #[inline]
1112
    pub fn __pos__(&self) -> Self {
4✔
1113
        self.clone()
4✔
1114
    }
4✔
1115

1116
    pub fn __neg__(&self) -> Self {
484✔
1117
        Self {
484✔
1118
            inner: ParameterExpression::new(-&self.inner.expr, self.inner.name_map.clone()),
484✔
1119
        }
484✔
1120
    }
484✔
1121

1122
    pub fn __add__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
2,318,292✔
1123
        if let Ok(rhs) = Self::extract_coerce(rhs) {
2,318,292✔
1124
            Ok(self.inner.add(&rhs.inner)?.into())
2,318,280✔
1125
        } else {
1126
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1127
                "Unsupported data type for __add__",
12✔
1128
            ))
12✔
1129
        }
1130
    }
2,318,292✔
1131

1132
    pub fn __radd__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
63,998✔
1133
        if let Ok(lhs) = Self::extract_coerce(lhs) {
63,998✔
1134
            Ok(lhs.inner.add(&self.inner)?.into())
63,986✔
1135
        } else {
1136
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1137
                "Unsupported data type for __radd__",
12✔
1138
            ))
12✔
1139
        }
1140
    }
63,998✔
1141

1142
    pub fn __sub__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
18,456✔
1143
        if let Ok(rhs) = Self::extract_coerce(rhs) {
18,456✔
1144
            Ok(self.inner.sub(&rhs.inner)?.into())
18,444✔
1145
        } else {
1146
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1147
                "Unsupported data type for __sub__",
12✔
1148
            ))
12✔
1149
        }
1150
    }
18,456✔
1151

1152
    pub fn __rsub__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
4,366✔
1153
        if let Ok(lhs) = Self::extract_coerce(lhs) {
4,366✔
1154
            Ok(lhs.inner.sub(&self.inner)?.into())
4,354✔
1155
        } else {
1156
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1157
                "Unsupported data type for __rsub__",
12✔
1158
            ))
12✔
1159
        }
1160
    }
4,366✔
1161

1162
    pub fn __mul__<'py>(&self, rhs: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
2,408,100✔
1163
        let py = rhs.py();
2,408,100✔
1164
        if let Ok(rhs) = Self::extract_coerce(rhs) {
2,408,100✔
1165
            match self.inner.mul(&rhs.inner) {
2,400,744✔
1166
                Ok(result) => PyParameterExpression::from(result).into_bound_py_any(py),
2,400,742✔
1167
                Err(e) => Err(PyErr::from(e)),
2✔
1168
            }
1169
        } else {
1170
            PyNotImplemented::get(py).into_bound_py_any(py)
7,356✔
1171
        }
1172
    }
2,408,100✔
1173

1174
    pub fn __rmul__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
12,060✔
1175
        if let Ok(lhs) = Self::extract_coerce(lhs) {
12,060✔
1176
            Ok(lhs.inner.mul(&self.inner)?.into())
12,048✔
1177
        } else {
1178
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1179
                "Unsupported data type for __rmul__",
12✔
1180
            ))
12✔
1181
        }
1182
    }
12,060✔
1183

1184
    pub fn __truediv__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
8,528✔
1185
        if let Ok(rhs) = Self::extract_coerce(rhs) {
8,528✔
1186
            Ok(self.inner.div(&rhs.inner)?.into())
8,516✔
1187
        } else {
1188
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1189
                "Unsupported data type for __truediv__",
12✔
1190
            ))
12✔
1191
        }
1192
    }
8,528✔
1193

1194
    pub fn __rtruediv__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
4,070✔
1195
        if let Ok(lhs) = Self::extract_coerce(lhs) {
4,070✔
1196
            Ok(lhs.inner.div(&self.inner)?.into())
4,058✔
1197
        } else {
1198
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1199
                "Unsupported data type for __rtruediv__",
12✔
1200
            ))
12✔
1201
        }
1202
    }
4,070✔
1203

1204
    pub fn __pow__(&self, rhs: &Bound<PyAny>, _modulo: Option<i32>) -> PyResult<Self> {
1,998✔
1205
        if let Ok(rhs) = Self::extract_coerce(rhs) {
1,998✔
1206
            Ok(self.inner.pow(&rhs.inner)?.into())
1,986✔
1207
        } else {
1208
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1209
                "Unsupported data type for __pow__",
12✔
1210
            ))
12✔
1211
        }
1212
    }
1,998✔
1213

1214
    pub fn __rpow__(&self, lhs: &Bound<PyAny>, _modulo: Option<i32>) -> PyResult<Self> {
1,042✔
1215
        if let Ok(lhs) = Self::extract_coerce(lhs) {
1,042✔
1216
            Ok(lhs.inner.pow(&self.inner)?.into())
1,030✔
1217
        } else {
1218
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1219
                "Unsupported data type for __rpow__",
12✔
1220
            ))
12✔
1221
        }
1222
    }
1,042✔
1223

1224
    pub fn __int__(&self) -> PyResult<i64> {
176✔
1225
        match self.inner.try_to_value(false)? {
176✔
1226
            Value::Complex(_) => Err(PyTypeError::new_err(
42✔
1227
                "Cannot cast complex parameter to int.",
42✔
1228
            )),
42✔
1229
            Value::Real(r) => {
90✔
1230
                let rounded = r.floor();
90✔
1231
                Ok(rounded as i64)
90✔
1232
            }
1233
            Value::Int(i) => Ok(i),
42✔
1234
        }
1235
    }
176✔
1236

1237
    pub fn __float__(&self) -> PyResult<f64> {
666✔
1238
        match self.inner.try_to_value(false)? {
666✔
1239
            Value::Complex(c) => {
44✔
1240
                if c.im.abs() > SYMEXPR_EPSILON {
44✔
1241
                    Err(PyTypeError::new_err(
44✔
1242
                        "Could not cast complex parameter expression to float.",
44✔
1243
                    ))
44✔
1244
                } else {
1245
                    Ok(c.re)
×
1246
                }
1247
            }
1248
            Value::Real(r) => Ok(r),
114✔
1249
            Value::Int(i) => Ok(i as f64),
56✔
1250
        }
1251
    }
666✔
1252

1253
    pub fn __complex__(&self) -> PyResult<Complex64> {
77,856✔
1254
        match self.inner.try_to_value(false)? {
77,856✔
1255
            Value::Complex(c) => Ok(c),
23,162✔
1256
            Value::Real(r) => Ok(Complex64::new(r, 0.)),
39,574✔
1257
            Value::Int(i) => Ok(Complex64::new(i as f64, 0.)),
15,118✔
1258
        }
1259
    }
77,856✔
1260

1261
    pub fn __str__(&self) -> String {
336,786✔
1262
        self.to_string()
336,786✔
1263
    }
336,786✔
1264

1265
    pub fn __hash__(&self, py: Python) -> PyResult<u64> {
9,164,728✔
1266
        match self.inner.try_to_value(false) {
9,164,728✔
1267
            // if a value, we promise to match the hash of the raw value!
1268
            Ok(value) => {
2✔
1269
                let py_hash = BUILTIN_HASH.get_bound(py);
2✔
1270
                match value {
2✔
1271
                    Value::Complex(c) => py_hash.call1((c,))?.extract::<u64>(),
×
1272
                    Value::Real(r) => py_hash.call1((r,))?.extract::<u64>(),
2✔
1273
                    Value::Int(i) => py_hash.call1((i,))?.extract::<u64>(),
×
1274
                }
1275
            }
1276
            Err(_) => {
1277
                let mut hasher = DefaultHasher::new();
9,164,726✔
1278
                self.inner.expr.string_id().hash(&mut hasher);
9,164,726✔
1279
                Ok(hasher.finish())
9,164,726✔
1280
            }
1281
        }
1282
    }
9,164,728✔
1283

1284
    fn __getstate__(&self) -> PyResult<(Vec<OPReplay>, Option<ParameterValueType>)> {
6,132✔
1285
        // We distinguish in two cases:
1286
        //  (a) This is indeed an expression which can be rebuild from the QPY replay. This means
1287
        //      the replay is *not empty* and it contains all symbols.
1288
        //  (b) This expression is in fact only a Value or a Symbol. In this case, the QPY replay
1289
        //      will be empty and we instead pass a `ParameterValueType` for reconstruction.
1290
        let qpy = self._qpy_replay()?;
6,132✔
1291
        if !qpy.is_empty() {
6,132✔
1292
            Ok((qpy, None))
6,132✔
1293
        } else {
1294
            let value = ParameterValueType::extract_from_expr(&self.inner.expr);
×
1295
            if value.is_none() {
×
1296
                Err(PyValueError::new_err(format!(
×
1297
                    "Failed to serialize the parameter expression: {self:?}"
×
1298
                )))
×
1299
            } else {
1300
                Ok((qpy, value))
×
1301
            }
1302
        }
1303
    }
6,132✔
1304

1305
    fn __setstate__(&mut self, state: (Vec<OPReplay>, Option<ParameterValueType>)) -> PyResult<()> {
×
1306
        // if there a replay, load from the replay
1307
        if !state.0.is_empty() {
×
1308
            let from_qpy = ParameterExpression::from_qpy(&state.0)?;
×
1309
            self.inner = from_qpy;
×
1310
        // otherwise, load from the ParameterValueType
1311
        } else if let Some(value) = state.1 {
×
1312
            let expr = ParameterExpression::from(value);
×
1313
            self.inner = expr;
×
1314
        } else {
×
1315
            return Err(PyValueError::new_err(
×
1316
                "Failed to read QPY replay or extract value.",
×
1317
            ));
×
1318
        }
1319
        Ok(())
×
1320
    }
×
1321

1322
    #[getter]
1323
    fn _qpy_replay(&self) -> PyResult<Vec<OPReplay>> {
6,434✔
1324
        let mut replay = Vec::new();
6,434✔
1325
        qpy_replay(&self.inner, &self.inner.name_map, &mut replay);
6,434✔
1326
        Ok(replay)
6,434✔
1327
    }
6,434✔
1328
}
1329

1330
/// A compile-time symbolic parameter.
1331
///
1332
/// The value of a :class:`.Parameter` must be entirely determined before a circuit begins execution.
1333
/// Typically this will mean that you should supply values for all :class:`.Parameter`\ s in a
1334
/// circuit using :meth:`.QuantumCircuit.assign_parameters`, though certain hardware vendors may
1335
/// allow you to give them a circuit in terms of these parameters, provided you also pass the values
1336
/// separately.
1337
///
1338
/// This is the atom of :class:`.ParameterExpression`, and is itself an expression.  The numeric
1339
/// value of a parameter need not be fixed while the circuit is being defined.
1340
///
1341
/// Examples:
1342
///
1343
///     Construct a variable-rotation X gate using circuit parameters.
1344
///
1345
///     .. plot::
1346
///         :alt: Circuit diagram output by the previous code.
1347
///         :include-source:
1348
///
1349
///         from qiskit.circuit import QuantumCircuit, Parameter
1350
///
1351
///         # create the parameter
1352
///         phi = Parameter("phi")
1353
///         qc = QuantumCircuit(1)
1354
///
1355
///         # parameterize the rotation
1356
///         qc.rx(phi, 0)
1357
///         qc.draw("mpl")
1358
///
1359
///         # bind the parameters after circuit to create a bound circuit
1360
///         bc = qc.assign_parameters({phi: 3.14})
1361
///         bc.measure_all()
1362
///         bc.draw("mpl")
1363
#[pyclass(sequence, subclass, module="qiskit._accelerate.circuit", extends=PyParameterExpression, name="Parameter")]
1364
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)]
1365
pub struct PyParameter {
1366
    symbol: Symbol,
1367
}
1368

1369
impl Hash for PyParameter {
1370
    fn hash<H: Hasher>(&self, state: &mut H) {
4,556,396✔
1371
        self.symbol.hash(state);
4,556,396✔
1372
    }
4,556,396✔
1373
}
1374

1375
// This needs to be implemented manually, since PyO3 does not provide this conversion
1376
// for subclasses.
1377
impl<'py> IntoPyObject<'py> for PyParameter {
1378
    type Target = PyParameter;
1379
    type Output = Bound<'py, Self::Target>;
1380
    type Error = PyErr;
1381

1382
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
7,168✔
1383
        let symbol = &self.symbol;
7,168✔
1384
        let symbol_expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
7,168✔
1385
        let expr = ParameterExpression::from_symbol_expr(symbol_expr);
7,168✔
1386
        let py_expr = PyParameterExpression::from(expr);
7,168✔
1387

1388
        Ok(Py::new(py, (self, py_expr))?.into_bound(py))
7,168✔
1389
    }
7,168✔
1390
}
1391

1392
impl PyParameter {
1393
    /// Get a Python class initialization from a symbol.
1394
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
4,669,326✔
1395
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
4,669,326✔
1396

1397
        let py_parameter = Self { symbol };
4,669,326✔
1398
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
4,669,326✔
1399

1400
        PyClassInitializer::from(py_expr).add_subclass(py_parameter)
4,669,326✔
1401
    }
4,669,326✔
1402

1403
    /// Get a reference to the underlying symbol.
1404
    pub fn symbol(&self) -> &Symbol {
4,557,332✔
1405
        &self.symbol
4,557,332✔
1406
    }
4,557,332✔
1407
}
1408

1409
#[pymethods]
×
1410
impl PyParameter {
1411
    /// Args:
1412
    ///     name: name of the parameter, used for visual representation. This can
1413
    ///         be any Unicode string, e.g. ``"Ï•"``.
1414
    ///     uuid: For advanced usage only.  Override the UUID of this parameter, in order to make it
1415
    ///         compare equal to some other parameter object.  By default, two parameters with the
1416
    ///         same name do not compare equal to help catch shadowing bugs when two circuits
1417
    ///         containing the same named parameters are spurious combined.  Setting the ``uuid``
1418
    ///         field when creating two parameters to the same thing (along with the same name)
1419
    ///         allows them to be equal.  This is useful during serialization and deserialization.
1420
    #[new]
1421
    #[pyo3(signature = (name, uuid=None))]
1422
    fn py_new(
81,744✔
1423
        py: Python<'_>,
81,744✔
1424
        name: String,
81,744✔
1425
        uuid: Option<PyObject>,
81,744✔
1426
    ) -> PyResult<PyClassInitializer<Self>> {
81,744✔
1427
        let uuid = uuid_from_py(py, uuid)?;
81,744✔
1428
        let symbol = Symbol::new(name.as_str(), uuid, None);
81,744✔
1429
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
81,744✔
1430

1431
        let py_parameter = Self { symbol };
81,744✔
1432
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
81,744✔
1433

1434
        Ok(PyClassInitializer::from(py_expr).add_subclass(py_parameter))
81,744✔
1435
    }
81,744✔
1436

1437
    /// Returns the name of the :class:`.Parameter`.
1438
    #[getter]
1439
    fn name<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
21,334✔
1440
        PyString::new(py, &self.symbol.repr(false))
21,334✔
1441
    }
21,334✔
1442

1443
    /// Returns the :class:`~uuid.UUID` of the :class:`Parameter`.
1444
    ///
1445
    /// In advanced use cases, this property can be passed to the
1446
    /// :class:`.Parameter` constructor to produce an instance that compares
1447
    /// equal to another instance.
1448
    #[getter]
1449
    fn uuid(&self, py: Python<'_>) -> PyResult<PyObject> {
1,368✔
1450
        uuid_to_py(py, self.symbol.uuid)
1,368✔
1451
    }
1,368✔
1452

1453
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
64✔
1454
        let str = format!("Parameter({})", self.symbol.repr(false),);
64✔
1455
        PyString::new(py, str.as_str())
64✔
1456
    }
64✔
1457

1458
    pub fn __getnewargs__(&self) -> (String, u128) {
18,780✔
1459
        (self.symbol.repr(false), self.symbol.uuid.as_u128())
18,780✔
1460
    }
18,780✔
1461

1462
    pub fn __getstate__(&self) -> (String, u128) {
18,780✔
1463
        (self.symbol.repr(false), self.symbol.uuid.as_u128())
18,780✔
1464
    }
18,780✔
1465

1466
    pub fn __setstate__(&mut self, state: (String, u128)) {
4✔
1467
        let name = state.0.as_str();
4✔
1468
        let uuid = Uuid::from_u128(state.1);
4✔
1469
        let symbol = Symbol::new(name, Some(uuid), None);
4✔
1470
        self.symbol = symbol;
4✔
1471
    }
4✔
1472

1473
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1474
        // Parameter is immutable. Note that this **cannot** be deferred to the parent class
1475
        // since PyO3 would then always return the parent type.
1476
        slf
×
1477
    }
×
1478

1479
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
8✔
1480
        // Everything inside a Parameter is immutable. Note that this **cannot** be deferred to the
1481
        // parent class since PyO3 would then always return the parent type.
1482
        slf
8✔
1483
    }
8✔
1484

1485
    #[pyo3(name = "subs")]
1486
    #[pyo3(signature = (parameter_map, allow_unknown_parameters=false))]
1487
    pub fn py_subs<'py>(
416✔
1488
        &self,
416✔
1489
        py: Python<'py>,
416✔
1490
        parameter_map: HashMap<PyParameter, Bound<'py, PyAny>>,
416✔
1491
        allow_unknown_parameters: bool,
416✔
1492
    ) -> PyResult<Bound<'py, PyAny>> {
416✔
1493
        // We implement this method on this class, and do not defer to the parent, such
1494
        // that x.subs({x: y}) remains a Parameter, and is not upgraded to an expression.
1495
        // Also this should be faster than going via ParameterExpression, which constructs
1496
        // intermediary HashMaps we don't need here.
1497
        match parameter_map.get(self) {
416✔
1498
            None => {
1499
                if allow_unknown_parameters {
4✔
1500
                    self.clone().into_bound_py_any(py)
2✔
1501
                } else {
1502
                    Err(CircuitError::new_err(
2✔
1503
                        "Cannot bind parameters not present in parameter.",
2✔
1504
                    ))
2✔
1505
                }
1506
            }
1507
            Some(replacement) => {
412✔
1508
                if allow_unknown_parameters || parameter_map.len() == 1 {
412✔
1509
                    Ok(replacement.clone())
412✔
1510
                } else {
1511
                    Err(CircuitError::new_err(
×
1512
                        "Cannot bind parameters not present in parameter.",
×
1513
                    ))
×
1514
                }
1515
            }
1516
        }
1517
    }
416✔
1518

1519
    #[pyo3(name = "bind")]
1520
    #[pyo3(signature = (parameter_values, allow_unknown_parameters=false))]
1521
    pub fn py_bind<'py>(
146✔
1522
        &self,
146✔
1523
        py: Python<'py>,
146✔
1524
        parameter_values: HashMap<PyParameter, Bound<'py, PyAny>>,
146✔
1525
        allow_unknown_parameters: bool,
146✔
1526
    ) -> PyResult<Bound<'py, PyAny>> {
146✔
1527
        // Returns PyAny to cover Parameter and ParameterExpression(value).
1528
        match parameter_values.get(self) {
146✔
1529
            None => {
1530
                if allow_unknown_parameters {
×
1531
                    self.clone().into_bound_py_any(py)
×
1532
                } else {
1533
                    Err(CircuitError::new_err(
×
1534
                        "Cannot bind parameters not present in parameter.",
×
1535
                    ))
×
1536
                }
1537
            }
1538
            Some(replacement) => {
146✔
1539
                if allow_unknown_parameters || parameter_values.len() == 1 {
146✔
1540
                    let expr = PyParameterExpression::extract_coerce(replacement)?;
146✔
1541
                    if let SymbolExpr::Value(_) = &expr.inner.expr {
146✔
1542
                        expr.clone().into_bound_py_any(py)
146✔
1543
                    } else {
1544
                        Err(PyValueError::new_err("Invalid binding value."))
×
1545
                    }
1546
                } else {
1547
                    Err(CircuitError::new_err(
×
1548
                        "Cannot bind parameters not present in parameter.",
×
1549
                    ))
×
1550
                }
1551
            }
1552
        }
1553
    }
146✔
1554

1555
    #[pyo3(name = "assign")]
1556
    pub fn py_assign<'py>(
408✔
1557
        &self,
408✔
1558
        py: Python<'py>,
408✔
1559
        parameter: PyParameter,
408✔
1560
        value: &Bound<'py, PyAny>,
408✔
1561
    ) -> PyResult<Bound<'py, PyAny>> {
408✔
1562
        if value.downcast::<PyParameterExpression>().is_ok() {
408✔
1563
            let map = [(parameter, value.clone())].into_iter().collect();
408✔
1564
            self.py_subs(py, map, false)
408✔
1565
        } else if value.extract::<Value>().is_ok() {
×
1566
            let map = [(parameter, value.clone())].into_iter().collect();
×
1567
            self.py_bind(py, map, false)
×
1568
        } else {
1569
            Err(PyValueError::new_err(
×
1570
                "Unexpected value in assign: {replacement:?}",
×
1571
            ))
×
1572
        }
1573
    }
408✔
1574
}
1575

1576
/// An element of a :class:`.ParameterVector`.
1577
///
1578
/// .. note::
1579
///     There is very little reason to ever construct this class directly.  Objects of this type are
1580
///     automatically constructed efficiently as part of creating a :class:`.ParameterVector`.
1581
#[pyclass(sequence, subclass, module="qiskit._accelerate.circuit", extends=PyParameter, name="ParameterVectorElement")]
1582
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
1583
pub struct PyParameterVectorElement {
1584
    symbol: Symbol,
1585
}
1586

1587
impl<'py> IntoPyObject<'py> for PyParameterVectorElement {
1588
    type Target = PyParameterVectorElement;
1589
    type Output = Bound<'py, Self::Target>;
1590
    type Error = PyErr;
1591

1592
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
176✔
1593
        let symbol = &self.symbol;
176✔
1594
        let py_param = PyParameter::from_symbol(symbol.clone());
176✔
1595
        let py_element = py_param.add_subclass(self);
176✔
1596

1597
        Ok(Py::new(py, py_element)?.into_bound(py))
176✔
1598
    }
176✔
1599
}
1600

1601
impl PyParameterVectorElement {
1602
    pub fn symbol(&self) -> &Symbol {
16,460✔
1603
        &self.symbol
16,460✔
1604
    }
16,460✔
1605

1606
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
4,556,066✔
1607
        let py_element = Self {
4,556,066✔
1608
            symbol: symbol.clone(),
4,556,066✔
1609
        };
4,556,066✔
1610
        let py_parameter = PyParameter::from_symbol(symbol);
4,556,066✔
1611

1612
        py_parameter.add_subclass(py_element)
4,556,066✔
1613
    }
4,556,066✔
1614
}
1615

1616
#[pymethods]
×
1617
impl PyParameterVectorElement {
1618
    #[new]
1619
    #[pyo3(signature = (vector, index, uuid=None))]
1620
    pub fn py_new(
27,860✔
1621
        py: Python<'_>,
27,860✔
1622
        vector: PyObject,
27,860✔
1623
        index: u32,
27,860✔
1624
        uuid: Option<PyObject>,
27,860✔
1625
    ) -> PyResult<PyClassInitializer<Self>> {
27,860✔
1626
        let vector_name = vector.getattr(py, "name")?.extract::<String>(py)?;
27,860✔
1627
        let uuid = uuid_from_py(py, uuid)?.unwrap_or(Uuid::new_v4());
27,860✔
1628

1629
        let symbol = Symbol::py_new(
27,860✔
1630
            &vector_name,
27,860✔
1631
            Some(uuid.as_u128()),
27,860✔
1632
            Some(index),
27,860✔
1633
            Some(vector.clone_ref(py)),
27,860✔
1634
        )?;
×
1635

1636
        let py_parameter = PyParameter::from_symbol(symbol.clone());
27,860✔
1637
        let py_element = Self { symbol };
27,860✔
1638

1639
        Ok(py_parameter.add_subclass(py_element))
27,860✔
1640
    }
27,860✔
1641

1642
    pub fn __getnewargs__(&self, py: Python) -> PyResult<(PyObject, u32, Option<PyObject>)> {
8✔
1643
        let vector = self
8✔
1644
            .symbol
8✔
1645
            .vector
8✔
1646
            .clone()
8✔
1647
            .expect("vector element should have a vector");
8✔
1648
        let index = self
8✔
1649
            .symbol
8✔
1650
            .index
8✔
1651
            .expect("vector element should have an index");
8✔
1652
        let uuid = uuid_to_py(py, self.symbol.uuid)?;
8✔
1653
        Ok((vector, index, Some(uuid)))
8✔
1654
    }
8✔
1655

1656
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
48✔
1657
        let str = format!("ParameterVectorElement({})", self.symbol.repr(false),);
48✔
1658
        PyString::new(py, str.as_str())
48✔
1659
    }
48✔
1660

1661
    pub fn __getstate__(&self, py: Python) -> PyResult<(PyObject, u32, Option<PyObject>)> {
8✔
1662
        self.__getnewargs__(py)
8✔
1663
    }
8✔
1664

1665
    pub fn __setstate__(
×
1666
        &mut self,
×
1667
        py: Python,
×
1668
        state: (PyObject, u32, Option<PyObject>),
×
1669
    ) -> PyResult<()> {
×
1670
        let vector = state.0;
×
1671
        let index = state.1;
×
1672
        let vector_name = vector.getattr(py, "name")?.extract::<String>(py)?;
×
1673
        let uuid = uuid_from_py(py, state.2)?.map(|id| id.as_u128());
×
1674
        self.symbol = Symbol::py_new(&vector_name, uuid, Some(index), Some(vector))?;
×
1675
        Ok(())
×
1676
    }
×
1677

1678
    /// Get the index of this element in the parent vector.
1679
    #[getter]
1680
    pub fn index(&self) -> u32 {
336✔
1681
        self.symbol
336✔
1682
            .index
336✔
1683
            .expect("A vector element should have an index")
336✔
1684
    }
336✔
1685

1686
    /// Get the parent vector instance.
1687
    #[getter]
1688
    pub fn vector(&self) -> PyObject {
668✔
1689
        self.symbol
668✔
1690
            .clone()
668✔
1691
            .vector
668✔
1692
            .expect("A vector element should have a vector")
668✔
1693
    }
668✔
1694

1695
    /// For backward compatibility only. This should not be used and we ought to update those
1696
    /// usages!
1697
    #[getter]
1698
    pub fn _vector(&self) -> PyObject {
×
1699
        self.vector()
×
1700
    }
×
1701

1702
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1703
        // ParameterVectorElement is immutable.
1704
        slf
×
1705
    }
×
1706

1707
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
8✔
1708
        // Everything a ParameterVectorElement contains is immutable.
1709
        slf
8✔
1710
    }
8✔
1711
}
1712

1713
/// Try to extract a Uuid from a Python object, which could be a Python UUID or int.
1714
fn uuid_from_py(py: Python<'_>, uuid: Option<PyObject>) -> PyResult<Option<Uuid>> {
109,604✔
1715
    if let Some(val) = uuid {
109,604✔
1716
        // construct from u128
1717
        let as_u128 = if let Ok(as_u128) = val.extract::<u128>(py) {
28,030✔
1718
            as_u128
4✔
1719
        // construct from Python UUID type
1720
        } else if val.bind(py).is_exact_instance(UUID.get_bound(py)) {
28,026✔
1721
            val.getattr(py, "int")?.extract::<u128>(py)?
28,026✔
1722
        // invalid format
1723
        } else {
1724
            return Err(PyTypeError::new_err("not a UUID!"));
×
1725
        };
1726
        Ok(Some(Uuid::from_u128(as_u128)))
28,030✔
1727
    } else {
1728
        Ok(None)
81,574✔
1729
    }
1730
}
109,604✔
1731

1732
/// Convert a Rust Uuid object to a Python UUID object.
1733
fn uuid_to_py(py: Python<'_>, uuid: Uuid) -> PyResult<PyObject> {
1,376✔
1734
    let uuid = uuid.as_u128();
1,376✔
1735
    let kwargs = [("int", uuid)].into_py_dict(py)?;
1,376✔
1736
    Ok(UUID.get_bound(py).call((), Some(&kwargs))?.unbind())
1,376✔
1737
}
1,376✔
1738

1739
/// Extract a [Symbol] for a Python object, which could either be a Parameter or a
1740
/// ParameterVectorElement.
1741
fn symbol_from_py_parameter(param: &Bound<'_, PyAny>) -> PyResult<Symbol> {
60✔
1742
    if let Ok(element) = param.extract::<PyParameterVectorElement>() {
60✔
1743
        Ok(element.symbol.clone())
×
1744
    } else if let Ok(parameter) = param.extract::<PyParameter>() {
60✔
1745
        Ok(parameter.symbol.clone())
60✔
1746
    } else {
1747
        Err(PyValueError::new_err("Could not extract parameter"))
×
1748
    }
1749
}
60✔
1750

1751
/// A singular parameter value used for QPY serialization. This covers anything
1752
/// but a [PyParameterExpression], which is represented by [None] in the serialization.
1753
#[derive(IntoPyObject, FromPyObject, Clone, Debug)]
1754
pub enum ParameterValueType {
1755
    Int(i64),
1756
    Float(f64),
1757
    Complex(Complex64),
1758
    Parameter(PyParameter),
1759
    VectorElement(PyParameterVectorElement),
1760
}
1761

1762
impl ParameterValueType {
1763
    fn extract_from_expr(expr: &SymbolExpr) -> Option<ParameterValueType> {
14,398✔
1764
        if let Some(value) = expr.eval(true) {
14,398✔
1765
            match value {
5,888✔
1766
                Value::Int(i) => Some(ParameterValueType::Int(i)),
100✔
1767
                Value::Real(r) => Some(ParameterValueType::Float(r)),
5,780✔
1768
                Value::Complex(c) => Some(ParameterValueType::Complex(c)),
8✔
1769
            }
1770
        } else if let SymbolExpr::Symbol(symbol) = expr {
8,510✔
1771
            match symbol.index {
7,342✔
1772
                None => {
1773
                    let param = PyParameter {
7,166✔
1774
                        symbol: symbol.as_ref().clone(),
7,166✔
1775
                    };
7,166✔
1776
                    Some(ParameterValueType::Parameter(param))
7,166✔
1777
                }
1778
                Some(_) => {
1779
                    let param = PyParameterVectorElement {
176✔
1780
                        symbol: symbol.as_ref().clone(),
176✔
1781
                    };
176✔
1782
                    Some(ParameterValueType::VectorElement(param))
176✔
1783
                }
1784
            }
1785
        } else {
1786
            // ParameterExpressions have the value None, as they must be constructed
1787
            None
1,168✔
1788
        }
1789
    }
14,398✔
1790
}
1791

1792
impl From<ParameterValueType> for ParameterExpression {
1793
    fn from(value: ParameterValueType) -> Self {
×
1794
        match value {
×
1795
            ParameterValueType::Parameter(param) => {
×
1796
                let expr = SymbolExpr::Symbol(Arc::new(param.symbol));
×
1797
                Self::from_symbol_expr(expr)
×
1798
            }
1799
            ParameterValueType::VectorElement(param) => {
×
1800
                let expr = SymbolExpr::Symbol(Arc::new(param.symbol));
×
1801
                Self::from_symbol_expr(expr)
×
1802
            }
1803
            ParameterValueType::Int(i) => {
×
1804
                let expr = SymbolExpr::Value(Value::Int(i));
×
1805
                Self::from_symbol_expr(expr)
×
1806
            }
1807
            ParameterValueType::Float(f) => {
×
1808
                let expr = SymbolExpr::Value(Value::Real(f));
×
1809
                Self::from_symbol_expr(expr)
×
1810
            }
1811
            ParameterValueType::Complex(c) => {
×
1812
                let expr = SymbolExpr::Value(Value::Complex(c));
×
1813
                Self::from_symbol_expr(expr)
×
1814
            }
1815
        }
1816
    }
×
1817
}
1818

1819
#[pyclass(module = "qiskit._accelerate.circuit")]
×
1820
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
1821
#[repr(u8)]
1822
pub enum OpCode {
1823
    ADD = 0,
1824
    SUB = 1,
1825
    MUL = 2,
1826
    DIV = 3,
1827
    POW = 4,
1828
    SIN = 5,
1829
    COS = 6,
1830
    TAN = 7,
1831
    ASIN = 8,
1832
    ACOS = 9,
1833
    EXP = 10,
1834
    LOG = 11,
1835
    SIGN = 12,
1836
    GRAD = 13, // for backward compatibility, unused in Rust's ParameterExpression
1837
    CONJ = 14,
1838
    SUBSTITUTE = 15, // for backward compatibility, unused in Rust's ParameterExpression
1839
    ABS = 16,
1840
    ATAN = 17,
1841
    RSUB = 18,
1842
    RDIV = 19,
1843
    RPOW = 20,
1844
}
1845

1846
impl From<OpCode> for u8 {
1847
    fn from(value: OpCode) -> Self {
×
1848
        value as u8
×
1849
    }
×
1850
}
1851

1852
unsafe impl ::bytemuck::CheckedBitPattern for OpCode {
1853
    type Bits = u8;
1854

1855
    #[inline(always)]
1856
    fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
×
1857
        *bits <= 20
×
1858
    }
×
1859
}
1860

1861
unsafe impl ::bytemuck::NoUninit for OpCode {}
1862

1863
#[pymethods]
×
1864
impl OpCode {
1865
    #[new]
1866
    fn py_new(value: u8) -> PyResult<Self> {
×
1867
        let code: OpCode = ::bytemuck::checked::try_cast(value)
×
1868
            .map_err(|_| ParameterError::InvalidU8ToOpCode(value))?;
×
1869
        Ok(code)
×
1870
    }
×
1871

1872
    fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
560✔
1873
        if let Ok(code) = other.downcast::<OpCode>() {
560✔
1874
            *code.borrow() == *self
560✔
1875
        } else {
1876
            false
×
1877
        }
1878
    }
560✔
1879

1880
    fn __hash__(&self) -> u8 {
2,088✔
1881
        *self as u8
2,088✔
1882
    }
2,088✔
1883

1884
    fn __getnewargs__(&self) -> (u8,) {
6,972✔
1885
        (*self as u8,)
6,972✔
1886
    }
6,972✔
1887
}
1888

1889
// enum for QPY replay
1890
#[pyclass(sequence, module = "qiskit._accelerate.circuit")]
1891
#[derive(Clone, Debug)]
1892
pub struct OPReplay {
1893
    pub op: OpCode,
1894
    pub lhs: Option<ParameterValueType>,
1895
    pub rhs: Option<ParameterValueType>,
1896
}
1897

1898
#[pymethods]
×
1899
impl OPReplay {
1900
    #[new]
1901
    pub fn py_new(
×
1902
        op: OpCode,
×
1903
        lhs: Option<ParameterValueType>,
×
1904
        rhs: Option<ParameterValueType>,
×
1905
    ) -> OPReplay {
×
1906
        OPReplay { op, lhs, rhs }
×
1907
    }
×
1908

1909
    #[getter]
1910
    fn op(&self) -> OpCode {
1,194✔
1911
        self.op
1,194✔
1912
    }
1,194✔
1913

1914
    #[getter]
1915
    fn lhs(&self) -> Option<ParameterValueType> {
630✔
1916
        self.lhs.clone()
630✔
1917
    }
630✔
1918

1919
    #[getter]
1920
    fn rhs(&self) -> Option<ParameterValueType> {
630✔
1921
        self.rhs.clone()
630✔
1922
    }
630✔
1923

1924
    fn __getnewargs__(
6,972✔
1925
        &self,
6,972✔
1926
    ) -> (
6,972✔
1927
        OpCode,
6,972✔
1928
        Option<ParameterValueType>,
6,972✔
1929
        Option<ParameterValueType>,
6,972✔
1930
    ) {
6,972✔
1931
        (self.op, self.lhs.clone(), self.rhs.clone())
6,972✔
1932
    }
6,972✔
1933
}
1934

1935
/// Internal helper. Extract one part of the expression tree, keeping the name map up to date.
1936
///
1937
/// Example: Given expr1 + expr2, each being [PyParameterExpression], we need the ability to
1938
/// extract one of the expressions with the proper name map.
1939
///
1940
/// Args:
1941
///     - joint_parameter_expr: The full expression, e.g. expr1 + expr2.
1942
///     - sub_expr: The sub expression, on whose symbols we restrict the name map.
1943
fn filter_name_map(
14,398✔
1944
    sub_expr: &SymbolExpr,
14,398✔
1945
    name_map: &HashMap<String, Symbol>,
14,398✔
1946
) -> ParameterExpression {
14,398✔
1947
    let sub_symbols: HashSet<&Symbol> = sub_expr.iter_symbols().collect();
14,398✔
1948
    let restricted_name_map: HashMap<String, Symbol> = name_map
14,398✔
1949
        .iter()
14,398✔
1950
        .filter(|(_, symbol)| sub_symbols.contains(*symbol))
18,614✔
1951
        .map(|(name, symbol)| (name.clone(), symbol.clone()))
14,398✔
1952
        .collect();
14,398✔
1953

1954
    ParameterExpression {
14,398✔
1955
        expr: sub_expr.clone(),
14,398✔
1956
        name_map: restricted_name_map,
14,398✔
1957
    }
14,398✔
1958
}
14,398✔
1959

1960
pub fn qpy_replay(
20,832✔
1961
    expr: &ParameterExpression,
20,832✔
1962
    name_map: &HashMap<String, Symbol>,
20,832✔
1963
    replay: &mut Vec<OPReplay>,
20,832✔
1964
) {
20,832✔
1965
    match &expr.expr {
20,832✔
1966
        SymbolExpr::Value(_) | SymbolExpr::Symbol(_) => {
13,230✔
1967
            // nothing to do here, we only need to traverse instructions
13,230✔
1968
        }
13,230✔
1969
        SymbolExpr::Unary { op, expr } => {
806✔
1970
            let op = match op {
806✔
1971
                symbol_expr::UnaryOp::Abs => OpCode::ABS,
6✔
1972
                symbol_expr::UnaryOp::Acos => OpCode::ACOS,
6✔
1973
                symbol_expr::UnaryOp::Asin => OpCode::ASIN,
6✔
1974
                symbol_expr::UnaryOp::Atan => OpCode::ATAN,
6✔
1975
                symbol_expr::UnaryOp::Conj => OpCode::CONJ,
6✔
1976
                symbol_expr::UnaryOp::Cos => OpCode::COS,
6✔
1977
                symbol_expr::UnaryOp::Exp => OpCode::EXP,
6✔
1978
                symbol_expr::UnaryOp::Log => OpCode::LOG,
6✔
1979
                symbol_expr::UnaryOp::Neg => OpCode::MUL,
730✔
1980
                symbol_expr::UnaryOp::Sign => OpCode::SIGN,
4✔
1981
                symbol_expr::UnaryOp::Sin => OpCode::SIN,
18✔
1982
                symbol_expr::UnaryOp::Tan => OpCode::TAN,
6✔
1983
            };
1984
            // TODO filter shouldn't be necessary for unary ops
1985
            let lhs = filter_name_map(expr, name_map);
806✔
1986

1987
            // recurse on the instruction
1988
            qpy_replay(&lhs, name_map, replay);
806✔
1989

1990
            let lhs_value = ParameterValueType::extract_from_expr(expr);
806✔
1991

1992
            // MUL is special: we implement ``neg`` as multiplication by -1
1993
            if let OpCode::MUL = &op {
806✔
1994
                replay.push(OPReplay {
730✔
1995
                    op,
730✔
1996
                    lhs: lhs_value,
730✔
1997
                    rhs: Some(ParameterValueType::Int(-1)),
730✔
1998
                });
730✔
1999
            } else {
730✔
2000
                replay.push(OPReplay {
76✔
2001
                    op,
76✔
2002
                    lhs: lhs_value,
76✔
2003
                    rhs: None,
76✔
2004
                });
76✔
2005
            }
76✔
2006
        }
2007
        SymbolExpr::Binary { op, lhs, rhs } => {
6,796✔
2008
            let lhs_value = ParameterValueType::extract_from_expr(lhs);
6,796✔
2009
            let rhs_value = ParameterValueType::extract_from_expr(rhs);
6,796✔
2010

2011
            // recurse on the parameter expressions
2012
            let lhs = filter_name_map(lhs, name_map);
6,796✔
2013
            let rhs = filter_name_map(rhs, name_map);
6,796✔
2014
            qpy_replay(&lhs, name_map, replay);
6,796✔
2015
            qpy_replay(&rhs, name_map, replay);
6,796✔
2016

2017
            // add the expression to the replay
2018
            match lhs_value {
6,566✔
2019
                None
2020
                | Some(ParameterValueType::Parameter(_))
2021
                | Some(ParameterValueType::VectorElement(_)) => {
2022
                    let op = match op {
1,038✔
2023
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
592✔
2024
                        symbol_expr::BinaryOp::Sub => OpCode::SUB,
328✔
2025
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
86✔
2026
                        symbol_expr::BinaryOp::Div => OpCode::DIV,
12✔
2027
                        symbol_expr::BinaryOp::Pow => OpCode::POW,
20✔
2028
                    };
2029
                    replay.push(OPReplay {
1,038✔
2030
                        op,
1,038✔
2031
                        lhs: lhs_value,
1,038✔
2032
                        rhs: rhs_value,
1,038✔
2033
                    });
1,038✔
2034
                }
2035
                _ => {
2036
                    let op = match op {
5,758✔
2037
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
466✔
2038
                        symbol_expr::BinaryOp::Sub => OpCode::RSUB,
104✔
2039
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
5,180✔
2040
                        symbol_expr::BinaryOp::Div => OpCode::RDIV,
4✔
2041
                        symbol_expr::BinaryOp::Pow => OpCode::RPOW,
4✔
2042
                    };
2043
                    if let OpCode::ADD | OpCode::MUL = op {
5,758✔
2044
                        replay.push(OPReplay {
5,646✔
2045
                            op,
5,646✔
2046
                            lhs: lhs_value,
5,646✔
2047
                            rhs: rhs_value,
5,646✔
2048
                        });
5,646✔
2049
                    } else {
5,646✔
2050
                        // this covers RSUB, RDIV, RPOW, hence we swap lhs and rhs
112✔
2051
                        replay.push(OPReplay {
112✔
2052
                            op,
112✔
2053
                            lhs: rhs_value,
112✔
2054
                            rhs: lhs_value,
112✔
2055
                        });
112✔
2056
                    }
112✔
2057
                }
2058
            }
2059
        }
2060
    }
2061
}
20,832✔
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