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

Qiskit / qiskit / 16654959753

31 Jul 2025 04:47PM UTC coverage: 87.618% (-0.2%) from 87.781%
16654959753

push

github

web-flow
[Alternative] Move Parameter to Rust  (#14757)

* Split py and py-free methods

* playing with Symbol as Parameter

-- 606 failing tests

* fix errors

* about to start the mess

* suite passing

* PVE in Rust

* start assign

* add assign

* dealing with unknown params and conflicts

* an attempt at vector fixing

* fix more tests

- name conflicts
- hash(2.3) == hash(ParamExpr(2.3))
- some error messages

* implement name_map as track record

This allows keeping track of which parameters are still in the expression, even though the expression is optimized. E.g. x*0 = 0, but x is still valid to bind.

* The string debacle

- don't use UUID in to_string, only in string_id (using strings for comparisons should probably be avoided in future)
- clippy
- relax Python is-checks to eq-checks

* some housekeeping

* implement _qpy_replay

* implement sympify

* fix copy/deepcopy

* Implement pickle via QPY and Parameter.assign to return Parameter

* fix template matching

this is rather a temporary solution, maybe we ought to disable string parsing in favor or a proper SymPy->parameter expression conversion

* fix rhs - lhs mixup

* fix more tests on binding {x: 0*y}

* fix last failing tests (see numeric(strict=False))

* clippy

* a lot of docs

* rename PyParameterExpression to ParameterExpression

* Thou shan't fake hyperrefs

* proper split: (Py)ParameterExpression

* rm pyclass from Symbol

* TODO's and no more RwLock

- rm Arc<RwLock<..>> for inner, since expressions are immutable and not shared, this is not needed
- address a bunch of todos, that include
  - rm rsub/rdiv/rpow
  - introduce try_to_value and _to_symbol
  - make internal data private

* First round of review comments

mainly removing some unnecessary clones, and fixing Hash for ParameterExpression

* rm `name_map` from PyParameterExpression

--- and just reconstruct the Py symbols from ParameterExpression.name_map

* Fix vector element parsing

Co-authored-by: "U-AzureAD\JUNDOI" doichan... (continued)

1204 of 1475 new or added lines in 12 files covered. (81.63%)

108 existing lines in 5 files now uncovered.

81743 of 93295 relevant lines covered (87.62%)

541492.67 hits per line

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

83.3
/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 hashbrown::hash_map::Entry;
14
use hashbrown::{HashMap, HashSet};
15
use num_complex::Complex64;
16
use pyo3::exceptions::{PyRuntimeError, PyTypeError, PyValueError, PyZeroDivisionError};
17
use pyo3::types::{IntoPyDict, PyNotImplemented, PySet, PyString};
18
use thiserror::Error;
19
use uuid::Uuid;
20

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

25
use pyo3::prelude::*;
26
use pyo3::IntoPyObjectExt;
27

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

34
use super::symbol_expr::{Symbol, Value, SYMEXPR_EPSILON};
35

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

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

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

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

100
impl PartialEq for ParameterExpression {
NEW
101
    fn eq(&self, other: &Self) -> bool {
×
NEW
102
        self.expr.eq(&other.expr)
×
NEW
103
    }
×
104
}
105

106
impl Eq for ParameterExpression {}
107

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

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

133
/// Lookup for which operations are binary (i.e. require two operands).
134
static BINARY_OPS: [OpCode; 8] = [
135
    // a HashSet would be better but requires unstable features
136
    OpCode::ADD,
137
    OpCode::SUB,
138
    OpCode::MUL,
139
    OpCode::DIV,
140
    OpCode::POW,
141
    OpCode::RSUB,
142
    OpCode::RDIV,
143
    OpCode::RPOW,
144
];
145

146
impl ParameterExpression {
147
    /// Initialize with an existing [SymbolExpr] and its valid name map.
148
    ///
149
    /// Caution: The caller **guarantees** that ``name_map`` is consistent with ``expr``.
150
    /// If uncertain, call [Self::from_symbol_expr], which automatically builds the correct name map.
151
    pub fn new(expr: SymbolExpr, name_map: HashMap<String, Symbol>) -> Self {
4,847,230✔
152
        Self { expr, name_map }
4,847,230✔
153
    }
4,847,230✔
154

155
    /// Construct from a [Symbol].
156
    pub fn from_symbol(symbol: Symbol) -> Self {
203,806✔
157
        Self {
203,806✔
158
            expr: SymbolExpr::Symbol(symbol.clone()),
203,806✔
159
            name_map: [(symbol.name(), symbol)].into(),
203,806✔
160
        }
203,806✔
161
    }
203,806✔
162

163
    /// Try casting to a [Symbol].
164
    ///
165
    /// This only succeeds if the underlying expression is, in fact, only a symbol.
NEW
166
    pub fn try_to_symbol(&self) -> Result<Symbol, ParameterError> {
×
NEW
167
        if let SymbolExpr::Symbol(symbol) = &self.expr {
×
NEW
168
            Ok(symbol.clone())
×
169
        } else {
NEW
170
            Err(ParameterError::NotASymbol)
×
171
        }
NEW
172
    }
×
173

174
    /// Try casting to a [Value].
175
    ///
176
    /// Attempt to evaluate the expression recursively and return a [Value] if fully bound.
177
    ///
178
    /// # Arguments
179
    ///
180
    /// * strict - If ``true``, only allow returning a value if all symbols are bound. If
181
    ///     ``false``, allow casting expressions to values, even though symbols might still exist.
182
    ///     For example, ``0 * x`` will return ``0`` for ``strict=false`` and otherwise return
183
    ///     an error.
184
    pub fn try_to_value(&self, strict: bool) -> Result<Value, ParameterError> {
4,863,670✔
185
        if strict && !self.name_map.is_empty() {
4,863,670✔
186
            let free_symbols = self.expr.parameters();
96✔
187
            return Err(ParameterError::UnboundParameters(free_symbols));
96✔
188
        }
4,863,574✔
189

190
        match self.expr.eval(true) {
4,863,574✔
191
            Some(value) => {
2,582,982✔
192
                // we try to restrict complex to real, if possible
193
                if let Value::Complex(c) = value {
2,582,982✔
194
                    if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON).contains(&c.im)
40,208✔
195
                    {
NEW
196
                        return Ok(Value::Real(c.re));
×
197
                    }
40,208✔
198
                }
2,542,774✔
199
                Ok(value)
2,582,982✔
200
            }
201
            None => {
202
                let free_symbols = self.expr.parameters();
2,280,592✔
203
                Err(ParameterError::UnboundParameters(free_symbols))
2,280,592✔
204
            }
205
        }
206
    }
4,863,670✔
207

208
    /// Construct from a [SymbolExpr].
209
    ///
210
    /// This populates the name map with the symbols in the expression.
211
    pub fn from_symbol_expr(expr: SymbolExpr) -> Self {
792,952✔
212
        let name_map = expr.name_map();
792,952✔
213
        Self { expr, name_map }
792,952✔
214
    }
792,952✔
215

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

NEW
221
        for inst in replay.iter() {
×
NEW
222
            let OPReplay { op, lhs, rhs } = inst;
×
223

224
            // put the values on the stack, if they exist
NEW
225
            if let Some(value) = lhs {
×
NEW
226
                stack.push(value.clone().into());
×
NEW
227
            }
×
NEW
228
            if let Some(value) = rhs {
×
NEW
229
                stack.push(value.clone().into());
×
NEW
230
            }
×
231

232
            // if we need two operands, pop rhs from the stack
NEW
233
            let rhs = if BINARY_OPS.contains(op) {
×
NEW
234
                Some(stack.pop().expect("Pop from empty stack"))
×
235
            } else {
NEW
236
                None
×
237
            };
238

239
            // pop lhs from the stack, this we always need
NEW
240
            let lhs = stack.pop().expect("Pop from empty stack");
×
241

242
            // apply the operation and put the result onto the stack for the next replay
NEW
243
            let result: ParameterExpression = match op {
×
NEW
244
                OpCode::ADD => lhs.add(&rhs.unwrap())?,
×
NEW
245
                OpCode::MUL => lhs.mul(&rhs.unwrap())?,
×
NEW
246
                OpCode::SUB => lhs.sub(&rhs.unwrap())?,
×
NEW
247
                OpCode::RSUB => rhs.unwrap().sub(&lhs)?,
×
NEW
248
                OpCode::POW => lhs.pow(&rhs.unwrap())?,
×
NEW
249
                OpCode::RPOW => rhs.unwrap().pow(&lhs)?,
×
NEW
250
                OpCode::DIV => lhs.div(&rhs.unwrap())?,
×
NEW
251
                OpCode::RDIV => rhs.unwrap().div(&lhs)?,
×
NEW
252
                OpCode::ABS => lhs.abs(),
×
NEW
253
                OpCode::SIN => lhs.sin(),
×
NEW
254
                OpCode::ASIN => lhs.asin(),
×
NEW
255
                OpCode::COS => lhs.cos(),
×
NEW
256
                OpCode::ACOS => lhs.acos(),
×
NEW
257
                OpCode::TAN => lhs.tan(),
×
NEW
258
                OpCode::ATAN => lhs.atan(),
×
NEW
259
                OpCode::CONJ => lhs.conjugate(),
×
NEW
260
                OpCode::LOG => lhs.log(),
×
NEW
261
                OpCode::EXP => lhs.exp(),
×
NEW
262
                OpCode::SIGN => lhs.sign(),
×
263
                OpCode::GRAD | OpCode::SUBSTITUTE => {
NEW
264
                    panic!("GRAD and SUBSTITUTE are not supported.")
×
265
                }
266
            };
NEW
267
            stack.push(result);
×
268
        }
269

270
        // once we're done, just return the last element in the stack
NEW
271
        Ok(stack
×
NEW
272
            .pop()
×
NEW
273
            .expect("Invalid QPY replay encountered during deserialization: empty OPReplay."))
×
NEW
274
    }
×
275

276
    /// Add an expression; ``self + rhs``.
277
    pub fn add(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,390,422✔
278
        let name_map = self.merged_name_map(rhs)?;
2,390,422✔
279
        Ok(Self {
2,390,420✔
280
            expr: &self.expr + &rhs.expr,
2,390,420✔
281
            name_map,
2,390,420✔
282
        })
2,390,420✔
283
    }
2,390,422✔
284

285
    /// Multiply with an expression; ``self * rhs``.
286
    pub fn mul(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,413,782✔
287
        let name_map = self.merged_name_map(rhs)?;
2,413,782✔
288
        Ok(Self {
2,413,780✔
289
            expr: &self.expr * &rhs.expr,
2,413,780✔
290
            name_map,
2,413,780✔
291
        })
2,413,780✔
292
    }
2,413,782✔
293

