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

Qiskit / qiskit / 16946508612

13 Aug 2025 06:51PM UTC coverage: 88.239% (-0.007%) from 88.246%
16946508612

Pull #14900

github

web-flow
Merge 71b02a6e3 into 1e7a3fe35
Pull Request #14900: fix: corrected `sparsify_label` docstring example to match actual return ordering

87202 of 98825 relevant lines covered (88.24%)

520386.03 hits per line

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

83.39
/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,958✔
104
        self.expr.eq(&other.expr)
17,958✔
105
    }
17,958✔
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

145
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
188,036✔
146
        let expr = PyParameterExpression::from(self.clone());
188,036✔
147
        expr.into_pyobject(py)
188,036✔
148
    }
188,036✔
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,510,438✔
170
        Self { expr, name_map }
2,510,438✔
171
    }
2,510,438✔
172

173
    /// Construct from a [Symbol].
174
    pub fn from_symbol(symbol: Symbol) -> Self {
260,640✔
175
        Self {
260,640✔
176
            expr: SymbolExpr::Symbol(Arc::new(symbol.clone())),
260,640✔
177
            name_map: [(symbol.repr(false), symbol)].into(),
260,640✔
178
        }
260,640✔
179
    }
260,640✔
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> {
236,894✔
185
        if let SymbolExpr::Symbol(symbol) = &self.expr {
236,894✔
186
            Ok(symbol.as_ref().clone())
232,624✔
187
        } else {
188
            Err(ParameterError::NotASymbol)
4,270✔
189
        }
190
    }
236,894✔
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> {
10,309,096✔
203
        if strict && !self.name_map.is_empty() {
10,309,096✔
204
            let free_symbols = self.expr.iter_symbols().cloned().collect();
214,870✔
205
            return Err(ParameterError::UnboundParameters(free_symbols));
214,870✔
206
        }
10,094,226✔
207

208
        match self.expr.eval(true) {
10,094,226✔
209
            Some(value) => {
390,442✔
210
                // we try to restrict complex to real, if possible
211
                if let Value::Complex(c) = value {
390,442✔
212
                    if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON).contains(&c.im)
40,122✔
213
                    {
214
                        return Ok(Value::Real(c.re));
×
215
                    }
40,122✔
216
                }
350,320✔
217
                Ok(value)
390,442✔
218
            }
219
            None => {
220
                let free_symbols = self.expr.iter_symbols().cloned().collect();
9,703,784✔
221
                Err(ParameterError::UnboundParameters(free_symbols))
9,703,784✔
222
            }
223
        }
224
    }
10,309,096✔
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 {
5,308,106✔
230
        let name_map = expr.name_map();
5,308,106✔
231
        Self { expr, name_map }
5,308,106✔
232
    }
5,308,106✔
233

234
    /// Initialize from an f64.
235
    pub fn from_f64(value: f64) -> Self {
23,618✔
236
        Self {
23,618✔
237
            expr: SymbolExpr::Value(Value::Real(value)),
23,618✔
238
            name_map: HashMap::new(),
23,618✔
239
        }
23,618✔
240
    }
23,618✔
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> + '_ {
277,416✔
303
        self.name_map.values()
277,416✔
304
    }
277,416✔
305

306
    /// Whether the expression represents a complex number. None if cannot be determined.
307
    pub fn is_complex(&self) -> Option<bool> {
22,970✔
308
        self.expr.is_complex()
22,970✔
309
    }
22,970✔
310

311
    /// Whether the expression represents a int. None if cannot be determined.
312
    pub fn is_int(&self) -> Option<bool> {
85,480✔
313
        self.expr.is_int()
85,480✔
314
    }
85,480✔
315

316
    /// Add an expression; ``self + rhs``.
317
    pub fn add(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,390,026✔
318
        let name_map = self.merged_name_map(rhs)?;
2,390,026✔
319
        Ok(Self {
2,390,024✔
320
            expr: &self.expr + &rhs.expr,
2,390,024✔
321
            name_map,
2,390,024✔
322
        })
2,390,024✔
323
    }
2,390,026✔
324

325
    /// Multiply with an expression; ``self * rhs``.
326
    pub fn mul(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,413,782✔
327
        let name_map = self.merged_name_map(rhs)?;
2,413,782✔
328
        Ok(Self {
2,413,780✔
329
            expr: &self.expr * &rhs.expr,
2,413,780✔
330
            name_map,
2,413,780✔
331
        })
2,413,780✔
332
    }
2,413,782✔
333

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

343
    /// Divide by another expression; ``self / rhs``.
344
    pub fn div(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
13,078✔
345
        if rhs.expr.is_zero() {
13,078✔
346
            return Err(ParameterError::ZeroDivisionError);
1,012✔
347
        }
12,066✔
348

349
        let name_map = self.merged_name_map(rhs)?;
12,066✔
350
        Ok(Self {
12,064✔
351
            expr: &self.expr / &rhs.expr,
12,064✔
352
            name_map,
12,064✔
353
        })
12,064✔
354
    }
13,078✔
355

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

365
    /// Apply the sine to this expression; ``sin(self)``.
366
    pub fn sin(&self) -> Self {
362✔
367
        Self {
362✔
368
            expr: self.expr.sin(),
362✔
369
            name_map: self.name_map.clone(),
362✔
370
        }
362✔
371
    }
