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

Qiskit / qiskit / 16830026850

08 Aug 2025 12:08PM UTC coverage: 87.665%. Remained the same
16830026850

push

github

web-flow
Fix clippy issues in new MCX synthesis plugin (#14843)

In the recently merged #14666 two small clippy issues with latest stable
slipped into the PR. We run clippy with our MSRV in CI for a stable CI
as newer versions of Rust/clippy introduce new rules and/or
change/improve existing rules so a new rust release would break CI which
is disruptive. However, you can still use newer clippy for local
development and when you do the issues it finds highlight real things
that need to be fixed in the code. This commit fixes the two things that
clippy flags in the mcx plugin code, one is just a code style change but
the other is a small docs formatting issue which would have resulted in
weird formatting for the rendered rustdoc.

1 of 1 new or added line in 1 file covered. (100.0%)

19 existing lines in 5 files now uncovered.

82212 of 93780 relevant lines covered (87.66%)

547397.33 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,828✔
104
        self.expr.eq(&other.expr)
17,828✔
105
    }
17,828✔
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> {
170,118✔
146
        let expr = PyParameterExpression::from(self.clone());
170,118✔
147
        expr.into_pyobject(py)
170,118✔
148
    }
170,118✔
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,436✔
170
        Self { expr, name_map }
2,510,436✔
171
    }
2,510,436✔
172

173
    /// Construct from a [Symbol].
174
    pub fn from_symbol(symbol: Symbol) -> Self {
242,318✔
175
        Self {
242,318✔
176
            expr: SymbolExpr::Symbol(Arc::new(symbol.clone())),
242,318✔
177
            name_map: [(symbol.repr(false), symbol)].into(),
242,318✔
178
        }
242,318✔
179
    }
242,318✔
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> {
218,272✔
185
        if let SymbolExpr::Symbol(symbol) = &self.expr {
218,272✔
186
            Ok(symbol.as_ref().clone())
214,002✔
187
        } else {
188
            Err(ParameterError::NotASymbol)
4,270✔
189
        }
190
    }
218,272✔
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,219,750✔
203
        if strict && !self.name_map.is_empty() {
10,219,750✔
204
            let free_symbols = self.expr.iter_symbols().cloned().collect();
196,626✔
205
            return Err(ParameterError::UnboundParameters(free_symbols));
196,626✔
206
        }
10,023,124✔
207

208
        match self.expr.eval(true) {
10,023,124✔
209
            Some(value) => {
371,634✔
210
                // we try to restrict complex to real, if possible
211
                if let Value::Complex(c) = value {
371,634✔
212
                    if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON).contains(&c.im)
41,698✔
213
                    {
214
                        return Ok(Value::Real(c.re));
×
215
                    }
41,698✔
216
                }
329,936✔
217
                Ok(value)
371,634✔
218
            }
219
            None => {
220
                let free_symbols = self.expr.iter_symbols().cloned().collect();
9,651,490✔
221
                Err(ParameterError::UnboundParameters(free_symbols))
9,651,490✔
222
            }
223
        }
224
    }
10,219,750✔
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,250,652✔
230
        let name_map = expr.name_map();
5,250,652✔
231
        Self { expr, name_map }
5,250,652✔
232
    }
5,250,652✔
233

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

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

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

316
    /// Add an expression; ``self + rhs``.
317
    pub fn add(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,389,594✔
318
        let name_map = self.merged_name_map(rhs)?;
2,389,594✔
319
        Ok(Self {
2,389,592✔
320
            expr: &self.expr + &rhs.expr,
2,389,592✔
321
            name_map,
2,389,592✔
322
        })
2,389,592✔
323
    }