294
    /// Subtract another expression; ``self - rhs``.
295
    pub fn sub(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
22,858✔
296
        let name_map = self.merged_name_map(rhs)?;
22,858✔
297
        Ok(Self {
22,856✔
298
            expr: &self.expr - &rhs.expr,
22,856✔
299
            name_map,
22,856✔
300
        })
22,856✔
301
    }
22,858✔
302

303
    /// Divide by another expression; ``self / rhs``.
304
    pub fn div(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
13,078✔
305
        if rhs.expr.is_zero() {
13,078✔
306
            return Err(ParameterError::ZeroDivisionError);
1,012✔
307
        }
12,066✔
308

309
        let name_map = self.merged_name_map(rhs)?;
12,066✔
310
        Ok(Self {
12,064✔
311
            expr: &self.expr / &rhs.expr,
12,064✔
312
            name_map,
12,064✔
313
        })
12,064✔
314
    }
13,078✔
315

316
    /// Raise this expression to a power; ``self ^ rhs``.
317
    pub fn pow(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
3,016✔
318
        let name_map = self.merged_name_map(rhs)?;
3,016✔
319
        Ok(Self {
3,016✔
320
            expr: self.expr.pow(&rhs.expr),
3,016✔
321
            name_map,
3,016✔
322
        })
3,016✔
323
    }
3,016✔
324

325
    /// Apply the sine to this expression; ``sin(self)``.
326
    pub fn sin(&self) -> Self {
362✔
327
        Self {
362✔
328
            expr: self.expr.sin(),
362✔
329
            name_map: self.name_map.clone(),
362✔
330
        }
362✔
331
    }
362✔
332

333
    /// Apply the cosine to this expression; ``cos(self)``.
334
    pub fn cos(&self) -> Self {
352✔
335
        Self {
352✔
336
            expr: self.expr.cos(),
352✔
337
            name_map: self.name_map.clone(),
352✔
338
        }
352✔
339
    }
352✔
340

341
    /// Apply the tangent to this expression; ``tan(self)``.
342
    pub fn tan(&self) -> Self {
352✔
343
        Self {
352✔
344
            expr: self.expr.tan(),
352✔
345
            name_map: self.name_map.clone(),
352✔
346
        }
352✔
347
    }
352✔
348

349
    /// Apply the arcsine to this expression; ``asin(self)``.
350
    pub fn asin(&self) -> Self {
100✔
351
        Self {
100✔
352
            expr: self.expr.asin(),
100✔
353
            name_map: self.name_map.clone(),
100✔
354
        }
100✔
355
    }
100✔
356

357
    /// Apply the arccosine to this expression; ``acos(self)``.
358
    pub fn acos(&self) -> Self {
100✔
359
        Self {
100✔
360
            expr: self.expr.acos(),
100✔
361
            name_map: self.name_map.clone(),
100✔
362
        }
100✔
363
    }
100✔
364

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

373
    /// Exponentiate this expression; ``exp(self)``.
374
    pub fn exp(&self) -> Self {
350✔
375
        Self {
350✔
376
            expr: self.expr.exp(),
350✔
377
            name_map: self.name_map.clone(),
350✔
378
        }
350✔
379
    }
350✔
380

381
    /// Take the (natural) logarithm of this expression; ``log(self)``.
382
    pub fn log(&self) -> Self {
266✔
383
        Self {
266✔
384
            expr: self.expr.log(),
266✔
385
            name_map: self.name_map.clone(),
266✔
386
        }
266✔
387
    }
266✔
388

389
    /// Take the absolute value of this expression; ``|self|``.
390
    pub fn abs(&self) -> Self {
374✔
391
        Self {
374✔
392
            expr: self.expr.abs(),
374✔
393
            name_map: self.name_map.clone(),
374✔
394
        }
374✔
395
    }
374✔
396

397
    /// Return the sign of this expression; ``sign(self)``.
398
    pub fn sign(&self) -> Self {
10✔
399
        Self {
10✔
400
            expr: self.expr.sign(),
10✔
401
            name_map: self.name_map.clone(),
10✔
402
        }
10✔
403
    }
10✔
404

405
    /// Complex conjugate the expression.
406
    pub fn conjugate(&self) -> Self {
1,832✔
407
        Self {
1,832✔
408
            expr: self.expr.conjugate(),
1,832✔
409
            name_map: self.name_map.clone(),
1,832✔
410
        }
1,832✔
411
    }
1,832✔
412

413
    /// Compute the derivative of the expression with respect to the provided symbol.
414
    ///
415
    /// Note that this keeps the name map unchanged. Meaning that computing the derivative
416
    /// of ``x`` will yield ``1`` but the expression still owns the symbol ``x``. This is
417
    /// done such that we can still bind the value ``x`` in an automated process.
418
    pub fn derivative(&self, param: &Symbol) -> Result<Self, ParameterError> {
60✔
419
        Ok(Self {
420
            expr: self
60✔
421
                .expr
60✔
422
                .derivative(param)
60✔
423
                .map_err(ParameterError::DerivativeNotSupported)?,
60✔
424
            name_map: self.name_map.clone(),
56✔
425
        })
426
    }
60✔
427

428
    /// Substitute symbols with [ParameterExpression]s.
429
    ///
430
    /// # Arguments
431
    ///
432
    /// * map - A hashmap with [Symbol] keys and [ParameterExpression]s to replace these
433
    ///     symbols with.
434
    /// * allow_unknown_parameters - If `false`, returns an error if any symbol in the
435
    ///     hashmap is not present in the expression. If `true`, unknown symbols are ignored.
436
    ///     Setting to `true` is slightly faster as it does not involve additional checks.
437
    ///
438
    /// # Returns
439
    ///
440
    /// * `Ok(Self)` - A parameter expression with the substituted expressions.
441
    /// * `Err(ParameterError)` - An error if the subtitution failed.
442
    pub fn subs(
13,566✔
443
        &self,
13,566✔
444
        map: &HashMap<Symbol, Self>,
13,566✔
445
        allow_unknown_parameters: bool,
13,566✔
446
    ) -> Result<Self, ParameterError> {
13,566✔
447
        // Build the outgoing name map. In the process we check for any duplicates.
448
        let mut name_map: HashMap<String, Symbol> = HashMap::new();
13,566✔
449
        let mut symbol_map: HashMap<Symbol, SymbolExpr> = HashMap::new();
13,566✔
450

451
        // If we don't allow for unknown parameters, check if there are any.
452
        if !allow_unknown_parameters {
13,566✔
453
            let existing: HashSet<&Symbol> = self.name_map.values().collect();
13,564✔
454
            let to_replace: HashSet<&Symbol> = map.keys().collect();
13,564✔
455
            let mut difference = to_replace.difference(&existing).peekable();
13,564✔
456

457
            if difference.peek().is_some() {
13,564✔
458
                let different_symbols = difference.map(|s| (**s).clone()).collect();
2✔
459
                return Err(ParameterError::UnknownParameters(different_symbols));
2✔
460
            }
13,562✔
461
        }
2✔
462

463
        for (name, symbol) in self.name_map.iter() {
16,306✔
464
            // check if the symbol will get replaced
465
            if let Some(replacement) = map.get(symbol) {
16,306✔
466
                // If yes, update the name_map. This also checks for duplicates.
467
                for (replacement_name, replacement_symbol) in replacement.name_map.iter() {
13,792✔
468
                    if let Some(duplicate) = name_map.get(replacement_name) {
13,792✔
469
                        // If a symbol with the same name already exists, check whether it is
470
                        // the same symbol (fine) or a different symbol with the same name (conflict)!
471
                        if duplicate != replacement_symbol {
34✔
NEW
472
                            return Err(ParameterError::NameConflict);
×
473
                        }
34✔
474
                    } else {
13,758✔
475
                        // SAFETY: We know the key does not exist yet.
13,758✔
476
                        unsafe {
13,758✔
477
                            name_map.insert_unique_unchecked(
13,758✔
478
                                replacement_name.clone(),
13,758✔
479
                                replacement_symbol.clone(),
13,758✔
480
                            )
13,758✔
481
                        };
13,758✔
482
                    }
13,758✔
483
                }
484

485
                // If we got until here, there were no duplicates, so we are safe to
486
                // add this symbol to the internal replacement map.
487
                symbol_map.insert(symbol.clone(), replacement.expr.clone());
13,566✔
488
            } else {
489
                // no replacement for this symbol, carry on
490
                match name_map.entry(name.clone()) {
2,740✔
491
                    Entry::Occupied(duplicate) => {
20✔
492
                        if duplicate.get() != symbol {
20✔
493
                            return Err(ParameterError::NameConflict);
2✔
494
                        }
18✔
495
                    }
496
                    Entry::Vacant(e) => {
2,720✔
497
                        e.insert(symbol.clone());
2,720✔
498
                    }
2,720✔
499
                }
500
            }
501
        }
502

503
        let res = self.expr.subs(&symbol_map);
13,562✔
504
        Ok(Self {
13,562✔
505
            expr: res,
13,562✔
506
            name_map,
13,562✔
507
        })
13,562✔
508
    }
13,566✔
509

510
    /// Bind symbols to values.
511
    ///
512
    /// # Arguments
513
    ///
514
    /// * map - A hashmap with [Symbol] keys and [Value]s to replace these
515
    ///     symbols with.
516
    /// * allow_unknown_parameter - If `false`, returns an error if any symbol in the
517
    ///     hashmap is not present in the expression. If `true`, unknown symbols are ignored.
518
    ///     Setting to `true` is slightly faster as it does not involve additional checks.
519
    ///
520
    /// # Returns
521
    ///
522
    /// * `Ok(Self)` - A parameter expression with the bound symbols.
523
    /// * `Err(ParameterError)` - An error if binding failed.