362✔
372

373
    /// Apply the cosine to this expression; ``cos(self)``.
374
    pub fn cos(&self) -> Self {
352✔
375
        Self {
352✔
376
            expr: self.expr.cos(),
352✔
377
            name_map: self.name_map.clone(),
352✔
378
        }
352✔
379
    }
352✔
380

381
    /// Apply the tangent to this expression; ``tan(self)``.
382
    pub fn tan(&self) -> Self {
352✔
383
        Self {
352✔
384
            expr: self.expr.tan(),
352✔
385
            name_map: self.name_map.clone(),
352✔
386
        }
352✔
387
    }
352✔
388

389
    /// Apply the arcsine to this expression; ``asin(self)``.
390
    pub fn asin(&self) -> Self {
100✔
391
        Self {
100✔
392
            expr: self.expr.asin(),
100✔
393
            name_map: self.name_map.clone(),
100✔
394
        }
100✔
395
    }
100✔
396

397
    /// Apply the arccosine to this expression; ``acos(self)``.
398
    pub fn acos(&self) -> Self {
100✔
399
        Self {
100✔
400
            expr: self.expr.acos(),
100✔
401
            name_map: self.name_map.clone(),
100✔
402
        }
100✔
403
    }
100✔
404

405
    /// Apply the arctangent to this expression; ``atan(self)``.
406
    pub fn atan(&self) -> Self {
100✔
407
        Self {
100✔
408
            expr: self.expr.atan(),
100✔
409
            name_map: self.name_map.clone(),
100✔
410
        }
100✔
411
    }
100✔
412

413
    /// Exponentiate this expression; ``exp(self)``.
414
    pub fn exp(&self) -> Self {
350✔
415
        Self {
350✔
416
            expr: self.expr.exp(),
350✔
417
            name_map: self.name_map.clone(),
350✔
418
        }
350✔
419
    }
350✔
420

421
    /// Take the (natural) logarithm of this expression; ``log(self)``.
422
    pub fn log(&self) -> Self {
266✔
423
        Self {
266✔
424
            expr: self.expr.log(),
266✔
425
            name_map: self.name_map.clone(),
266✔
426
        }
266✔
427
    }
266✔
428

429
    /// Take the absolute value of this expression; ``|self|``.
430
    pub fn abs(&self) -> Self {
374✔
431
        Self {
374✔
432
            expr: self.expr.abs(),
374✔
433
            name_map: self.name_map.clone(),
374✔
434
        }
374✔
435
    }
374✔
436

437
    /// Return the sign of this expression; ``sign(self)``.
438
    pub fn sign(&self) -> Self {
10✔
439
        Self {
10✔
440
            expr: self.expr.sign(),
10✔
441
            name_map: self.name_map.clone(),
10✔
442
        }
10✔
443
    }
10✔
444

445
    /// Complex conjugate the expression.
446
    pub fn conjugate(&self) -> Self {
1,832✔
447
        Self {
1,832✔
448
            expr: self.expr.conjugate(),
1,832✔
449
            name_map: self.name_map.clone(),
1,832✔
450
        }
1,832✔
451
    }
1,832✔
452

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

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

491
        // If we don't allow for unknown parameters, check if there are any.
492
        if !allow_unknown_parameters {
37,154✔
493
            let existing: HashSet<&Symbol> = self.name_map.values().collect();
37,152✔
494
            let to_replace: HashSet<&Symbol> = map.keys().collect();
37,152✔
495
            let mut difference = to_replace.difference(&existing).peekable();
37,152✔
496

497
            if difference.peek().is_some() {
37,152✔
498
                let different_symbols = difference.map(|s| (**s).clone()).collect();
2✔
499
                return Err(ParameterError::UnknownParameters(different_symbols));
2✔
500
            }
37,150✔
501
        }
2✔
502

503
        for (name, symbol) in self.name_map.iter() {
39,894✔
504
            // check if the symbol will get replaced
505
            if let Some(replacement) = map.get(symbol) {
39,894✔
506
                // If yes, update the name_map. This also checks for duplicates.
507
                for (replacement_name, replacement_symbol) in replacement.name_map.iter() {
38,258✔
508
                    if let Some(duplicate) = name_map.get(replacement_name) {
38,258✔
509
                        // If a symbol with the same name already exists, check whether it is
510
                        // the same symbol (fine) or a different symbol with the same name (conflict)!
511
                        if duplicate != replacement_symbol {
32✔
512
                            return Err(ParameterError::NameConflict);
2✔
513
                        }
30✔
514
                    } else {
38,226✔
515
                        // SAFETY: We know the key does not exist yet.
38,226✔
516
                        unsafe {
38,226✔
517
                            name_map.insert_unique_unchecked(
38,226✔
518
                                replacement_name.clone(),
38,226✔
519
                                replacement_symbol.clone(),
38,226✔
520
                            )
38,226✔
521
                        };
38,226✔
522
                    }
38,226✔
523
                }
524

525
                // If we got until here, there were no duplicates, so we are safe to
526
                // add this symbol to the internal replacement map.
527
                symbol_map.insert(symbol.clone(), replacement.expr.clone());
37,152✔
528
            } else {
529
                // no replacement for this symbol, carry on
530
                match name_map.entry(name.clone()) {
2,740✔
531
                    Entry::Occupied(duplicate) => {
22✔
532
                        if duplicate.get() != symbol {
22✔
533
                            return Err(ParameterError::NameConflict);
×
534
                        }
22✔
535
                    }
536
                    Entry::Vacant(e) => {
2,718✔
537
                        e.insert(symbol.clone());
2,718✔
538
                    }
2,718✔
539
                }
540
            }
541
        }