2,389,594✔
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(
35,768✔
483
        &self,
35,768✔
484
        map: &HashMap<Symbol, Self>,
35,768✔
485
        allow_unknown_parameters: bool,
35,768✔
486
    ) -> Result<Self, ParameterError> {
35,768✔
487
        // Build the outgoing name map. In the process we check for any duplicates.
488
        let mut name_map: HashMap<String, Symbol> = HashMap::new();
35,768✔
489
        let mut symbol_map: HashMap<Symbol, SymbolExpr> = HashMap::new();
35,768✔
490

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

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

503
        for (name, symbol) in self.name_map.iter() {
38,508✔
504
            // check if the symbol will get replaced
505
            if let Some(replacement) = map.get(symbol) {
38,508✔
506
                // If yes, update the name_map. This also checks for duplicates.
507
                for (replacement_name, replacement_symbol) in replacement.name_map.iter() {
36,872✔
508
                    if let Some(duplicate) = name_map.get(replacement_name) {
36,872✔
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 {
26✔
512
                            return Err(ParameterError::NameConflict);
2✔
513
                        }
24✔
514
                    } else {
36,846✔
515
                        // SAFETY: We know the key does not exist yet.
36,846✔
516
                        unsafe {
36,846✔
517
                            name_map.insert_unique_unchecked(
36,846✔
518
                                replacement_name.clone(),
36,846✔
519
                                replacement_symbol.clone(),
36,846✔
520
                            )
36,846✔
521
                        };
36,846✔
522
                    }
36,846✔
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());
35,766✔
528
            } else {
529
                // no replacement for this symbol, carry on
530
                match name_map.entry(name.clone()) {
2,740✔
531
                    Entry::Occupied(duplicate) => {
28✔
532
                        if duplicate.get() != symbol {
28✔
UNCOV
533
                            return Err(ParameterError::NameConflict);
×
534
                        }
28✔
535
                    }
536
                    Entry::Vacant(e) => {
2,712✔
537
                        e.insert(symbol.clone());
2,712✔
538
                    }
2,712✔
539
                }
540
            }
541
        }
542

543
        let res = self.expr.subs(&symbol_map);
35,764✔
544
        Ok(Self {
35,764✔
545
            expr: res,
35,764✔
546
            name_map,
35,764✔
547
        })
35,764✔
548
    }
35,768✔
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(
371,546✔
565
        &self,
371,546✔
566
        map: &HashMap<&Symbol, Value>,
371,546✔
567
        allow_unknown_parameters: bool,
371,546✔
568
    ) -> Result<Self, ParameterError> {
371,546✔
569
        // The set of symbols we will bind. Used twice, hence pre-computed here.
570
        let bind_symbols: HashSet<&Symbol> = map.keys().cloned().collect();
371,546✔
571

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

577
            if difference.peek().is_some() {
371,444✔
578
                let different_symbols = difference.map(|s| (**s).clone()).collect();
×
579
                return Err(ParameterError::UnknownParameters(different_symbols));
×
580
            }
371,444✔
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);
371,546✔
585
        let bound = match bound_expr.eval(true) {
371,546✔
586
            Some(v) => match &v {
370,306✔
587
                Value::Real(r) => {
299,518✔
588
                    if r.is_infinite() {
299,518✔
589
                        Err(ParameterError::BindingInf)
1,828✔
590
                    } else if r.is_nan() {
297,690✔
591
                        Err(ParameterError::BindingNaN)
×
592
                    } else {
593
                        Ok(SymbolExpr::Value(v))
297,690✔
594
                    }
595
                }
596
                Value::Int(_) => Ok(SymbolExpr::Value(v)),
27,560✔
597
                Value::Complex(c) => {
43,228✔
598
                    if c.re.is_infinite() || c.im.is_infinite() {
43,228✔
599
                        Err(ParameterError::BindingInf)
×
600
                    } else if c.re.is_nan() || c.im.is_nan() {
43,228✔
601
                        Err(ParameterError::BindingNaN)
×
602
                    } else if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON)
43,228✔
603
                        .contains(&c.im)
43,228✔
604
                    {
605
                        Ok(SymbolExpr::Value(Value::Real(c.re)))
1,728✔
606
                    } else {
607
                        Ok(SymbolExpr::Value(v))
41,500✔
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
369,718✔
616
            .name_map
369,718✔
617
            .iter()
369,718✔
618
            .filter(|(_, symbol)| !bind_symbols.contains(symbol))
4,668,676✔
619
            .map(|(name, symbol)| (name.clone(), symbol.clone()))
369,718✔
620
            .collect();
369,718✔
621

622
        Ok(Self {
369,718✔
623
            expr: bound,
369,718✔
624
            name_map: bound_name_map,
369,718✔
625
        })
369,718✔
626
    }
371,546✔
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,316✔
639
        let mut merged = self.name_map.clone();
4,841,316✔
640
        for (name, param) in other.name_map.iter() {
7,049,648✔
641
            match merged.get(name) {
7,049,648✔
642
                Some(existing_param) => {
3,990,368✔
643
                    if param != existing_param {
3,990,368✔
644
                        return Err(ParameterError::NameConflict);
8✔
645
                    }
3,990,360✔
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,308✔
654
    }
4,841,316✔
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,465,448✔
688
        Self { inner: value }
13,465,448✔
689
    }
13,465,448✔
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,138,182✔
707
        if let Ok(i) = ob.downcast::<PyInt>() {
5,138,182✔
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,003,544✔
714
            let r: f64 = r.extract()?;
13,660✔
715
            if r.is_infinite() || r.is_nan() {
13,660✔
716
                return Err(ParameterError::InvalidValue.into());
40✔
717
            }
13,620✔
718
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(r)), HashMap::new()).into())
13,620✔
719
        } else if let Ok(c) = ob.downcast::<PyComplex>() {
4,989,884✔
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,628,548✔
726
            Ok(ParameterExpression::from_symbol(element.borrow().symbol.clone()).into())
227,524✔
727
        } else if let Ok(parameter) = ob.downcast::<PyParameter>() {
2,401,024✔
728
            Ok(ParameterExpression::from_symbol(parameter.borrow().symbol.clone()).into())
14,794✔
729
        } else {
730
            ob.extract::<PyParameterExpression>()
2,386,230✔
731
        }