524
    pub fn bind(
208,288✔
525
        &self,
208,288✔
526
        map: &HashMap<Symbol, Value>,
208,288✔
527
        allow_unknown_parameters: bool,
208,288✔
528
    ) -> Result<Self, ParameterError> {
208,288✔
529
        // The set of symbols we will bind. Used twice, hence pre-computed here.
530
        let bind_symbols: HashSet<&Symbol> = map.keys().collect();
208,288✔
531

532
        // If we don't allow for unknown parameters, check if there are any.
533
        if !allow_unknown_parameters {
208,288✔
534
            let existing: HashSet<&Symbol> = self.name_map.values().collect();
208,186✔
535
            let to_replace: HashSet<&Symbol> = map.keys().collect();
208,186✔
536
            let mut difference = to_replace.difference(&existing).peekable();
208,186✔
537

538
            if difference.peek().is_some() {
208,186✔
NEW
539
                let different_symbols = difference.map(|s| (**s).clone()).collect();
×
NEW
540
                return Err(ParameterError::UnknownParameters(different_symbols));
×
541
            }
208,186✔
542
        }
102✔
543

544
        // bind the symbol expression and then check the outcome for inf/nan, or numeric values
545
        let bound_expr = self.expr.bind(map);
208,288✔
546
        let bound = match bound_expr.eval(true) {
208,288✔
547
            Some(v) => match &v {
207,048✔
548
                Value::Real(r) => {
143,898✔
549
                    if r.is_infinite() {
143,898✔
550
                        Err(ParameterError::BindingInf)
1,828✔
551
                    } else if r.is_nan() {
142,070✔
NEW
552
                        Err(ParameterError::BindingNaN)
×
553
                    } else {
554
                        Ok(SymbolExpr::Value(v))
142,070✔
555
                    }
556
                }
557
                Value::Int(_) => Ok(SymbolExpr::Value(v)),
21,418✔
558
                Value::Complex(c) => {
41,732✔
559
                    if c.re.is_infinite() || c.im.is_infinite() {
41,732✔
NEW
560
                        Err(ParameterError::BindingInf)
×
561
                    } else if c.re.is_nan() || c.im.is_nan() {
41,732✔
NEW
562
                        Err(ParameterError::BindingNaN)
×
563
                    } else if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON)
41,732✔
564
                        .contains(&c.im)
41,732✔
565
                    {
566
                        Ok(SymbolExpr::Value(Value::Real(c.re)))
1,724✔
567
                    } else {
568
                        Ok(SymbolExpr::Value(v))
40,008✔
569
                    }
570
                }
571
            },
572
            None => Ok(bound_expr),
1,240✔
573
        }?;
1,828✔
574

575
        // update the name map by removing the bound parameters
576
        let bound_name_map: HashMap<String, Symbol> = self
206,460✔
577
            .name_map
206,460✔
578
            .iter()
206,460✔
579
            .filter(|(_, symbol)| !bind_symbols.contains(symbol))
453,506✔
580
            .map(|(name, symbol)| (name.clone(), symbol.clone()))
206,460✔
581
            .collect();
206,460✔
582

583
        Ok(Self {
206,460✔
584
            expr: bound,
206,460✔
585
            name_map: bound_name_map,
206,460✔
586
        })
206,460✔
587
    }
208,288✔
588

589
    /// Merge name maps.
590
    ///
591
    /// # Arguments
592
    ///
593
    /// * `other` - The other parameter expression whose symbols we add to self.
594
    ///
595
    /// # Returns
596
    ///
597
    /// * `Ok(HashMap<String, Symbol>)` - The merged name map.
598
    /// * `Err(ParameterError)` - An error if there was a name conflict.
599
    fn merged_name_map(&self, other: &Self) -> Result<HashMap<String, Symbol>, ParameterError> {
4,842,144✔
600
        let mut merged = self.name_map.clone();
4,842,144✔
601
        for (name, param) in other.name_map.iter() {
4,842,144✔
602
            match merged.get(name) {
434,410✔
603
                Some(existing_param) => {
83,152✔
604
                    if param != existing_param {
83,152✔
605
                        return Err(ParameterError::NameConflict);
8✔
606
                    }
83,144✔
607
                }
608
                None => {
351,258✔
609
                    // SAFETY: We ensured the key is unique
351,258✔
610
                    let _ = unsafe { merged.insert_unique_unchecked(name.clone(), param.clone()) };
351,258✔
611
                }
351,258✔
612
            }
613
        }
614
        Ok(merged)
4,842,136✔
615
    }
4,842,144✔
616
}
617

618
/// A parameter expression.
619
///
620
/// This is backed by Qiskit's symbolic expression engine and a cache
621
/// for the parameters inside the expression.
622
#[pyclass(
623
    subclass,
624
    module = "qiskit._accelerate.circuit",
625
    name = "ParameterExpression"
626
)]
627
#[derive(Clone, Debug)]
628
pub struct PyParameterExpression {
629
    pub inner: ParameterExpression,
630
}
631

632
impl Default for PyParameterExpression {
633
    /// The default constructor returns zero.
NEW
634
    fn default() -> Self {
×
NEW
635
        Self {
×
NEW
636
            inner: ParameterExpression::default(),
×
NEW
637
        }
×
NEW
638
    }
×
639
}
640

641
impl fmt::Display for PyParameterExpression {
642
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336,786✔
643
        self.inner.fmt(f)
336,786✔
644
    }
336,786✔
645
}
646

647
impl From<ParameterExpression> for PyParameterExpression {
648
    fn from(value: ParameterExpression) -> Self {
10,909,558✔
649
        Self { inner: value }
10,909,558✔
650
    }
10,909,558✔
651
}
652

653
impl PyParameterExpression {
654
    /// Attempt to extract a `PyParameterExpression` from a bound `PyAny`.
655
    ///
656
    /// This will try to coerce to the strictest data type:
657
    /// Int - Real - Complex - PyParameterVectorElement - PyParameter - PyParameterExpression.
658
    ///
659
    /// # Arguments:
660
    ///
661
    /// * ob - The bound `PyAny` to extract from.
662
    ///
663
    /// # Returns
664
    ///
665
    /// * `Ok(Self)` - The extracted expression.
666
    /// * `Err(PyResult)` - An error if extraction to all above types failed.
667
    fn extract_coerce(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
5,216,000✔
668
        if let Ok(i) = ob.extract::<i64>() {
5,216,000✔
669
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(i)), HashMap::new()).into())
72,848✔
670
        } else if let Ok(c) = ob.extract::<Complex64>() {
5,143,152✔
671
            if c.is_infinite() || c.is_nan() {
4,773,580✔
672
                return Err(ParameterError::InvalidValue.into());
40✔
673
            }
4,773,540✔
674
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(c)), HashMap::new()).into())
4,773,540✔
675
        } else if let Ok(r) = ob.extract::<f64>() {
369,572✔
NEW
676
            if r.is_infinite() || r.is_nan() {
×
NEW
677
                return Err(ParameterError::InvalidValue.into());
×
NEW
678
            }
×
NEW
679
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(r)), HashMap::new()).into())
×
680
        } else if let Ok(element) = ob.extract::<PyParameterVectorElement>() {
369,572✔
681
            Ok(ParameterExpression::from_symbol(element.symbol.clone()).into())
194,504✔
682
        } else if let Ok(parameter) = ob.extract::<PyParameter>() {
175,068✔
683
            Ok(ParameterExpression::from_symbol(parameter.symbol.clone()).into())
9,302✔
684
        } else {
685
            ob.extract::<PyParameterExpression>()
165,766✔
686
        }
687
    }
5,216,000✔
688
}
689