542

543
        let res = self.expr.subs(&symbol_map);
37,150✔
544
        Ok(Self {
37,150✔
545
            expr: res,
37,150✔
546
            name_map,
37,150✔
547
        })
37,150✔
548
    }
37,154✔
549

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

572
        // If we don't allow for unknown parameters, check if there are any.
573
        if !allow_unknown_parameters {
390,222✔
574
            let existing: HashSet<&Symbol> = self.name_map.values().collect();
390,120✔
575
            let mut difference = bind_symbols.difference(&existing).peekable();
390,120✔
576

577
            if difference.peek().is_some() {
390,120✔
578
                let different_symbols = difference.map(|s| (**s).clone()).collect();
×
579
                return Err(ParameterError::UnknownParameters(different_symbols));
×
580
            }
390,120✔
581
        }
102✔
582

583
        // bind the symbol expression and then check the outcome for inf/nan, or numeric values
584
        let bound_expr = self.expr.bind(map);
390,222✔
585
        let bound = match bound_expr.eval(true) {
390,222✔
586
            Some(v) => match &v {
388,982✔
587
                Value::Real(r) => {
321,976✔
588
                    if r.is_infinite() {
321,976✔
589
                        Err(ParameterError::BindingInf)
1,828✔
590
                    } else if r.is_nan() {
320,148✔
591
                        Err(ParameterError::BindingNaN)
×
592
                    } else {
593
                        Ok(SymbolExpr::Value(v))
320,148✔
594
                    }
595
                }
596
                Value::Int(_) => Ok(SymbolExpr::Value(v)),
25,354✔
597
                Value::Complex(c) => {
41,652✔
598
                    if c.re.is_infinite() || c.im.is_infinite() {
41,652✔
599
                        Err(ParameterError::BindingInf)
×
600
                    } else if c.re.is_nan() || c.im.is_nan() {
41,652✔
601
                        Err(ParameterError::BindingNaN)
×
602
                    } else if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON)
41,652✔
603
                        .contains(&c.im)
41,652✔
604
                    {
605
                        Ok(SymbolExpr::Value(Value::Real(c.re)))
1,728✔
606
                    } else {
607
                        Ok(SymbolExpr::Value(v))
39,924✔
608
                    }
609
                }
610
            },
611
            None => Ok(bound_expr),
1,240✔
612
        }?;
1,828✔
613

614
        // update the name map by removing the bound parameters
615
        let bound_name_map: HashMap<String, Symbol> = self
388,394✔
616
            .name_map
388,394✔
617
            .iter()
388,394✔
618
            .filter(|(_, symbol)| !bind_symbols.contains(symbol))
4,687,352✔
619
            .map(|(name, symbol)| (name.clone(), symbol.clone()))
388,394✔
620
            .collect();
388,394✔
621

622
        Ok(Self {
388,394✔
623
            expr: bound,
388,394✔
624
            name_map: bound_name_map,
388,394✔
625
        })
388,394✔
626
    }
390,222✔
627

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

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

671
impl Default for PyParameterExpression {
672
    /// The default constructor returns zero.
673
    fn default() -> Self {
×
674
        Self {
×
675
            inner: ParameterExpression::default(),
×
676
        }
×
677
    }
×
678
}
679

680
impl fmt::Display for PyParameterExpression {
681
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336,786✔
682
        self.inner.fmt(f)
336,786✔
683
    }
336,786✔
684
}
685

686
impl From<ParameterExpression> for PyParameterExpression {
687
    fn from(value: ParameterExpression) -> Self {
13,593,878✔
688
        Self { inner: value }
13,593,878✔
689
    }
13,593,878✔
690
}
691

692
impl PyParameterExpression {
693
    /// Attempt to extract a `PyParameterExpression` from a bound `PyAny`.
694
    ///
695
    /// This will try to coerce to the strictest data type:
696
    /// Int - Real - Complex - PyParameterVectorElement - PyParameter - PyParameterExpression.
697
    ///
698
    /// # Arguments:
699
    ///
700
    /// * ob - The bound `PyAny` to extract from.
701
    ///
702
    /// # Returns
703
    ///
704
    /// * `Ok(Self)` - The extracted expression.
705
    /// * `Err(PyResult)` - An error if extraction to all above types failed.
706
    pub fn extract_coerce(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
5,156,464✔
707
        if let Ok(i) = ob.downcast::<PyInt>() {
5,156,464✔
708
            Ok(ParameterExpression::new(
134,638✔
709
                SymbolExpr::Value(Value::from(i.extract::<i64>()?)),
134,638✔
710
                HashMap::new(),
134,638✔
711
            )
712
            .into())
134,638✔
713
        } else if let Ok(r) = ob.downcast::<PyFloat>() {
5,021,826✔
714
            let r: f64 = r.extract()?;
13,662✔
715
            if r.is_infinite() || r.is_nan() {
13,662✔
716
                return Err(ParameterError::InvalidValue.into());
40✔
717
            }
13,622✔
718
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(r)), HashMap::new()).into())
13,622✔
719
        } else if let Ok(c) = ob.downcast::<PyComplex>() {
5,008,164✔
720
            let c: Complex64 = c.extract()?;
2,361,336✔
721
            if c.is_infinite() || c.is_nan() {
2,361,336✔
722
                return Err(ParameterError::InvalidValue.into());
×
723
            }
2,361,336✔
724
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(c)), HashMap::new()).into())
2,361,336✔
725
        } else if let Ok(element) = ob.downcast::<PyParameterVectorElement>() {
2,646,828✔
726
            Ok(ParameterExpression::from_symbol(element.borrow().symbol.clone()).into())
245,442✔
727
        } else if let Ok(parameter) = ob.downcast::<PyParameter>() {
2,401,386✔
728
            Ok(ParameterExpression::from_symbol(parameter.borrow().symbol.clone()).into())
15,156✔
729
        } else {
730
            ob.extract::<PyParameterExpression>()
2,386,230✔
731
        }