732
    }
5,138,182✔
733

734
    pub fn coerce_into_py(&self, py: Python) -> PyResult<PyObject> {
159,804✔
735
        if let Ok(value) = self.inner.try_to_value(true) {
159,804✔
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() {
159,790✔
742
            if symbol.index.is_some() {
155,520✔
743
                Ok(Py::new(py, PyParameterVectorElement::from_symbol(symbol))?.into_any())
143,870✔
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
    }
159,804✔
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> {
216,458✔
815
        match self.inner.try_to_value(strict)? {
216,458✔
816
            Value::Real(r) => r.into_py_any(py),
189,830✔
817
            Value::Int(i) => i.into_py_any(py),
12,022✔
818
            Value::Complex(c) => c.into_py_any(py),
14,488✔
819
        }
820
    }
216,458✔
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>> {
306,560✔
841
        let py_parameters: Vec<PyObject> = self
306,560✔
842
            .inner
306,560✔
843
            .name_map
306,560✔
844
            .values()
306,560✔
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<_>>()?;
306,560✔
858
        PySet::new(py, py_parameters)
306,560✔
859
    }
306,560✔
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(
295,798✔
1031
        &self,
295,798✔
1032
        parameter_values: HashMap<PyParameter, Bound<PyAny>>,
295,798✔
1033
        allow_unknown_parameters: bool,
295,798✔
1034
    ) -> PyResult<Self> {
295,798✔
1035
        // reduce the map to a HashMap<Symbol, Value>
1036
        let map = parameter_values
295,798✔
1037
            .iter()
295,798✔
1038
            .map(|(param, value)| {
4,730,320✔
1039
                let value = value.extract()?;
4,730,320✔
1040
                Ok((param.symbol(), value))
4,730,320✔
1041
            })
4,730,320✔
1042
            .collect::<PyResult<_>>()?;
295,798✔
1043

1044
        // apply to the inner expression
1045
        match self.inner.bind(&map, allow_unknown_parameters) {
295,798✔
1046
            Ok(bound) => Ok(bound.into()),
293,970✔
1047
            Err(e) => Err(e.into()),
1,828✔
1048
        }
1049
    }
295,798✔
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> {
211,006✔
1088
        if let Ok(rhs) = Self::extract_coerce(rhs) {
211,006✔
1089
            match rhs.inner.expr {
210,994✔
1090
                SymbolExpr::Value(v) => match self.inner.try_to_value(false) {
2,712✔
1091
                    Ok(e) => Ok(e == v),
2,512✔
1092
                    Err(_) => Ok(false),
200✔
1093
                },
1094
                _ => Ok(self.inner.expr == rhs.inner.expr),
208,282✔
1095
            }
1096
        } else {
1097
            Ok(false)
12✔
1098
        }
1099
    }
211,006✔
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),
27,036✔
1251
            Value::Real(r) => Ok(Complex64::new(r, 0.)),
35,392✔
1252
            Value::Int(i) => Ok(Complex64::new(i as f64, 0.)),
15,426✔
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,650,724✔
1261
        match self.inner.try_to_value(false) {
9,650,724✔
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,650,722✔
1273
                self.inner.expr.string_id().hash(&mut hasher);
9,650,722✔
1274
                Ok(hasher.finish())
9,650,722✔
1275
            }
1276
        }
1277
    }