NEW
690
#[pymethods]
×
691
impl PyParameterExpression {
692
    /// This is a **strictly internal** constructor and **should not be used**.
693
    /// It is subject to arbitrary change in between Qiskit versions and cannot be relied on.
694
    /// Parameter expressions should always be constructed from applying operations on
695
    /// parameters, or by loading via QPY.
696
    ///
697
    /// The input values are allowed to be None for pickling purposes.
698
    #[new]
699
    #[pyo3(signature = (name_map=None, expr=None))]
700
    pub fn py_new(
70✔
701
        name_map: Option<HashMap<String, PyParameter>>,
70✔
702
        expr: Option<String>,
70✔
703
    ) -> PyResult<Self> {
70✔
704
        match (name_map, expr) {
70✔
NEW
705
            (None, None) => Ok(Self::default()),
×
706
            (Some(name_map), Some(expr)) => {
70✔
707
                // We first parse the expression and then update the symbols with the ones
708
                // the user provided. The replacement relies on the names to match.
709
                // This is hacky and we likely want a more reliably conversion from a SymPy object,
710
                // if we decide we want to continue supporting this.
711
                let expr = parse_expression(&expr)
70✔
712
                    .map_err(|_| PyRuntimeError::new_err("Failed parsing input expression"))?;
70✔
713
                let symbol_map: HashMap<String, Symbol> = name_map
70✔
714
                    .iter()
70✔
715
                    .map(|(string, param)| (string.clone(), param.symbol.clone()))
70✔
716
                    .collect();
70✔
717

718
                let replaced_expr = symbol_expr::replace_symbol(&expr, &symbol_map);
70✔
719

720
                let inner = ParameterExpression::new(replaced_expr, symbol_map);
70✔
721
                Ok(Self { inner })
70✔
722
            }
NEW
723
            _ => Err(PyValueError::new_err(
×
NEW
724
                "Pass either both a name_map and expr, or neither",
×
NEW
725
            )),
×
726
        }
727
    }
70✔
728

729
    #[allow(non_snake_case)]
730
    #[staticmethod]
731
    pub fn _Value(value: &Bound<PyAny>) -> PyResult<Self> {
34✔
732
        Self::extract_coerce(value)
34✔
733
    }
34✔
734

735
    /// Check if the expression corresponds to a plain symbol.
736
    ///
737
    /// Returns:
738
    ///     ``True`` is this expression corresponds to a symbol, ``False`` otherwise.
739
    pub fn is_symbol(&self) -> PyResult<bool> {
480✔
740
        Ok(matches!(self.inner.expr, SymbolExpr::Symbol(_)))
480✔
741
    }
480✔
742

743
    /// Cast this expression to a numeric value.
744
    ///
745
    /// Args:
746
    ///     strict: If ``True`` (default) this function raises an error if there are any
747
    ///         unbound symbols in the expression. If ``False``, this allows casting
748
    ///         if the expression represents a numeric value, regardless of unbound symbols.
749
    ///         For example ``(0 * Parameter("x"))`` is 0 but has the symbol ``x`` present.
750
    #[pyo3(signature = (strict=true))]
751
    pub fn numeric(&self, py: Python, strict: bool) -> PyResult<PyObject> {
280,192✔
752
        match self.inner.try_to_value(strict)? {
280,192✔
753
            Value::Real(r) => r.into_py_any(py),
252,658✔
754
            Value::Int(i) => i.into_py_any(py),
12,834✔
755
            Value::Complex(c) => c.into_py_any(py),
14,492✔
756
        }
757
    }
280,192✔
758

759
    /// Return a SymPy equivalent of this expression.
760
    ///
761
    /// Returns:
762
    ///     A SymPy equivalent of this expression.
763
    pub fn sympify(&self, py: Python) -> PyResult<PyObject> {
480✔
764
        let py_sympify = SYMPIFY_PARAMETER_EXPRESSION.get(py);
480✔
765
        py_sympify.call1(py, (self.clone(),))
480✔
766
    }
480✔
767

768
    /// Get the parameters present in the expression.
769
    ///
770
    /// .. note::
771
    ///     
772
    ///     Qiskit guarantees equality (via ``==``) of parameters retrieved from an expression
773
    ///     with the original :class:`.Parameter` objects used to create this expression,
774
    ///     but does **not guarantee** ``is`` comparisons to succeed.
775
    ///
776
    #[getter]
777
    pub fn parameters<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PySet>> {
655,262✔
778
        let py_parameters: Vec<PyObject> = self
655,262✔
779
            .inner
655,262✔
780
            .name_map
655,262✔
781
            .values()
655,262✔
782
            .map(|symbol| {
678,138✔
783
                match (&symbol.index, &symbol.vector) {
678,138✔
784
                    // if index and vector is set, it is an element
785
                    (Some(_index), Some(_vector)) => Ok(Py::new(
582,072✔
786
                        py,
582,072✔
787
                        PyParameterVectorElement::from_symbol(symbol.clone()),
582,072✔
NEW
788
                    )?
×
789
                    .into_any()),
582,072✔
790
                    // else, a normal parameter
791
                    _ => Ok(Py::new(py, PyParameter::from_symbol(symbol.clone()))?.into_any()),
96,066✔
792
                }
793
            })
678,138✔
794
            .collect::<PyResult<_>>()?;
655,262✔
795
        PySet::new(py, py_parameters)
655,262✔
796
    }
655,262✔
797

798
    /// Sine of the expression.
799
    #[inline]
800
    #[pyo3(name = "sin")]
801
    pub fn py_sin(&self) -> Self {
362✔
802
        self.inner.sin().into()
362✔
803
    }
362✔
804

805
    /// Cosine of the expression.
806
    #[inline]
807
    #[pyo3(name = "cos")]
808
    pub fn py_cos(&self) -> Self {
352✔
809
        self.inner.cos().into()
352✔
810
    }
352✔
811

812
    /// Tangent of the expression.
813
    #[inline]
814
    #[pyo3(name = "tan")]
815
    pub fn py_tan(&self) -> Self {
352✔
816
        self.inner.tan().into()
352✔
817
    }
352✔
818

819
    /// Arcsine of the expression.
820
    #[inline]
821
    pub fn arcsin(&self) -> Self {
100✔
822
        self.inner.asin().into()
100✔
823
    }
100✔
824

825
    /// Arccosine of the expression.
826
    #[inline]
827
    pub fn arccos(&self) -> Self {
100✔
828
        self.inner.acos().into()
100✔
829
    }
100✔
830

831
    /// Arctangent of the expression.
832
    #[inline]
833
    pub fn arctan(&self) -> Self {
100✔
834
        self.inner.atan().into()
100✔
835
    }
100✔
836

837
    /// Exponentiate the expression.
838
    #[inline]
839
    #[pyo3(name = "exp")]
840
    pub fn py_exp(&self) -> Self {
350✔
841
        self.inner.exp().into()
350✔
842
    }
350✔
843

844
    /// Take the natural logarithm of the expression.
845
    #[inline]
846
    #[pyo3(name = "log")]
847
    pub fn py_log(&self) -> Self {
266✔
848
        self.inner.log().into()
266✔
849
    }
266✔
850

851
    /// Take the absolute value of the expression.
852
    #[inline]
853
    #[pyo3(name = "abs")]
854
    pub fn py_abs(&self) -> Self {
12✔
855
        self.inner.abs().into()
12✔
856
    }
12✔
857

858
    /// Return the sign of the expression.
859
    #[inline]
860
    #[pyo3(name = "sign")]
861
    pub fn py_sign(&self) -> Self {
10✔
862
        self.inner.sign().into()
10✔
863
    }
10✔
864

865
    /// Return the complex conjugate of the expression.
866
    #[inline]
867
    #[pyo3(name = "conjugate")]
868
    pub fn py_conjugate(&self) -> Self {
1,832✔
869
        self.inner.conjugate().into()
1,832✔
870
    }
1,832✔
871

872
    /// Check whether the expression represents a real number.
873
    ///
874
    /// Note that this will return ``None`` if there are unbound parameters, in which case
875
    /// it cannot be determined whether the expression is real.
876
    #[inline]
877
    #[pyo3(name = "is_real")]
878
    pub fn py_is_real(&self) -> Option<bool> {
270✔
879
        self.inner.expr.is_real()
270✔
880
    }
270✔
881

882
    /// Return derivative of this expression with respect to the input parameter.
883
    ///
884
    /// Args:
885
    ///     param: The parameter with respect to which the derivative is calculated.
886
    ///
887
    /// Returns:
888
    ///     The derivative.
889
    pub fn gradient(&self, param: &Bound<'_, PyAny>) -> PyResult<Self> {
60✔
890
        let symbol = symbol_from_py_parameter(param)?;
60✔
891
        let d_expr = self.inner.derivative(&symbol)?;
60✔
892
        Ok(d_expr.into())
56✔
893
    }
60✔
894

895
    /// Return all values in this equation.
896
    pub fn _values(&self, py: Python) -> PyResult<Vec<PyObject>> {
78✔
897
        self.inner
78✔
898
            .expr
78✔
899
            .values()
78✔
900
            .iter()
78✔
901
            .map(|val| match val {
78✔
902
                Value::Real(r) => r.into_py_any(py),
34✔
903
                Value::Int(i) => i.into_py_any(py),
10✔
NEW
904
                Value::Complex(c) => c.into_py_any(py),
×
905
            })
44✔
906
            .collect()
78✔
907
    }
78✔
908

909
    /// Returns a new expression with replacement parameters.
910
    ///
911
    /// Args:
912
    ///     parameter_map: Mapping from :class:`.Parameter`\ s in ``self`` to the
913
    ///         :class:`.ParameterExpression` instances with which they should be replaced.
914
    ///     allow_unknown_parameters: If ``False``, raises an error if ``parameter_map``
915
    ///         contains :class:`.Parameter`\ s in the keys outside those present in the expression.
916
    ///         If ``True``, any such parameters are simply ignored.
917
    ///
918
    /// Raises:
919
    ///     CircuitError:
920
    ///         - If parameter_map contains parameters outside those in self.
921
    ///         - If the replacement parameters in ``parameter_map`` would result in
922
    ///           a name conflict in the generated expression.
923
    ///
924
    /// Returns:
925
    ///     A new expression with the specified parameters replaced.
926
    #[pyo3(name = "subs")]
927
    #[pyo3(signature = (parameter_map, allow_unknown_parameters=false))]
928
    pub fn py_subs(
13,566✔
929
        &self,
13,566✔
930
        parameter_map: HashMap<PyParameter, Self>,
13,566✔
931
        allow_unknown_parameters: bool,
13,566✔
932
    ) -> PyResult<Self> {
13,566✔
933
        // reduce the map to a HashMap<Symbol, ParameterExpression>
934
        let map = parameter_map
13,566✔
935
            .into_iter()
13,566✔
936
            .map(|(param, expr)| Ok((param.symbol, expr.inner)))
13,570✔
937
            .collect::<PyResult<_>>()?;
13,566✔
938

939
        // apply to the inner expression
940
        match self.inner.subs(&map, allow_unknown_parameters) {
13,566✔
941
            Ok(subbed) => Ok(subbed.into()),
13,562✔
942
            Err(e) => Err(e.into()),
4✔
943
        }
944
    }
13,566✔
945

946
    /// Binds the provided set of parameters to their corresponding values.
947
    ///
948
    /// Args:
949
    ///     parameter_values: Mapping of :class:`.Parameter` instances to the numeric value to which
950
    ///         they will be bound.
951
    ///     allow_unknown_parameters: If ``False``, raises an error if ``parameter_values``
952
    ///         contains :class:`.Parameter`\ s in the keys outside those present in the expression.
953
    ///         If ``True``, any such parameters are simply ignored.
954
    ///
955
    /// Raises:
956
    ///     CircuitError:
957
    ///         - If parameter_values contains parameters outside those in self.
958
    ///         - If a non-numeric value is passed in ``parameter_values``.
959
    ///     ZeroDivisionError:
960
    ///         - If binding the provided values requires division by zero.
961
    ///
962
    /// Returns:
963
    ///     A new expression parameterized by any parameters which were not bound by
964
    ///     ``parameter_values``.
965
    #[pyo3(name = "bind")]
966
    #[pyo3(signature = (parameter_values, allow_unknown_parameters=false))]
967
    pub fn py_bind(
208,288✔
968
        &self,
208,288✔
969
        parameter_values: HashMap<PyParameter, Bound<PyAny>>,
208,288✔
970
        allow_unknown_parameters: bool,
208,288✔
971
    ) -> PyResult<Self> {
208,288✔
972
        // reduce the map to a HashMap<Symbol, Value>
973
        let map = parameter_values
208,288✔
974
            .into_iter()
208,288✔
975
            .map(|(param, value)| {
455,034✔
976
                let value = value.extract()?;
455,034✔
977
                Ok((param.symbol, value))
455,034✔
978
            })
455,034✔
979
            .collect::<PyResult<_>>()?;
208,288✔
980

981
        // apply to the inner expression
982
        match self.inner.bind(&map, allow_unknown_parameters) {
208,288✔
983
            Ok(bound) => Ok(bound.into()),
206,460✔
984
            Err(e) => Err(e.into()),
1,828✔
985
        }
986
    }
208,288✔
987

988
    /// Assign one parameter to a value, which can either be numeric or another parameter
989
    /// expression.
990
    ///
991
    /// Args:
992
    ///     parameter: A parameter in this expression whose value will be updated.
993
    ///     value: The new value to bind to.
994
    ///
995
    /// Returns:
996
    ///     A new expression parameterized by any parameters which were not bound by assignment.
997
    #[pyo3(name = "assign")]
998
    pub fn py_assign(&self, parameter: PyParameter, value: &Bound<PyAny>) -> PyResult<Self> {
36,240✔
999
        if let Ok(expr) = value.downcast::<Self>() {
36,240✔
1000
            let map = [(parameter, expr.borrow().clone())].into_iter().collect();
13,488✔
1001
            self.py_subs(map, false)
13,488✔
1002
        } else if value.extract::<Value>().is_ok() {
22,752✔
1003
            let map = [(parameter, value.clone())].into_iter().collect();
22,752✔
1004
            self.py_bind(map, false)
22,752✔
1005
        } else {
NEW
1006
            Err(PyValueError::new_err(
×
NEW
1007
                "Unexpected value in assign: {replacement:?}",
×
NEW
1008
            ))
×
1009
        }
1010
    }
36,240✔
1011

1012
    #[inline]
NEW
1013
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1014
        // ParameterExpression is immutable.
NEW
1015
        slf
×
NEW
1016
    }
×
1017

1018
    #[inline]
1019
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
242✔
1020
        // Everything a ParameterExpression contains is immutable.
1021
        slf
242✔
1022
    }
242✔
1023