732
    }
5,156,464✔
733

734
    pub fn coerce_into_py(&self, py: Python) -> PyResult<PyObject> {
176,662✔
735
        if let Ok(value) = self.inner.try_to_value(true) {
176,662✔
736
            match value {
14✔
737
                Value::Int(i) => Ok(PyInt::new(py, i).unbind().into_any()),
×
738
                Value::Real(r) => Ok(PyFloat::new(py, r).unbind().into_any()),
14✔
739
                Value::Complex(c) => Ok(PyComplex::from_complex_bound(py, c).unbind().into_any()),
×
740
            }
741
        } else if let Ok(symbol) = self.inner.try_to_symbol() {
176,648✔
742
            if symbol.index.is_some() {
172,378✔
743
                Ok(Py::new(py, PyParameterVectorElement::from_symbol(symbol))?.into_any())
160,728✔
744
            } else {
745
                Ok(Py::new(py, PyParameter::from_symbol(symbol))?.into_any())
11,650✔
746
            }
747
        } else {
748
            self.clone().into_py_any(py)
4,270✔
749
        }
750
    }
176,662✔
751
}
752

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

781
                let replaced_expr = symbol_expr::replace_symbol(&expr, &symbol_map);
70✔
782

783
                let inner = ParameterExpression::new(replaced_expr, symbol_map);
70✔
784
                Ok(Self { inner })
70✔
785
            }
786
            _ => Err(PyValueError::new_err(
×
787
                "Pass either both a name_map and expr, or neither",
×
788
            )),
×
789
        }
790
    }
70✔
791

792
    #[allow(non_snake_case)]
793
    #[staticmethod]
794
    pub fn _Value(value: &Bound<PyAny>) -> PyResult<Self> {
34✔
795
        Self::extract_coerce(value)
34✔
796
    }
34✔
797

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

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

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

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

861
    /// Sine of the expression.
862
    #[inline]
863
    #[pyo3(name = "sin")]
864
    pub fn py_sin(&self) -> Self {
362✔
865
        self.inner.sin().into()
362✔
866
    }
362✔
867

868
    /// Cosine of the expression.
869
    #[inline]
870
    #[pyo3(name = "cos")]
871
    pub fn py_cos(&self) -> Self {
352✔
872
        self.inner.cos().into()
352✔
873
    }
352✔
874

875
    /// Tangent of the expression.
876
    #[inline]
877
    #[pyo3(name = "tan")]
878
    pub fn py_tan(&self) -> Self {
352✔
879
        self.inner.tan().into()
352✔
880
    }
352✔
881

882
    /// Arcsine of the expression.
883
    #[inline]
884
    pub fn arcsin(&self) -> Self {
100✔
885
        self.inner.asin().into()
100✔
886
    }
100✔
887

888
    /// Arccosine of the expression.
889
    #[inline]
890
    pub fn arccos(&self) -> Self {
100✔
891
        self.inner.acos().into()
100✔
892
    }
100✔
893

894
    /// Arctangent of the expression.
895
    #[inline]
896
    pub fn arctan(&self) -> Self {
100✔
897
        self.inner.atan().into()
100✔
898
    }
100✔
899

900
    /// Exponentiate the expression.
901
    #[inline]
902
    #[pyo3(name = "exp")]
903
    pub fn py_exp(&self) -> Self {
350✔
904
        self.inner.exp().into()
350✔
905
    }
350✔
906

907
    /// Take the natural logarithm of the expression.
908
    #[inline]
909
    #[pyo3(name = "log")]
910
    pub fn py_log(&self) -> Self {
266✔
911
        self.inner.log().into()
266✔
912
    }
266✔
913

914
    /// Take the absolute value of the expression.
915
    #[inline]
916
    #[pyo3(name = "abs")]
917
    pub fn py_abs(&self) -> Self {
12✔
918
        self.inner.abs().into()
12✔
919
    }
12✔
920

921
    /// Return the sign of the expression.
922
    #[inline]
923
    #[pyo3(name = "sign")]
924
    pub fn py_sign(&self) -> Self {
10✔
925
        self.inner.sign().into()
10✔
926
    }
10✔
927

928
    /// Return the complex conjugate of the expression.
929
    #[inline]