9,650,724✔
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,733,058✔
1366
        self.symbol.hash(state);
4,733,058✔
1367
    }
4,733,058✔
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,171,670✔
1390
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
5,171,670✔
1391

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

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

1398
    /// Get a reference to the underlying symbol.
1399
    pub fn symbol(&self) -> &Symbol {
4,732,614✔
1400
        &self.symbol
4,732,614✔
1401
    }
4,732,614✔
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(
74,862✔
1418
        py: Python<'_>,
74,862✔
1419
        name: String,
74,862✔
1420
        uuid: Option<PyObject>,
74,862✔
1421
    ) -> PyResult<PyClassInitializer<Self>> {
74,862✔
1422
        let uuid = uuid_from_py(py, uuid)?;
74,862✔
1423
        let symbol = Symbol::new(name.as_str(), uuid, None);
74,862✔
1424
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
74,862✔
1425

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

1429
        Ok(PyClassInitializer::from(py_expr).add_subclass(py_parameter))
74,862✔
1430
    }
74,862✔
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>(
144✔
1517
        &self,
144✔
1518
        py: Python<'py>,
144✔
1519
        parameter_values: HashMap<PyParameter, Bound<'py, PyAny>>,
144✔
1520
        allow_unknown_parameters: bool,
144✔
1521
    ) -> PyResult<Bound<'py, PyAny>> {
144✔
1522
        // Returns PyAny to cover Parameter and ParameterExpression(value).
1523
        match parameter_values.get(self) {
144✔
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) => {
144✔
1534
                if allow_unknown_parameters || parameter_values.len() == 1 {
144✔
1535
                    let expr = PyParameterExpression::extract_coerce(replacement)?;
144✔
1536
                    if let SymbolExpr::Value(_) = &expr.inner.expr {
144✔
1537
                        expr.clone().into_bound_py_any(py)
144✔
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
    }
144✔
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,048,506✔
1602
        let py_element = Self {
5,048,506✔
1603
            symbol: symbol.clone(),
5,048,506✔
1604
        };
5,048,506✔
1605
        let py_parameter = PyParameter::from_symbol(symbol);
5,048,506✔
1606

1607
        py_parameter.add_subclass(py_element)
5,048,506✔
1608
    }
5,048,506✔
1609
}
1610

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

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

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

1634
        Ok(py_parameter.add_subclass(py_element))
37,686✔
1635
    }
37,686✔
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>> {
112,548✔
1710
    if let Some(val) = uuid {
112,548✔
1711
        // construct from u128
1712
        let as_u128 = if let Ok(as_u128) = val.extract::<u128>(py) {
37,856✔
1713
            as_u128
4✔
1714
        // construct from Python UUID type
1715
        } else if val.bind(py).is_exact_instance(UUID.get_bound(py)) {
37,852✔
1716
            val.getattr(py, "int")?.extract::<u128>(py)?
37,852✔
1717
        // invalid format
1718
        } else {
1719
            return Err(PyTypeError::new_err("not a UUID!"));
×
1720
        };
1721
        Ok(Some(Uuid::from_u128(as_u128)))
37,856✔
1722
    } else {
1723
        Ok(None)
74,692✔
1724
    }
1725
}
112,548✔
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

© 2026 Coveralls, Inc