1024
    pub fn __eq__(&self, rhs: &Bound<PyAny>) -> PyResult<bool> {
212,824✔
1025
        if let Ok(rhs) = Self::extract_coerce(rhs) {
212,824✔
1026
            match rhs.inner.expr {
212,812✔
1027
                SymbolExpr::Value(v) => match self.inner.try_to_value(false) {
20,148✔
1028
                    Ok(e) => Ok(e == v),
2,562✔
1029
                    Err(_) => Ok(false),
17,586✔
1030
                },
1031
                _ => Ok(self.inner.expr == rhs.inner.expr),
192,664✔
1032
            }
1033
        } else {
1034
            Ok(false)
12✔
1035
        }
1036
    }
212,824✔
1037

1038
    #[inline]
1039
    pub fn __abs__(&self) -> Self {
362✔
1040
        self.inner.abs().into()
362✔
1041
    }
362✔
1042

1043
    #[inline]
1044
    pub fn __pos__(&self) -> Self {
4✔
1045
        self.clone()
4✔
1046
    }
4✔
1047

1048
    pub fn __neg__(&self) -> Self {
772✔
1049
        Self {
772✔
1050
            inner: ParameterExpression::new(-&self.inner.expr, self.inner.name_map.clone()),
772✔
1051
        }
772✔
1052
    }
772✔
1053

1054
    pub fn __add__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
2,324,132✔
1055
        if let Ok(rhs) = Self::extract_coerce(rhs) {
2,324,132✔
1056
            Ok(self.inner.add(&rhs.inner)?.into())
2,324,120✔
1057
        } else {
1058
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1059
                "Unsupported data type for __add__",
12✔
1060
            ))
12✔
1061
        }
1062
    }
2,324,132✔
1063

1064
    pub fn __radd__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
66,314✔
1065
        if let Ok(lhs) = Self::extract_coerce(lhs) {
66,314✔
1066
            Ok(lhs.inner.add(&self.inner)?.into())
66,302✔
1067
        } else {
1068
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1069
                "Unsupported data type for __radd__",
12✔
1070
            ))
12✔
1071
        }
1072
    }
66,314✔
1073

1074
    pub fn __sub__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
18,516✔
1075
        if let Ok(rhs) = Self::extract_coerce(rhs) {
18,516✔
1076
            Ok(self.inner.sub(&rhs.inner)?.into())
18,504✔
1077
        } else {
1078
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1079
                "Unsupported data type for __sub__",
12✔
1080
            ))
12✔
1081
        }
1082
    }
18,516✔
1083

1084
    pub fn __rsub__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
4,366✔
1085
        if let Ok(lhs) = Self::extract_coerce(lhs) {
4,366✔
1086
            Ok(lhs.inner.sub(&self.inner)?.into())
4,354✔
1087
        } else {
1088
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1089
                "Unsupported data type for __rsub__",
12✔
1090
            ))
12✔
1091
        }
1092
    }
4,366✔
1093

1094
    pub fn __mul__<'py>(&self, rhs: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
2,408,264✔
1095
        let py = rhs.py();
2,408,264✔
1096
        if let Ok(rhs) = Self::extract_coerce(rhs) {
2,408,264✔
1097
            match self.inner.mul(&rhs.inner) {
2,400,908✔
1098
                Ok(result) => PyParameterExpression::from(result).into_bound_py_any(py),
2,400,906✔
1099
                Err(e) => Err(PyErr::from(e)),
2✔
1100
            }
1101
        } else {
1102
            PyNotImplemented::get(py).into_bound_py_any(py)
7,356✔
1103
        }
1104
    }
2,408,264✔
1105

1106
    pub fn __rmul__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
12,886✔
1107
        if let Ok(lhs) = Self::extract_coerce(lhs) {
12,886✔
1108
            Ok(lhs.inner.mul(&self.inner)?.into())
12,874✔
1109
        } else {
1110
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1111
                "Unsupported data type for __rmul__",
12✔
1112
            ))
12✔
1113
        }
1114
    }
12,886✔
1115

1116
    pub fn __truediv__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
9,032✔
1117
        if let Ok(rhs) = Self::extract_coerce(rhs) {
9,032✔
1118
            Ok(self.inner.div(&rhs.inner)?.into())
9,020✔
1119
        } else {
1120
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1121
                "Unsupported data type for __truediv__",
12✔
1122
            ))
12✔
1123
        }
1124
    }
9,032✔
1125

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

1136
    pub fn __pow__(&self, rhs: &Bound<PyAny>, _modulo: Option<i32>) -> PyResult<Self> {
1,998✔
1137
        if let Ok(rhs) = Self::extract_coerce(rhs) {
1,998✔
1138
            Ok(self.inner.pow(&rhs.inner)?.into())
1,986✔
1139
        } else {
1140
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1141
                "Unsupported data type for __pow__",
12✔
1142
            ))
12✔
1143
        }
1144
    }
1,998✔
1145

1146
    pub fn __rpow__(&self, lhs: &Bound<PyAny>, _modulo: Option<i32>) -> PyResult<Self> {
1,042✔
1147
        if let Ok(lhs) = Self::extract_coerce(lhs) {
1,042✔
1148
            Ok(lhs.inner.pow(&self.inner)?.into())
1,030✔
1149
        } else {
1150
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1151
                "Unsupported data type for __rpow__",
12✔
1152
            ))
12✔
1153
        }
1154
    }
1,042✔
1155

1156
    pub fn __int__(&self) -> PyResult<i64> {
176✔
1157
        match self.inner.try_to_value(false)? {
176✔
1158
            Value::Complex(_) => Err(PyTypeError::new_err(
42✔
1159
                "Cannot cast complex parameter to int.",
42✔
1160
            )),
42✔
1161
            Value::Real(r) => {
90✔
1162
                let rounded = r.floor();
90✔
1163
                Ok(rounded as i64)
90✔
1164
            }
1165
            Value::Int(i) => Ok(i),
42✔
1166
        }
1167
    }
176✔
1168

1169
    pub fn __float__(&self) -> PyResult<f64> {
362,816✔
1170
        match self.inner.try_to_value(false)? {
362,816✔
1171
            Value::Complex(c) => {
44✔
1172
                if c.im.abs() > SYMEXPR_EPSILON {
44✔
1173
                    Err(PyTypeError::new_err(
44✔
1174
                        "Could not cast complex parameter expression to float.",
44✔
1175
                    ))
44✔
1176
                } else {
NEW
1177
                    Ok(c.re)
×
1178
                }
1179
            }
1180
            Value::Real(r) => Ok(r),
128✔
1181
            Value::Int(i) => Ok(i as f64),
56✔
1182
        }
1183
    }
362,816✔
1184

1185
    pub fn __complex__(&self) -> PyResult<Complex64> {
2,662,170✔
1186
        match self.inner.try_to_value(false)? {
2,662,170✔
1187
            Value::Complex(c) => Ok(c),
25,546✔
1188
            Value::Real(r) => Ok(Complex64::new(r, 0.)),
2,254,452✔
1189
            Value::Int(i) => Ok(Complex64::new(i as f64, 0.)),
20,034✔
1190
        }
1191
    }
2,662,170✔
1192

1193
    pub fn __str__(&self) -> String {
336,786✔
1194
        self.to_string()
336,786✔
1195
    }
336,786✔
1196

1197
    pub fn __hash__(&self, py: Python) -> PyResult<u64> {
1,538,168✔
1198
        match self.inner.try_to_value(false) {
1,538,168✔
1199
            // if a value, we promise to match the hash of the raw value!
1200
            Ok(value) => {
2✔
1201
                let py_hash = BUILTIN_HASH.get_bound(py);
2✔
1202
                match value {
2✔
NEW
1203
                    Value::Complex(c) => py_hash.call1((c,))?.extract::<u64>(),
×
1204
                    Value::Real(r) => py_hash.call1((r,))?.extract::<u64>(),
2✔
NEW
1205
                    Value::Int(i) => py_hash.call1((i,))?.extract::<u64>(),
×
1206
                }
1207
            }
1208
            Err(_) => {
1209
                let mut hasher = DefaultHasher::new();
1,538,166✔
1210
                self.inner.expr.string_id().hash(&mut hasher);
1,538,166✔
1211
                Ok(hasher.finish())
1,538,166✔
1212
            }
1213
        }
1214
    }
1,538,168✔
1215

1216
    fn __getstate__(&self) -> PyResult<Vec<OPReplay>> {
1,540✔
1217
        // To pickle the object we use the QPY replay and rebuild from that.
1218
        self._qpy_replay()
1,540✔
1219
    }
1,540✔
1220

NEW
1221
    fn __setstate__(&mut self, state: Vec<OPReplay>) -> PyResult<()> {
×
NEW
1222
        let from_qpy = ParameterExpression::from_qpy(&state)?;
×
NEW
1223
        self.inner = from_qpy;
×
NEW
1224
        Ok(())
×
NEW
1225
    }
×
1226

1227
    #[getter]
1228
    fn _qpy_replay(&self) -> PyResult<Vec<OPReplay>> {
1,842✔
1229
        let mut replay = Vec::new();
1,842✔
1230
        qpy_replay(&self.inner, &self.inner.name_map, &mut replay);
1,842✔
1231
        Ok(replay)
1,842✔
1232
    }
1,842✔
1233
}
1234

1235
/// A compile-time symbolic parameter.
1236
///
1237
/// The value of a :class:`.Parameter` must be entirely determined before a circuit begins execution.
1238
/// Typically this will mean that you should supply values for all :class:`.Parameter`\ s in a
1239
/// circuit using :meth:`.QuantumCircuit.assign_parameters`, though certain hardware vendors may
1240
/// allow you to give them a circuit in terms of these parameters, provided you also pass the values
1241
/// separately.
1242
///
1243
/// This is the atom of :class:`.ParameterExpression`, and is itself an expression.  The numeric
1244
/// value of a parameter need not be fixed while the circuit is being defined.
1245
///
1246
/// Examples:
1247
///
1248
///     Construct a variable-rotation X gate using circuit parameters.
1249
///
1250
///     .. plot::
1251
///         :alt: Circuit diagram output by the previous code.
1252
///         :include-source:
1253
///
1254
///         from qiskit.circuit import QuantumCircuit, Parameter
1255
///
1256
///         # create the parameter
1257
///         phi = Parameter("phi")
1258
///         qc = QuantumCircuit(1)
1259
///
1260
///         # parameterize the rotation
1261
///         qc.rx(phi, 0)
1262
///         qc.draw("mpl")
1263
///
1264
///         # bind the parameters after circuit to create a bound circuit
1265
///         bc = qc.assign_parameters({phi: 3.14})
1266
///         bc.measure_all()
1267
///         bc.draw("mpl")
1268
#[pyclass(sequence, subclass, module="qiskit._accelerate.circuit", extends=PyParameterExpression, name="Parameter")]
1269
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)]
1270
pub struct PyParameter {
1271
    symbol: Symbol,
1272
}
1273