930
    #[pyo3(name = "conjugate")]
931
    pub fn py_conjugate(&self) -> Self {
1,832✔
932
        self.inner.conjugate().into()
1,832✔
933
    }
1,832✔
934

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

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

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

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

1002
        // apply to the inner expression
1003
        match self.inner.subs(&map, allow_unknown_parameters) {
1,612✔
1004
            Ok(subbed) => Ok(subbed.into()),
1,608✔
1005
            Err(e) => Err(e.into()),
4✔
1006
        }
1007
    }
1,612✔
1008

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

1044
        // apply to the inner expression
1045
        match self.inner.bind(&map, allow_unknown_parameters) {
313,716✔
1046
            Ok(bound) => Ok(bound.into()),
311,888✔
1047
            Err(e) => Err(e.into()),
1,828✔
1048
        }
1049
    }
313,716✔
1050

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

1075
    #[inline]
1076
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1077
        // ParameterExpression is immutable.
1078
        slf
×
1079
    }
×
1080

1081
    #[inline]
1082
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
242✔
1083
        // Everything a ParameterExpression contains is immutable.
1084
        slf
242✔
1085
    }
242✔
1086

1087
    pub fn __eq__(&self, rhs: &Bound<PyAny>) -> PyResult<bool> {
228,924✔
1088
        if let Ok(rhs) = Self::extract_coerce(rhs) {
228,924✔
1089
            match rhs.inner.expr {
228,912✔
1090
                SymbolExpr::Value(v) => match self.inner.try_to_value(false) {
2,844✔
1091
                    Ok(e) => Ok(e == v),
2,644✔
1092
                    Err(_) => Ok(false),
200✔
1093
                },
1094
                _ => Ok(self.inner.expr == rhs.inner.expr),
226,068✔
1095
            }
1096
        } else {
1097
            Ok(false)
12✔
1098
        }
1099
    }
228,924✔
1100

1101
    #[inline]
1102
    pub fn __abs__(&self) -> Self {
362✔
1103
        self.inner.abs().into()
362✔
1104
    }
362✔
1105

1106
    #[inline]
1107
    pub fn __pos__(&self) -> Self {
4✔
1108
        self.clone()
4✔
1109
    }
4✔
1110

1111
    pub fn __neg__(&self) -> Self {
772✔
1112
        Self {
772✔
1113
            inner: ParameterExpression::new(-&self.inner.expr, self.inner.name_map.clone()),
772✔
1114
        }
772✔
1115
    }
772✔
1116

1117
    pub fn __add__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
2,318,400✔
1118
        if let Ok(rhs) = Self::extract_coerce(rhs) {
2,318,400✔
1119
            Ok(self.inner.add(&rhs.inner)?.into())
2,318,388✔
1120
        } else {
1121
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1122
                "Unsupported data type for __add__",
12✔
1123
            ))
12✔
1124
        }
1125
    }
2,318,400✔
1126

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

1137
    pub fn __sub__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
18,516✔
1138
        if let Ok(rhs) = Self::extract_coerce(rhs) {
18,516✔
1139
            Ok(self.inner.sub(&rhs.inner)?.into())
18,504✔
1140
        } else {
1141
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1142
                "Unsupported data type for __sub__",
12✔
1143
            ))
12✔
1144
        }
1145
    }
18,516✔
1146

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

1157
    pub fn __mul__<'py>(&self, rhs: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
2,408,112✔
1158
        let py = rhs.py();
2,408,112✔
1159
        if let Ok(rhs) = Self::extract_coerce(rhs) {
2,408,112✔
1160
            match self.inner.mul(&rhs.inner) {
2,400,756✔
1161
                Ok(result) => PyParameterExpression::from(result).into_bound_py_any(py),
2,400,754✔
1162
                Err(e) => Err(PyErr::from(e)),
2✔
1163
            }
1164
        } else {
1165
            PyNotImplemented::get(py).into_bound_py_any(py)
7,356✔
1166
        }
1167
    }
2,408,112✔
1168

1169
    pub fn __rmul__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
12,084✔
1170
        if let Ok(lhs) = Self::extract_coerce(lhs) {
12,084✔
1171
            Ok(lhs.inner.mul(&self.inner)?.into())
12,072✔
1172
        } else {
1173
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1174
                "Unsupported data type for __rmul__",
12✔
1175
            ))
12✔
1176
        }
1177
    }
12,084✔
1178

1179
    pub fn __truediv__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
9,032✔
1180
        if let Ok(rhs) = Self::extract_coerce(rhs) {
9,032✔
1181
            Ok(self.inner.div(&rhs.inner)?.into())
9,020✔
1182
        } else {
1183
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1184
                "Unsupported data type for __truediv__",
12✔
1185
            ))
12✔
1186
        }
1187
    }
9,032✔
1188

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

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

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

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

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

1248
    pub fn __complex__(&self) -> PyResult<Complex64> {
77,856✔
1249
        match self.inner.try_to_value(false)? {
77,856✔
1250
            Value::Complex(c) => Ok(c),
25,460✔
1251
            Value::Real(r) => Ok(Complex64::new(r, 0.)),
39,200✔
1252
            Value::Int(i) => Ok(Complex64::new(i as f64, 0.)),
13,194✔
1253
        }
1254
    }
77,856✔
1255