1274
impl Hash for PyParameter {
1275
    fn hash<H: Hasher>(&self, state: &mut H) {
821,826✔
1276
        self.symbol.hash(state);
821,826✔
1277
    }
821,826✔
1278
}
1279

1280
// This needs to be implemented manually, since PyO3 does not provide this conversion
1281
// for subclasses.
1282
impl<'py> IntoPyObject<'py> for PyParameter {
1283
    type Target = PyParameter;
1284
    type Output = Bound<'py, Self::Target>;
1285
    type Error = PyErr;
1286

1287
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
2,016✔
1288
        let symbol = &self.symbol;
2,016✔
1289
        let symbol_expr = SymbolExpr::Symbol(symbol.clone());
2,016✔
1290
        let expr = ParameterExpression::from_symbol_expr(symbol_expr);
2,016✔
1291
        let py_expr = PyParameterExpression::from(expr);
2,016✔
1292

1293
        Ok(Py::new(py, (self, py_expr))?.into_bound(py))
2,016✔
1294
    }
2,016✔
1295
}
1296

1297
impl PyParameter {
1298
    /// Get a Python class initialization from a symbol.
1299
    fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
716,262✔
1300
        let expr = SymbolExpr::Symbol(symbol.clone());
716,262✔
1301

1302
        let py_parameter = Self { symbol };
716,262✔
1303
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
716,262✔
1304

1305
        PyClassInitializer::from(py_expr).add_subclass(py_parameter)
716,262✔
1306
    }
716,262✔
1307

1308
    /// Get a reference to the underlying symbol.
1309
    pub fn symbol_ref(&self) -> &Symbol {
171,588✔
1310
        &self.symbol
171,588✔
1311
    }
171,588✔
1312
}
1313

NEW
1314
#[pymethods]
×
1315
impl PyParameter {
1316
    /// Args:
1317
    ///     name: name of the parameter, used for visual representation. This can
1318
    ///         be any Unicode string, e.g. ``"Ï•"``.
1319
    ///     uuid: For advanced usage only.  Override the UUID of this parameter, in order to make it
1320
    ///         compare equal to some other parameter object.  By default, two parameters with the
1321
    ///         same name do not compare equal to help catch shadowing bugs when two circuits
1322
    ///         containing the same named parameters are spurious combined.  Setting the ``uuid``
1323
    ///         field when creating two parameters to the same thing (along with the same name)
1324
    ///         allows them to be equal.  This is useful during serialization and deserialization.
1325
    #[new]
1326
    #[pyo3(signature = (name, uuid=None))]
1327
    fn py_new(
74,674✔
1328
        py: Python<'_>,
74,674✔
1329
        name: String,
74,674✔
1330
        uuid: Option<PyObject>,
74,674✔
1331
    ) -> PyResult<PyClassInitializer<Self>> {
74,674✔
1332
        let uuid = uuid_from_py(py, uuid)?;
74,674✔
1333
        let symbol = Symbol::new(name.as_str(), uuid, None);
74,674✔
1334
        let expr = SymbolExpr::Symbol(symbol.clone());
74,674✔
1335

1336
        let py_parameter = Self { symbol };
74,674✔
1337
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
74,674✔
1338

1339
        Ok(PyClassInitializer::from(py_expr).add_subclass(py_parameter))
74,674✔
1340
    }
74,674✔
1341

1342
    /// Returns the name of the :class:`.Parameter`.
1343
    #[getter]
1344
    fn name<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
85,046✔
1345
        PyString::new(py, &self.symbol.name())
85,046✔
1346
    }
85,046✔
1347

1348
    /// Returns the :class:`~uuid.UUID` of the :class:`Parameter`.
1349
    ///
1350
    /// In advanced use cases, this property can be passed to the
1351
    /// :class:`.Parameter` constructor to produce an instance that compares
1352
    /// equal to another instance.
1353
    #[getter]
1354
    fn uuid(&self, py: Python<'_>) -> PyResult<PyObject> {
1,368✔
1355
        uuid_to_py(py, self.symbol.uuid)
1,368✔
1356
    }
1,368✔
1357

1358
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
68✔
1359
        let str = format!("Parameter({})", self.symbol.name(),);
68✔
1360
        PyString::new(py, str.as_str())
68✔
1361
    }
68✔
1362

1363
    pub fn __getnewargs__(&self) -> (String, u128) {
3,856✔
1364
        (self.symbol.name(), self.symbol.uuid.as_u128())
3,856✔
1365
    }
3,856✔
1366

1367
    pub fn __getstate__(&self) -> (String, u128) {
3,856✔
1368
        (self.symbol.name(), self.symbol.uuid.as_u128())
3,856✔
1369
    }
3,856✔
1370

1371
    pub fn __setstate__(&mut self, state: (String, u128)) {
4✔
1372
        let name = state.0.as_str();
4✔
1373
        let uuid = Uuid::from_u128(state.1);
4✔
1374
        let symbol = Symbol::new(name, Some(uuid), None);
4✔
1375
        self.symbol = symbol;
4✔
1376
    }
4✔
1377

NEW
1378
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1379
        // Parameter is immutable. Note that this **cannot** be deferred to the parent class
1380
        // since PyO3 would then always return the parent type.
NEW
1381
        slf
×
NEW
1382
    }
×
1383

1384
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
8✔
1385
        // Everything inside a Parameter is immutable. Note that this **cannot** be deferred to the
1386
        // parent class since PyO3 would then always return the parent type.
1387
        slf
8✔
1388
    }
8✔
1389

1390
    #[pyo3(name = "subs")]
1391
    #[pyo3(signature = (parameter_map, allow_unknown_parameters=false))]
1392
    pub fn py_subs<'py>(
24,088✔
1393
        &self,
24,088✔
1394
        py: Python<'py>,
24,088✔
1395
        parameter_map: HashMap<PyParameter, Bound<'py, PyAny>>,
24,088✔
1396
        allow_unknown_parameters: bool,
24,088✔
1397
    ) -> PyResult<Bound<'py, PyAny>> {
24,088✔
1398
        // We implement this method on this class, and do not defer to the parent, such
1399
        // that x.subs({x: y}) remains a Parameter, and is not upgraded to an expression.
1400
        // Also this should be faster than going via ParameterExpression, which constructs
1401
        // intermediary HashMaps we don't need here.
1402
        match parameter_map.get(self) {
24,088✔
1403
            None => {
1404
                if allow_unknown_parameters {
4✔
1405
                    self.clone().into_bound_py_any(py)
2✔
1406
                } else {
1407
                    Err(CircuitError::new_err(
2✔
1408
                        "Cannot bind parameters not present in parameter.",
2✔
1409
                    ))
2✔
1410
                }
1411
            }
1412
            Some(replacement) => {
24,084✔
1413
                if allow_unknown_parameters || parameter_map.len() == 1 {
24,084✔
1414
                    Ok(replacement.clone())
24,084✔
1415
                } else {
NEW
1416
                    Err(CircuitError::new_err(
×
NEW
1417
                        "Cannot bind parameters not present in parameter.",
×
NEW
1418
                    ))
×
1419
                }
1420
            }
1421
        }
1422
    }
24,088✔
1423

1424
    #[pyo3(name = "bind")]
1425
    #[pyo3(signature = (parameter_values, allow_unknown_parameters=false))]
1426
    pub fn py_bind<'py>(
152,522✔
1427
        &self,
152,522✔
1428
        py: Python<'py>,
152,522✔
1429
        parameter_values: HashMap<PyParameter, Bound<'py, PyAny>>,
152,522✔
1430
        allow_unknown_parameters: bool,
152,522✔
1431
    ) -> PyResult<Bound<'py, PyAny>> {
152,522✔
1432
        // Returns PyAny to cover Parameter and ParameterExpression(value).
1433
        match parameter_values.get(self) {
152,522✔
1434
            None => {
NEW
1435
                if allow_unknown_parameters {
×
NEW
1436
                    self.clone().into_bound_py_any(py)
×
1437
                } else {
NEW
1438
                    Err(CircuitError::new_err(
×
NEW
1439
                        "Cannot bind parameters not present in parameter.",
×
NEW
1440
                    ))
×
1441
                }
1442
            }
1443
            Some(replacement) => {
152,522✔
1444
                if allow_unknown_parameters || parameter_values.len() == 1 {
152,522✔
1445
                    let expr = PyParameterExpression::extract_coerce(replacement)?;
152,522✔
1446
                    if let SymbolExpr::Value(_) = &expr.inner.expr {
152,522✔
1447
                        expr.clone().into_bound_py_any(py)
152,522✔
1448
                    } else {
NEW
1449
                        Err(PyValueError::new_err("Invalid binding value."))
×
1450
                    }
1451
                } else {
NEW
1452
                    Err(CircuitError::new_err(
×
NEW
1453
                        "Cannot bind parameters not present in parameter.",
×
NEW
1454
                    ))
×
1455
                }
1456
            }
1457
        }
1458
    }
152,522✔
1459

1460
    #[pyo3(name = "assign")]
1461
    pub fn py_assign<'py>(
77,070✔
1462
        &self,
77,070✔
1463
        py: Python<'py>,
77,070✔
1464
        parameter: PyParameter,
77,070✔
1465
        value: &Bound<'py, PyAny>,
77,070✔
1466
    ) -> PyResult<Bound<'py, PyAny>> {
77,070✔
1467
        if value.downcast::<PyParameterExpression>().is_ok() {
77,070✔
1468
            let map = [(parameter, value.clone())].into_iter().collect();
24,080✔
1469
            self.py_subs(py, map, false)
24,080✔
1470
        } else if value.extract::<Value>().is_ok() {
52,990✔
1471
            let map = [(parameter, value.clone())].into_iter().collect();
52,990✔
1472
            self.py_bind(py, map, false)
52,990✔
1473
        } else {
NEW
1474
            Err(PyValueError::new_err(
×
NEW
1475
                "Unexpected value in assign: {replacement:?}",
×
NEW
1476
            ))
×
1477
        }
1478
    }
77,070✔
1479
}
1480

1481
/// An element of a :class:`.ParameterVector`.
1482
///
1483
/// .. note::
1484
///     There is very little reason to ever construct this class directly.  Objects of this type are
1485
///     automatically constructed efficiently as part of creating a :class:`.ParameterVector`.
1486
#[pyclass(sequence, subclass, module="qiskit._accelerate.circuit", extends=PyParameter, name="ParameterVectorElement")]
1487
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
1488
pub struct PyParameterVectorElement {
1489
    symbol: Symbol,
1490
}
1491