1256
    pub fn __str__(&self) -> String {
336,786✔
1257
        self.to_string()
336,786✔
1258
    }
336,786✔
1259

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

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

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

1317
    #[getter]
1318
    fn _qpy_replay(&self) -> PyResult<Vec<OPReplay>> {
3,634✔
1319
        let mut replay = Vec::new();
3,634✔
1320
        qpy_replay(&self.inner, &self.inner.name_map, &mut replay);
3,634✔
1321
        Ok(replay)
3,634✔
1322
    }
3,634✔
1323
}
1324

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

1364
impl Hash for PyParameter {
1365
    fn hash<H: Hasher>(&self, state: &mut H) {
4,750,980✔
1366
        self.symbol.hash(state);
4,750,980✔
1367
    }
4,750,980✔
1368
}
1369

1370
// This needs to be implemented manually, since PyO3 does not provide this conversion
1371
// for subclasses.
1372
impl<'py> IntoPyObject<'py> for PyParameter {
1373
    type Target = PyParameter;
1374
    type Output = Bound<'py, Self::Target>;
1375
    type Error = PyErr;
1376

1377
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
4,116✔
1378
        let symbol = &self.symbol;
4,116✔
1379
        let symbol_expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
4,116✔
1380
        let expr = ParameterExpression::from_symbol_expr(symbol_expr);
4,116✔
1381
        let py_expr = PyParameterExpression::from(expr);
4,116✔
1382

1383
        Ok(Py::new(py, (self, py_expr))?.into_bound(py))
4,116✔
1384
    }
4,116✔
1385
}
1386

1387
impl PyParameter {
1388
    /// Get a Python class initialization from a symbol.
1389
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
5,224,766✔
1390
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
5,224,766✔
1391

1392
        let py_parameter = Self { symbol };
5,224,766✔
1393
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
5,224,766✔
1394

1395
        PyClassInitializer::from(py_expr).add_subclass(py_parameter)
5,224,766✔
1396
    }
5,224,766✔
1397

1398
    /// Get a reference to the underlying symbol.
1399
    pub fn symbol(&self) -> &Symbol {
4,750,532✔
1400
        &self.symbol
4,750,532✔
1401
    }
4,750,532✔
1402
}
1403

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

1426
        let py_parameter = Self { symbol };
79,220✔
1427
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
79,220✔
1428

1429
        Ok(PyClassInitializer::from(py_expr).add_subclass(py_parameter))
79,220✔
1430
    }
79,220✔
1431

1432
    /// Returns the name of the :class:`.Parameter`.
1433
    #[getter]
1434
    fn name<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
21,334✔
1435
        PyString::new(py, &self.symbol.repr(false))
21,334✔
1436
    }
21,334✔
1437

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

1448
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
68✔
1449
        let str = format!("Parameter({})", self.symbol.repr(false),);
68✔
1450
        PyString::new(py, str.as_str())
68✔
1451
    }
68✔
1452

1453
    pub fn __getnewargs__(&self) -> (String, u128) {
13,824✔
1454
        (self.symbol.repr(false), self.symbol.uuid.as_u128())
13,824✔
1455
    }
13,824✔
1456

1457
    pub fn __getstate__(&self) -> (String, u128) {
13,824✔
1458
        (self.symbol.repr(false), self.symbol.uuid.as_u128())
13,824✔
1459
    }
13,824✔
1460

1461
    pub fn __setstate__(&mut self, state: (String, u128)) {
4✔
1462
        let name = state.0.as_str();
4✔
1463
        let uuid = Uuid::from_u128(state.1);
4✔
1464
        let symbol = Symbol::new(name, Some(uuid), None);
4✔
1465
        self.symbol = symbol;
4✔
1466
    }
4✔
1467

1468
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1469
        // Parameter is immutable. Note that this **cannot** be deferred to the parent class
1470
        // since PyO3 would then always return the parent type.
1471
        slf
×
1472
    }
×
1473

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

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

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

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

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

1582
impl<'py> IntoPyObject<'py> for PyParameterVectorElement {
1583
    type Target = PyParameterVectorElement;
1584
    type Output = Bound<'py, Self::Target>;
1585
    type Error = PyErr;
1586

1587
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
176✔
1588
        let symbol = &self.symbol;
176✔
1589
        let py_param = PyParameter::from_symbol(symbol.clone());
176✔
1590
        let py_element = py_param.add_subclass(self);
176✔
1591

1592
        Ok(Py::new(py, py_element)?.into_bound(py))
176✔
1593
    }
176✔
1594
}
1595

1596
impl PyParameterVectorElement {
1597
    pub fn symbol(&self) -> &Symbol {
16,460✔
1598
        &self.symbol
16,460✔
1599
    }
16,460✔
1600

1601
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
5,101,200✔
1602
        let py_element = Self {
5,101,200✔
1603
            symbol: symbol.clone(),
5,101,200✔
1604
        };
5,101,200✔
1605
        let py_parameter = PyParameter::from_symbol(symbol);
5,101,200✔
1606

1607
        py_parameter.add_subclass(py_element)
5,101,200✔
1608
    }
5,101,200✔
1609
}
1610

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

1624
        let symbol = Symbol::py_new(
38,088✔
1625
            &vector_name,
38,088✔
1626
            Some(uuid.as_u128()),
38,088✔
1627
            Some(index),
38,088✔
1628
            Some(vector.clone_ref(py)),
38,088✔
1629
        )?;
×
1630

1631
        let py_parameter = PyParameter::from_symbol(symbol.clone());
38,088✔
1632
        let py_element = Self { symbol };
38,088✔
1633

1634
        Ok(py_parameter.add_subclass(py_element))
38,088✔
1635
    }
38,088✔
1636

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

1651
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
48✔
1652
        let str = format!("ParameterVectorElement({})", self.symbol.repr(false),);
48✔
1653
        PyString::new(py, str.as_str())
48✔
1654
    }
48✔
1655

1656
    pub fn __getstate__(&self, py: Python) -> PyResult<(PyObject, u32, Option<PyObject>)> {
8✔
1657
        self.__getnewargs__(py)
8✔
1658
    }
8✔
1659

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

1673
    /// Get the index of this element in the parent vector.
1674
    #[getter]
1675
    pub fn index(&self) -> u32 {
336✔
1676
        self.symbol
336✔
1677
            .index
336✔
1678
            .expect("A vector element should have an index")
336✔
1679
    }
336✔
1680

1681
    /// Get the parent vector instance.
1682
    #[getter]
1683
    pub fn vector(&self) -> PyObject {
668✔
1684
        self.symbol
668✔
1685
            .clone()
668✔
1686
            .vector
668✔
1687
            .expect("A vector element should have a vector")
668✔
1688
    }
668✔
1689

1690
    /// For backward compatibility only. This should not be used and we ought to update those
1691
    /// usages!
1692
    #[getter]
1693
    pub fn _vector(&self) -> PyObject {
×
1694
        self.vector()
×
1695
    }
×
1696

1697
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1698
        // ParameterVectorElement is immutable.
1699
        slf
×
1700
    }
×
1701

1702
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
8✔
1703
        // Everything a ParameterVectorElement contains is immutable.
1704
        slf
8✔
1705
    }
8✔
1706
}
1707

1708
/// Try to extract a Uuid from a Python object, which could be a Python UUID or int.
1709
fn uuid_from_py(py: Python<'_>, uuid: Option<PyObject>) -> PyResult<Option<Uuid>> {
117,308✔
1710
    if let Some(val) = uuid {
117,308✔
1711
        // construct from u128
1712
        let as_u128 = if let Ok(as_u128) = val.extract::<u128>(py) {
38,258✔
1713
            as_u128
4✔
1714
        // construct from Python UUID type
1715
        } else if val.bind(py).is_exact_instance(UUID.get_bound(py)) {
38,254✔
1716
            val.getattr(py, "int")?.extract::<u128>(py)?
38,254✔
1717
        // invalid format
1718
        } else {
1719
            return Err(PyTypeError::new_err("not a UUID!"));
×
1720
        };
1721
        Ok(Some(Uuid::from_u128(as_u128)))
38,258✔
1722
    } else {
1723
        Ok(None)
79,050✔
1724
    }
1725
}
117,308✔
1726

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

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

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

1757
impl ParameterValueType {
1758
    fn extract_from_expr(expr: &SymbolExpr) -> Option<ParameterValueType> {
9,862✔
1759
        if let Some(value) = expr.eval(true) {
9,862✔
1760
            match value {
3,564✔
1761
                Value::Int(i) => Some(ParameterValueType::Int(i)),
2,900✔
1762
                Value::Real(r) => Some(ParameterValueType::Float(r)),
656✔
1763
                Value::Complex(c) => Some(ParameterValueType::Complex(c)),
8✔
1764
            }
1765
        } else if let SymbolExpr::Symbol(symbol) = expr {
6,298✔
1766
            match symbol.index {
4,290✔
1767
                None => {
1768
                    let param = PyParameter {
4,114✔
1769
                        symbol: symbol.as_ref().clone(),
4,114✔
1770
                    };
4,114✔
1771
                    Some(ParameterValueType::Parameter(param))
4,114✔
1772
                }
1773
                Some(_) => {
1774
                    let param = PyParameterVectorElement {
176✔
1775
                        symbol: symbol.as_ref().clone(),
176✔
1776
                    };
176✔
1777
                    Some(ParameterValueType::VectorElement(param))
176✔
1778
                }
1779
            }
1780
        } else {
1781
            // ParameterExpressions have the value None, as they must be constructed
1782
            None
2,008✔
1783
        }
1784
    }
9,862✔
1785
}
1786

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

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

1841
impl From<OpCode> for u8 {
1842
    fn from(value: OpCode) -> Self {
×
1843
        value as u8
×
1844
    }
×
1845
}
1846

1847
unsafe impl ::bytemuck::CheckedBitPattern for OpCode {
1848
    type Bits = u8;
1849

1850
    #[inline(always)]
1851
    fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
×
1852
        *bits <= 20
×
1853
    }
×
1854
}
1855

1856
unsafe impl ::bytemuck::NoUninit for OpCode {}
1857

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

1867
    fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
560✔
1868
        if let Ok(code) = other.downcast::<OpCode>() {
560✔
1869
            *code.borrow() == *self
560✔
1870
        } else {
1871
            false
×
1872
        }
1873
    }
560✔
1874

1875
    fn __hash__(&self) -> u8 {
2,088✔
1876
        *self as u8
2,088✔
1877
    }