1492
impl<'py> IntoPyObject<'py> for PyParameterVectorElement {
1493
    type Target = PyParameterVectorElement;
1494
    type Output = Bound<'py, Self::Target>;
1495
    type Error = PyErr;
1496

1497
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
176✔
1498
        let symbol = &self.symbol;
176✔
1499
        let py_param = PyParameter::from_symbol(symbol.clone());
176✔
1500
        let py_element = py_param.add_subclass(self);
176✔
1501

1502
        Ok(Py::new(py, py_element)?.into_bound(py))
176✔
1503
    }
176✔
1504
}
1505

1506
impl PyParameterVectorElement {
1507
    fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
582,072✔
1508
        let py_element = Self {
582,072✔
1509
            symbol: symbol.clone(),
582,072✔
1510
        };
582,072✔
1511
        let py_parameter = PyParameter::from_symbol(symbol);
582,072✔
1512

1513
        py_parameter.add_subclass(py_element)
582,072✔
1514
    }
582,072✔
1515
}
1516

NEW
1517
#[pymethods]
×
1518
impl PyParameterVectorElement {
1519
    #[new]
1520
    #[pyo3(signature = (vector, index, uuid=None))]
1521
    pub fn py_new(
37,948✔
1522
        py: Python<'_>,
37,948✔
1523
        vector: PyObject,
37,948✔
1524
        index: u32,
37,948✔
1525
        uuid: Option<PyObject>,
37,948✔
1526
    ) -> PyResult<PyClassInitializer<Self>> {
37,948✔
1527
        let vector_name = vector.getattr(py, "name")?.extract::<String>(py)?;
37,948✔
1528
        let uuid = uuid_from_py(py, uuid)?.unwrap_or(Uuid::new_v4());
37,948✔
1529

1530
        let symbol = Symbol::py_new(
37,948✔
1531
            &vector_name,
37,948✔
1532
            Some(uuid.as_u128()),
37,948✔
1533
            Some(index),
37,948✔
1534
            Some(vector.clone_ref(py)),
37,948✔
NEW
1535
        )?;
×
1536

1537
        let py_parameter = PyParameter::from_symbol(symbol.clone());
37,948✔
1538
        let py_element = Self { symbol };
37,948✔
1539

1540
        Ok(py_parameter.add_subclass(py_element))
37,948✔
1541
    }
37,948✔
1542

1543
    pub fn __getnewargs__(&self, py: Python) -> PyResult<(PyObject, u32, Option<PyObject>)> {
8✔
1544
        let vector = self
8✔
1545
            .symbol
8✔
1546
            .vector
8✔
1547
            .clone()
8✔
1548
            .expect("vector element should have a vector");
8✔
1549
        let index = self
8✔
1550
            .symbol
8✔
1551
            .index
8✔
1552
            .expect("vector element should have an index");
8✔
1553
        let uuid = uuid_to_py(py, self.symbol.uuid)?;
8✔
1554
        Ok((vector, index, Some(uuid)))
8✔
1555
    }
8✔
1556

1557
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
48✔
1558
        let str = format!("ParameterVectorElement({})", self.symbol.name(),);
48✔
1559
        PyString::new(py, str.as_str())
48✔
1560
    }
48✔
1561

1562
    pub fn __getstate__(&self, py: Python) -> PyResult<(PyObject, u32, Option<PyObject>)> {
8✔
1563
        self.__getnewargs__(py)
8✔
1564
    }
8✔
1565

NEW
1566
    pub fn __setstate__(
×
NEW
1567
        &mut self,
×
NEW
1568
        py: Python,
×
NEW
1569
        state: (PyObject, u32, Option<PyObject>),
×
NEW
1570
    ) -> PyResult<()> {
×
NEW
1571
        let vector = state.0;
×
NEW
1572
        let index = state.1;
×
NEW
1573
        let vector_name = vector.getattr(py, "name")?.extract::<String>(py)?;
×
NEW
1574
        let uuid = uuid_from_py(py, state.2)?.map(|id| id.as_u128());
×
NEW
1575
        self.symbol = Symbol::py_new(&vector_name, uuid, Some(index), Some(vector))?;
×
NEW
1576
        Ok(())
×
NEW
1577
    }
×
1578

1579
    /// Get the index of this element in the parent vector.
1580
    #[getter]
1581
    pub fn index(&self) -> u32 {
57,638✔
1582
        self.symbol
57,638✔
1583
            .index
57,638✔
1584
            .expect("A vector element should have an index")
57,638✔
1585
    }
57,638✔
1586

1587
    /// Get the parent vector instance.
1588
    #[getter]
1589
    pub fn vector(&self) -> PyObject {
57,970✔
1590
        self.symbol
57,970✔
1591
            .clone()
57,970✔
1592
            .vector
57,970✔
1593
            .expect("A vector element should have a vector")
57,970✔
1594
    }
57,970✔
1595

1596
    /// For backward compatibility only. This should not be used and we ought to update those
1597
    /// usages!
1598
    #[getter]
NEW
1599
    pub fn _vector(&self) -> PyObject {
×
NEW
1600
        self.vector()
×
NEW
1601
    }
×
1602

NEW
1603
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1604
        // ParameterVectorElement is immutable.
NEW
1605
        slf
×
NEW
1606
    }
×
1607

1608
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
8✔
1609
        // Everything a ParameterVectorElement contains is immutable.
1610
        slf
8✔
1611
    }
8✔
1612
}
1613

1614
/// Try to extract a Uuid from a Python object, which could be a Python UUID or int.
1615
fn uuid_from_py(py: Python<'_>, uuid: Option<PyObject>) -> PyResult<Option<Uuid>> {
112,622✔
1616
    if let Some(val) = uuid {
112,622✔
1617
        // construct from u128
1618
        let as_u128 = if let Ok(as_u128) = val.extract::<u128>(py) {
38,118✔
1619
            as_u128
4✔
1620
        // construct from Python UUID type
1621
        } else if val.bind(py).is_exact_instance(UUID.get_bound(py)) {
38,114✔
1622
            val.getattr(py, "int")?.extract::<u128>(py)?
38,114✔
1623
        // invalid format
1624
        } else {
NEW
1625
            return Err(PyTypeError::new_err("not a UUID!"));
×
1626
        };
1627
        Ok(Some(Uuid::from_u128(as_u128)))
38,118✔
1628
    } else {
1629
        Ok(None)
74,504✔
1630
    }
1631
}
112,622✔
1632

1633
/// Convert a Rust Uuid object to a Python UUID object.
1634
fn uuid_to_py(py: Python<'_>, uuid: Uuid) -> PyResult<PyObject> {
1,376✔
1635
    let uuid = uuid.as_u128();
1,376✔
1636
    let kwargs = [("int", uuid)].into_py_dict(py)?;
1,376✔
1637
    Ok(UUID.get_bound(py).call((), Some(&kwargs))?.unbind())
1,376✔
1638
}
1,376✔
1639

1640
/// Extract a [Symbol] for a Python object, which could either be a Parameter or a
1641
/// ParameterVectorElement.
1642
fn symbol_from_py_parameter(param: &Bound<'_, PyAny>) -> PyResult<Symbol> {
60✔
1643
    if let Ok(element) = param.extract::<PyParameterVectorElement>() {
60✔
NEW
1644
        Ok(element.symbol.clone())
×
1645
    } else if let Ok(parameter) = param.extract::<PyParameter>() {
60✔
1646
        Ok(parameter.symbol.clone())
60✔
1647
    } else {
NEW
1648
        Err(PyValueError::new_err("Could not extract parameter"))
×
1649
    }
1650
}
60✔
1651

1652
/// A singular parameter value used for QPY serialization. This covers anything
1653
/// but a [PyParameterExpression], which is represented by [None] in the serialization.
1654
#[derive(IntoPyObject, FromPyObject, Clone, Debug)]
1655
pub enum ParameterValueType {
1656
    Int(i64),
1657
    Float(f64),
1658
    Complex(Complex64),
1659
    Parameter(PyParameter),
1660
    VectorElement(PyParameterVectorElement),
1661
}
1662

1663
impl ParameterValueType {
1664
    fn extract_from_expr(expr: &SymbolExpr) -> Option<ParameterValueType> {
5,046✔
1665
        if let Some(value) = expr.eval(true) {
5,046✔
1666
            match value {
1,800✔
1667
                Value::Int(i) => Some(ParameterValueType::Int(i)),
1,304✔
1668
                Value::Real(r) => Some(ParameterValueType::Float(r)),
488✔
1669
                Value::Complex(c) => Some(ParameterValueType::Complex(c)),
8✔
1670
            }
1671
        } else if let SymbolExpr::Symbol(symbol) = expr {
3,246✔
1672
            match symbol.index {
2,190✔
1673
                None => {
1674
                    let param = PyParameter {
2,014✔
1675
                        symbol: symbol.clone(),
2,014✔
1676
                    };
2,014✔
1677
                    Some(ParameterValueType::Parameter(param))
2,014✔
1678
                }
1679
                Some(_) => {
1680
                    let param = PyParameterVectorElement {
176✔
1681
                        symbol: symbol.clone(),
176✔
1682
                    };
176✔
1683
                    Some(ParameterValueType::VectorElement(param))
176✔
1684
                }
1685
            }
1686
        } else {
1687
            // ParameterExpressions have the value None, as they must be constructed
1688
            None
1,056✔
1689
        }
1690
    }
5,046✔
1691
}
1692

1693
impl From<ParameterValueType> for ParameterExpression {
NEW
1694
    fn from(value: ParameterValueType) -> Self {
×
NEW
1695
        match value {
×
NEW
1696
            ParameterValueType::Parameter(param) => {
×
NEW
1697
                let expr = SymbolExpr::Symbol(param.symbol);
×
NEW
1698
                Self::from_symbol_expr(expr)
×
1699
            }
NEW
1700
            ParameterValueType::VectorElement(param) => {
×
NEW
1701
                let expr = SymbolExpr::Symbol(param.symbol);
×
NEW
1702
                Self::from_symbol_expr(expr)
×
1703
            }
NEW
1704
            ParameterValueType::Int(i) => {
×
NEW
1705
                let expr = SymbolExpr::Value(Value::Int(i));
×
NEW
1706
                Self::from_symbol_expr(expr)
×
1707
            }
NEW
1708
            ParameterValueType::Float(f) => {
×
NEW
1709
                let expr = SymbolExpr::Value(Value::Real(f));
×
NEW
1710
                Self::from_symbol_expr(expr)
×
1711
            }
NEW
1712
            ParameterValueType::Complex(c) => {
×
NEW
1713
                let expr = SymbolExpr::Value(Value::Complex(c));
×
NEW
1714
                Self::from_symbol_expr(expr)
×
1715
            }
1716
        }
NEW
1717
    }
×
1718
}
1719

NEW
1720
#[pyclass(module = "qiskit._accelerate.circuit")]
×
1721
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
1722
#[repr(u8)]
1723
pub enum OpCode {
1724
    ADD = 0,
1725
    SUB = 1,
1726
    MUL = 2,
1727
    DIV = 3,
1728
    POW = 4,
1729
    SIN = 5,
1730
    COS = 6,
1731
    TAN = 7,
1732
    ASIN = 8,
1733
    ACOS = 9,
1734
    EXP = 10,
1735
    LOG = 11,
1736
    SIGN = 12,
1737
    GRAD = 13, // for backward compatibility, unused in Rust's ParameterExpression
1738
    CONJ = 14,
1739
    SUBSTITUTE = 15, // for backward compatibility, unused in Rust's ParameterExpression
1740
    ABS = 16,
1741
    ATAN = 17,
1742
    RSUB = 18,
1743
    RDIV = 19,
1744
    RPOW = 20,
1745
}
1746

1747
impl From<OpCode> for u8 {
NEW
1748
    fn from(value: OpCode) -> Self {
×
NEW
1749
        value as u8
×
NEW
1750
    }
×
1751
}
1752

1753
unsafe impl ::bytemuck::CheckedBitPattern for OpCode {
1754
    type Bits = u8;
1755

1756
    #[inline(always)]
NEW
1757
    fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
×
NEW
1758
        *bits <= 20
×
NEW
1759
    }
×
1760
}
1761

1762
unsafe impl ::bytemuck::NoUninit for OpCode {}
1763

NEW
1764
#[pymethods]
×
1765
impl OpCode {
1766
    #[new]
NEW
1767
    fn py_new(value: u8) -> PyResult<Self> {
×
NEW
1768
        let code: OpCode = ::bytemuck::checked::try_cast(value)
×
NEW
1769
            .map_err(|_| ParameterError::InvalidU8ToOpCode(value))?;
×
NEW
1770
        Ok(code)
×
NEW
1771
    }
×
1772

1773
    fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
560✔
1774
        if let Ok(code) = other.downcast::<OpCode>() {
560✔
1775
            *code.borrow() == *self
560✔
1776
        } else {
NEW
1777
            false
×
1778
        }
1779
    }
560✔
1780

1781
    fn __hash__(&self) -> u8 {
2,088✔
1782
        *self as u8
2,088✔
1783
    }
2,088✔
1784

1785
    fn __getnewargs__(&self) -> (u8,) {
2,268✔
1786
        (*self as u8,)
2,268✔
1787
    }
2,268✔
1788
}
1789

1790
// enum for QPY replay
1791
#[pyclass(sequence, module = "qiskit._accelerate.circuit")]
1792
#[derive(Clone, Debug)]
1793
pub struct OPReplay {
1794
    pub op: OpCode,
1795
    pub lhs: Option<ParameterValueType>,
1796
    pub rhs: Option<ParameterValueType>,
1797
}
1798

NEW
1799
#[pymethods]
×
1800
impl OPReplay {
1801
    #[new]
NEW
1802
    pub fn py_new(
×
NEW
1803
        op: OpCode,
×
NEW
1804
        lhs: Option<ParameterValueType>,
×
NEW
1805
        rhs: Option<ParameterValueType>,
×
NEW
1806
    ) -> OPReplay {
×
NEW
1807
        OPReplay { op, lhs, rhs }
×
NEW
1808
    }
×
1809

1810
    #[getter]
1811
    fn op(&self) -> OpCode {
1,194✔
1812
        self.op
1,194✔
1813
    }
1,194✔
1814

1815
    #[getter]
1816
    fn lhs(&self) -> Option<ParameterValueType> {
630✔
1817
        self.lhs.clone()
630✔
1818
    }
630✔
1819

1820
    #[getter]
1821
    fn rhs(&self) -> Option<ParameterValueType> {
630✔
1822
        self.rhs.clone()
630✔
1823
    }
630✔
1824

1825
    fn __getnewargs__(
2,268✔
1826
        &self,
2,268✔
1827
    ) -> (
2,268✔
1828
        OpCode,
2,268✔
1829
        Option<ParameterValueType>,
2,268✔
1830
        Option<ParameterValueType>,
2,268✔
1831
    ) {
2,268✔
1832
        (self.op, self.lhs.clone(), self.rhs.clone())
2,268✔
1833
    }
2,268✔
1834
}
1835

1836
/// Internal helper. Extract one part of the expression tree, keeping the name map up to date.
1837
///
1838
/// Example: Given expr1 + expr2, each being [PyParameterExpression], we need the ability to
1839
/// extract one of the expressions with the proper name map.
1840
///
1841
/// Args:
1842
///     - joint_parameter_expr: The full expression, e.g. expr1 + expr2.
1843
///     - sub_expr: The sub expression, on whose symbols we restrict the name map.
1844
fn filter_name_map(
5,046✔
1845
    sub_expr: &SymbolExpr,
5,046✔
1846
    name_map: &HashMap<String, Symbol>,
5,046✔
1847
) -> ParameterExpression {
5,046✔
1848
    let sub_symbols = sub_expr.parameters();
5,046✔
1849
    let restricted_name_map: HashMap<String, Symbol> = name_map
5,046✔
1850
        .iter()
5,046✔
1851
        .filter(|(_, symbol)| sub_symbols.contains(*symbol))
6,966✔
1852
        .map(|(name, symbol)| (name.clone(), symbol.clone()))
5,046✔
1853
        .collect();
5,046✔
1854

1855
    ParameterExpression {
5,046✔
1856
        expr: sub_expr.clone(),
5,046✔
1857
        name_map: restricted_name_map,
5,046✔
1858
    }
5,046✔
1859
}
5,046✔
1860

1861
pub fn qpy_replay(
6,888✔
1862
    expr: &ParameterExpression,
6,888✔
1863
    name_map: &HashMap<String, Symbol>,
6,888✔
1864
    replay: &mut Vec<OPReplay>,
6,888✔
1865
) {
6,888✔
1866
    match &expr.expr {
6,888✔
1867
        SymbolExpr::Value(_) | SymbolExpr::Symbol(_) => {
3,990✔
1868
            // nothing to do here, we only need to traverse instructions
3,990✔
1869
        }
3,990✔
1870
        SymbolExpr::Unary { op, expr } => {
750✔
1871
            let op = match op {
750✔
1872
                symbol_expr::UnaryOp::Abs => OpCode::ABS,
6✔
1873
                symbol_expr::UnaryOp::Acos => OpCode::ACOS,
6✔
1874
                symbol_expr::UnaryOp::Asin => OpCode::ASIN,
6✔
1875
                symbol_expr::UnaryOp::Atan => OpCode::ATAN,
6✔
1876
                symbol_expr::UnaryOp::Conj => OpCode::CONJ,
6✔
1877
                symbol_expr::UnaryOp::Cos => OpCode::COS,
6✔
1878
                symbol_expr::UnaryOp::Exp => OpCode::EXP,
6✔
1879
                symbol_expr::UnaryOp::Log => OpCode::LOG,
6✔
1880
                symbol_expr::UnaryOp::Neg => OpCode::MUL,
674✔
1881
                symbol_expr::UnaryOp::Sign => OpCode::SIGN,
4✔
1882
                symbol_expr::UnaryOp::Sin => OpCode::SIN,
18✔
1883
                symbol_expr::UnaryOp::Tan => OpCode::TAN,
6✔
1884
            };
1885
            // TODO filter shouldn't be necessary for unary ops
1886
            let lhs = filter_name_map(expr, name_map);
750✔
1887

1888
            // recurse on the instruction
1889
            qpy_replay(&lhs, name_map, replay);
750✔
1890

1891
            let lhs_value = ParameterValueType::extract_from_expr(expr);
750✔
1892

1893
            // MUL is special: we implement ``neg`` as multiplication by -1
1894
            if let OpCode::MUL = &op {
750✔
1895
                replay.push(OPReplay {
674✔
1896
                    op,
674✔
1897
                    lhs: lhs_value,
674✔
1898
                    rhs: Some(ParameterValueType::Int(-1)),
674✔
1899
                });
674✔
1900
            } else {
674✔
1901
                replay.push(OPReplay {
76✔
1902
                    op,
76✔
1903
                    lhs: lhs_value,
76✔
1904
                    rhs: None,
76✔
1905
                });
76✔
1906
            }
76✔
1907
        }
1908
        SymbolExpr::Binary { op, lhs, rhs } => {
2,148✔
1909
            let lhs_value = ParameterValueType::extract_from_expr(lhs);
2,148✔
1910
            let rhs_value = ParameterValueType::extract_from_expr(rhs);
2,148✔
1911

1912
            // recurse on the parameter expressions
1913
            let lhs = filter_name_map(lhs, name_map);
2,148✔
1914
            let rhs = filter_name_map(rhs, name_map);
2,148✔
1915
            qpy_replay(&lhs, name_map, replay);
2,148✔
1916
            qpy_replay(&rhs, name_map, replay);
2,148✔
1917

1918
            // add the expression to the replay
1919
            match lhs_value {
1,298✔
1920
                None
1921
                | Some(ParameterValueType::Parameter(_))
1922
                | Some(ParameterValueType::VectorElement(_)) => {
1923
                    let op = match op {
1,574✔
1924
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
144✔
1925
                        symbol_expr::BinaryOp::Sub => OpCode::SUB,
160✔
1926
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
62✔
1927
                        symbol_expr::BinaryOp::Div => OpCode::DIV,
1,188✔
1928
                        symbol_expr::BinaryOp::Pow => OpCode::POW,
20✔
1929
                    };
1930
                    replay.push(OPReplay {
1,574✔
1931
                        op,
1,574✔
1932
                        lhs: lhs_value,
1,574✔
1933
                        rhs: rhs_value,
1,574✔
1934
                    });
1,574✔
1935
                }
1936
                _ => {
1937
                    let op = match op {
574✔
1938
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
214✔
1939
                        symbol_expr::BinaryOp::Sub => OpCode::RSUB,
76✔
1940
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
276✔
1941
                        symbol_expr::BinaryOp::Div => OpCode::RDIV,
4✔
1942
                        symbol_expr::BinaryOp::Pow => OpCode::RPOW,
4✔
1943
                    };
1944
                    if let OpCode::ADD | OpCode::MUL = op {
574✔
1945
                        replay.push(OPReplay {
490✔
1946
                            op,
490✔
1947
                            lhs: lhs_value,
490✔
1948
                            rhs: rhs_value,
490✔
1949
                        });
490✔
1950
                    } else {
490✔
1951
                        // this covers RSUB, RDIV, RPOW, hence we swap lhs and rhs
84✔
1952
                        replay.push(OPReplay {
84✔
1953
                            op,
84✔
1954
                            lhs: rhs_value,
84✔
1955
                            rhs: lhs_value,
84✔
1956
                        });
84✔
1957
                    }
84✔
1958
                }
1959
            }
1960
        }
1961
    }
1962
}
6,888✔
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