2,088✔
1878

1879
    fn __getnewargs__(&self) -> (u8,) {
5,012✔
1880
        (*self as u8,)
5,012✔
1881
    }
5,012✔
1882
}
1883

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

1893
#[pymethods]
×
1894
impl OPReplay {
1895
    #[new]
1896
    pub fn py_new(
×
1897
        op: OpCode,
×
1898
        lhs: Option<ParameterValueType>,
×
1899
        rhs: Option<ParameterValueType>,
×
1900
    ) -> OPReplay {
×
1901
        OPReplay { op, lhs, rhs }
×
1902
    }
×
1903

1904
    #[getter]
1905
    fn op(&self) -> OpCode {
1,194✔
1906
        self.op
1,194✔
1907
    }
1,194✔
1908

1909
    #[getter]
1910
    fn lhs(&self) -> Option<ParameterValueType> {
630✔
1911
        self.lhs.clone()
630✔
1912
    }
630✔
1913

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

1919
    fn __getnewargs__(
5,012✔
1920
        &self,
5,012✔
1921
    ) -> (
5,012✔
1922
        OpCode,
5,012✔
1923
        Option<ParameterValueType>,
5,012✔
1924
        Option<ParameterValueType>,
5,012✔
1925
    ) {
5,012✔
1926
        (self.op, self.lhs.clone(), self.rhs.clone())
5,012✔
1927
    }
5,012✔
1928
}
1929

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

1949
    ParameterExpression {
9,862✔
1950
        expr: sub_expr.clone(),
9,862✔
1951
        name_map: restricted_name_map,
9,862✔
1952
    }
9,862✔
1953
}
9,862✔
1954

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

1982
            // recurse on the instruction
1983
            qpy_replay(&lhs, name_map, replay);
1,422✔
1984

1985
            let lhs_value = ParameterValueType::extract_from_expr(expr);
1,422✔
1986

1987
            // MUL is special: we implement ``neg`` as multiplication by -1
1988
            if let OpCode::MUL = &op {
1,422✔
1989
                replay.push(OPReplay {
1,346✔
1990
                    op,
1,346✔
1991
                    lhs: lhs_value,
1,346✔
1992
                    rhs: Some(ParameterValueType::Int(-1)),
1,346✔
1993
                });
1,346✔
1994
            } else {
1,346✔
1995
                replay.push(OPReplay {
76✔
1996
                    op,
76✔
1997
                    lhs: lhs_value,
76✔
1998
                    rhs: None,
76✔
1999
                });
76✔
2000
            }
76✔
2001
        }
2002
        SymbolExpr::Binary { op, lhs, rhs } => {
4,220✔
2003
            let lhs_value = ParameterValueType::extract_from_expr(lhs);
4,220✔
2004
            let rhs_value = ParameterValueType::extract_from_expr(rhs);
4,220✔
2005

2006
            // recurse on the parameter expressions
2007
            let lhs = filter_name_map(lhs, name_map);
4,220✔
2008
            let rhs = filter_name_map(rhs, name_map);
4,220✔
2009
            qpy_replay(&lhs, name_map, replay);
4,220✔
2010
            qpy_replay(&rhs, name_map, replay);
4,220✔
2011

2012
            // add the expression to the replay
2013
            match lhs_value {
2,394✔
2014
                None
2015
                | Some(ParameterValueType::Parameter(_))
2016
                | Some(ParameterValueType::VectorElement(_)) => {
2017
                    let op = match op {
3,502✔
2018
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
284✔
2019
                        symbol_expr::BinaryOp::Sub => OpCode::SUB,
384✔
2020
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
86✔
2021
                        symbol_expr::BinaryOp::Div => OpCode::DIV,
2,728✔
2022
                        symbol_expr::BinaryOp::Pow => OpCode::POW,
20✔
2023
                    };
2024
                    replay.push(OPReplay {
3,502✔
2025
                        op,
3,502✔
2026
                        lhs: lhs_value,
3,502✔
2027
                        rhs: rhs_value,
3,502✔
2028
                    });
3,502✔
2029
                }
2030
                _ => {
2031
                    let op = match op {
718✔
2032
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
326✔
2033
                        symbol_expr::BinaryOp::Sub => OpCode::RSUB,
76✔
2034
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
308✔
2035
                        symbol_expr::BinaryOp::Div => OpCode::RDIV,
4✔
2036
                        symbol_expr::BinaryOp::Pow => OpCode::RPOW,
4✔
2037
                    };
2038
                    if let OpCode::ADD | OpCode::MUL = op {
718✔
2039
                        replay.push(OPReplay {
634✔
2040
                            op,
634✔
2041
                            lhs: lhs_value,
634✔
2042
                            rhs: rhs_value,
634✔
2043
                        });
634✔
2044
                    } else {
634✔
2045
                        // this covers RSUB, RDIV, RPOW, hence we swap lhs and rhs
84✔
2046
                        replay.push(OPReplay {
84✔
2047
                            op,
84✔
2048
                            lhs: rhs_value,
84✔
2049
                            rhs: lhs_value,
84✔
2050
                        });
84✔
2051
                    }
84✔
2052
                }
2053
            }
2054
        }
2055
    }
2056
}
13,496✔
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

© 2025 Coveralls, Inc