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

Qiskit / qiskit / 17851777282

19 Sep 2025 07:05AM UTC coverage: 88.289% (-0.005%) from 88.294%
17851777282

push

github

web-flow
Use QSD from rust in default unitary synthesis plugin (#15003)

With QSD ported to rust we no longer need to use python to run QSD in
the default unitary synthesis plugin which is written in rust. This
commit updates the unitary synthesis code to directly call qsd from
rust. Doing this also enables the C API transpiler from handling 3+ q
unitaries. The handling around multiqubit unitaries is updated to
indicate they are now supported.

9 of 14 new or added lines in 1 file covered. (64.29%)

11 existing lines in 4 files now uncovered.

92767 of 105072 relevant lines covered (88.29%)

869706.53 hits per line

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

82.79
/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,478✔
65
        match value {
3,478✔
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()),
582✔
76
            ParameterError::InvalidValue => PyValueError::new_err(value.to_string()),
40✔
77
            _ => PyRuntimeError::new_err(value.to_string()),
4✔
78
        }
79
    }
3,478✔
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,896✔
104
        self.expr.eq(&other.expr)
17,896✔
105
    }
17,896✔
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,816✔
122
        write!(f, "{}", {
336,816✔
123
            if let SymbolExpr::Symbol(s) = &self.expr {
336,816✔
124
                s.repr(false)
192,758✔
125
            } else {
126
                match self.expr.eval(true) {
144,058✔
127
                    Some(e) => e.to_string(),
184✔
128
                    None => self.expr.to_string(),
143,874✔
129
                }
130
            }
131
        })
132
    }
336,816✔
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> {
×
146
        let expr = PyParameterExpression::from(self.clone());
×
147
        expr.into_pyobject(py)
×
148
    }
×
149
}
150

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

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

173
    /// Construct from a [Symbol].
174
    pub fn from_symbol(symbol: Symbol) -> Self {
76,117✔
175
        Self {
76,117✔
176
            expr: SymbolExpr::Symbol(Arc::new(symbol.clone())),
76,117✔
177
            name_map: [(symbol.repr(false), symbol)].into(),
76,117✔
178
        }
76,117✔
179
    }
76,117✔
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> {
248,956✔
185
        if let SymbolExpr::Symbol(symbol) = &self.expr {
248,956✔
186
            Ok(symbol.as_ref().clone())
239,362✔
187
        } else {
188
            Err(ParameterError::NotASymbol)
9,594✔
189
        }
190
    }
248,956✔
191

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

208
        match self.expr.eval(true) {
9,567,080✔
209
            Some(value) => {
401,510✔
210
                // we try to restrict complex to real, if possible
211
                if let Value::Complex(c) = value {
401,510✔
212
                    if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON).contains(&c.im)
38,960✔
213
                    {
214
                        return Ok(Value::Real(c.re));
×
215
                    }
38,960✔
216
                }
362,550✔
217
                Ok(value)
401,510✔
218
            }
219
            None => {
220
                let free_symbols = self.expr.iter_symbols().cloned().collect();
9,165,570✔
221
                Err(ParameterError::UnboundParameters(free_symbols))
9,165,570✔
222
            }
223
        }
224
    }
9,639,338✔
225

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

588
        // bind the symbol expression and then check the outcome for inf/nan, or numeric values
589
        let bound_expr = self.expr.bind(map);
402,760✔
590
        let bound = match bound_expr.eval(true) {
402,760✔
591
            Some(v) => match &v {
400,138✔
592
                Value::Real(r) => {
335,754✔
593
                    if r.is_infinite() {
335,754✔
594
                        Err(ParameterError::BindingInf)
1,828✔
595
                    } else if r.is_nan() {
333,926✔
596
                        Err(ParameterError::BindingNaN)
×
597
                    } else {
598
                        Ok(SymbolExpr::Value(v))
333,926✔
599
                    }
600
                }
601
                Value::Int(_) => Ok(SymbolExpr::Value(v)),
23,894✔
602
                Value::Complex(c) => {
40,490✔
603
                    if c.re.is_infinite() || c.im.is_infinite() {
40,490✔
604
                        Err(ParameterError::BindingInf)
×
605
                    } else if c.re.is_nan() || c.im.is_nan() {
40,490✔
606
                        Err(ParameterError::BindingNaN)
×
607
                    } else if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON)
40,490✔
608
                        .contains(&c.im)
40,490✔
609
                    {
610
                        Ok(SymbolExpr::Value(Value::Real(c.re)))
1,728✔
611
                    } else {
612
                        Ok(SymbolExpr::Value(v))
38,762✔
613
                    }
614
                }
615
            },
616
            None => Ok(bound_expr),
2,622✔
617
        }?;
1,828✔
618

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

627
        Ok(Self {
400,932✔
628
            expr: bound,
400,932✔
629
            name_map: bound_name_map,
400,932✔
630
        })
400,932✔
631
    }
402,760✔
632

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

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

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

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

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

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

739
    pub fn coerce_into_py(&self, py: Python) -> PyResult<Py<PyAny>> {
32,358✔
740
        if let Ok(value) = self.inner.try_to_value(true) {
32,358✔
741
            match value {
14✔
742
                Value::Int(i) => Ok(PyInt::new(py, i).unbind().into_any()),
×
743
                Value::Real(r) => Ok(PyFloat::new(py, r).unbind().into_any()),
14✔
744
                Value::Complex(c) => Ok(PyComplex::from_complex_bound(py, c).unbind().into_any()),
×
745
            }
746
        } else if let Ok(symbol) = self.inner.try_to_symbol() {
32,344✔
747
            if symbol.index.is_some() {
22,750✔
748
                Ok(Py::new(py, PyParameterVectorElement::from_symbol(symbol))?.into_any())
5,960✔
749
            } else {
750
                Ok(Py::new(py, PyParameter::from_symbol(symbol))?.into_any())
16,790✔
751
            }
752
        } else {
753
            self.clone().into_py_any(py)
9,594✔
754
        }
755
    }
32,358✔
756
}
757

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

950
    /// Return derivative of this expression with respect to the input parameter.
951
    ///
952
    /// Args:
953
    ///     param: The parameter with respect to which the derivative is calculated.
954
    ///
955
    /// Returns:
956
    ///     The derivative as either a constant numeric value or a symbolic
957
    ///     :class:`.ParameterExpression`.
958
    pub fn gradient(&self, param: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
74✔
959
        let symbol = symbol_from_py_parameter(param)?;
74✔
960
        let d_expr = self.inner.derivative(&symbol)?;
74✔
961

962
        // try converting to value and return as built-in numeric type
963
        match d_expr.try_to_value(false) {
70✔
964
            Ok(val) => match val {
20✔
965
                Value::Real(r) => r.into_py_any(param.py()),
18✔
966
                Value::Int(i) => i.into_py_any(param.py()),
2✔
967
                Value::Complex(c) => c.into_py_any(param.py()),
×
968
            },
969
            Err(_) => PyParameterExpression::from(d_expr).into_py_any(param.py()),
50✔
970
        }
971
    }
74✔
972

973
    /// Return all values in this equation.
974
    pub fn _values(&self, py: Python) -> PyResult<Vec<Py<PyAny>>> {
78✔
975
        self.inner
78✔
976
            .expr
78✔
977
            .values()
78✔
978
            .iter()
78✔
979
            .map(|val| match val {
78✔
980
                Value::Real(r) => r.into_py_any(py),
34✔
981
                Value::Int(i) => i.into_py_any(py),
10✔
982
                Value::Complex(c) => c.into_py_any(py),
×
983
            })
44✔
984
            .collect()
78✔
985
    }
78✔
986

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

1017
        // apply to the inner expression
1018
        match self.inner.subs(&map, allow_unknown_parameters) {
228✔
1019
            Ok(subbed) => Ok(subbed.into()),
224✔
1020
            Err(e) => Err(e.into()),
4✔
1021
        }
1022
    }
228✔
1023

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

1059
        // apply to the inner expression
1060
        match self.inner.bind(&map, allow_unknown_parameters) {
127,040✔
1061
            Ok(bound) => Ok(bound.into()),
125,212✔
1062
            Err(e) => Err(e.into()),
1,828✔
1063
        }
1064
    }
127,040✔
1065

1066
    /// Bind all of the parameters in ``self`` to numeric values in the dictionary, returning a
1067
    /// numeric value.
1068
    ///
1069
    /// This is a special case of :meth:`bind` which can reach higher performance.  It is no problem
1070
    /// for the ``values`` dictionary to contain parameters that are not used in this expression;
1071
    /// the expectation is that the same bindings dictionary will be fed to other expressions as
1072
    /// well.
1073
    ///
1074
    /// It is an error to call this method with a ``values`` dictionary that does not bind all of
1075
    /// the values, or to call this method with non-numeric values, but this is not explicitly
1076
    /// checked, since this method is intended for performance-sensitive use.  Passing an incorrect
1077
    /// dictionary may result in unexpected behavior.
1078
    ///
1079
    /// Unlike :meth:`bind`, this method will not raise an exception if non-finite floating-point
1080
    /// values are encountered.
1081
    ///
1082
    /// Args:
1083
    ///     values: mapping of parameters to numeric values.
1084
    #[pyo3(name = "bind_all")]
1085
    #[pyo3(signature = (values, *))]
1086
    pub fn py_bind_all(&self, values: Bound<PyAny>) -> PyResult<Value> {
4✔
1087
        let mut partial_map = HashMap::with_capacity(self.inner.name_map.len());
4✔
1088
        for symbol in self.inner.name_map.values() {
8✔
1089
            let py_parameter = symbol.clone().into_pyobject(values.py())?;
8✔
1090
            partial_map.insert(symbol, values.get_item(py_parameter)?.extract()?);
8✔
1091
        }
1092
        let bound = self.inner.expr.bind(&partial_map);
2✔
1093
        bound.eval(true).ok_or_else(|| {
2✔
1094
            PyTypeError::new_err(format!(
×
1095
                "binding did not produce a numeric quantity: {bound:?}"
×
1096
            ))
1097
        })
×
1098
    }
4✔
1099

1100
    /// Assign one parameter to a value, which can either be numeric or another parameter
1101
    /// expression.
1102
    ///
1103
    /// Args:
1104
    ///     parameter: A parameter in this expression whose value will be updated.
1105
    ///     value: The new value to bind to.
1106
    ///
1107
    /// Returns:
1108
    ///     A new expression parameterized by any parameters which were not bound by assignment.
1109
    #[pyo3(name = "assign")]
1110
    pub fn py_assign(&self, parameter: PyParameter, value: &Bound<PyAny>) -> PyResult<Self> {
174✔
1111
        if let Ok(expr) = value.downcast::<Self>() {
174✔
1112
            let map = [(parameter, expr.borrow().clone())].into_iter().collect();
150✔
1113
            self.py_subs(map, false)
150✔
1114
        } else if value.extract::<Value>().is_ok() {
24✔
1115
            let map = [(parameter, value.clone())].into_iter().collect();
24✔
1116
            self.py_bind(map, false)
24✔
1117
        } else {
1118
            Err(PyValueError::new_err(
×
1119
                "Unexpected value in assign: {replacement:?}",
×
1120
            ))
×
1121
        }
1122
    }
174✔
1123

1124
    #[inline]
1125
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1126
        // ParameterExpression is immutable.
1127
        slf
×
1128
    }
×
1129

1130
    #[inline]
1131
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
518✔
1132
        // Everything a ParameterExpression contains is immutable.
1133
        slf
518✔
1134
    }
518✔
1135

1136
    pub fn __eq__(&self, rhs: &Bound<PyAny>) -> PyResult<bool> {
33,392✔
1137
        if let Ok(rhs) = Self::extract_coerce(rhs) {
33,392✔
1138
            match rhs.inner.expr {
33,380✔
1139
                SymbolExpr::Value(v) => match self.inner.try_to_value(false) {
2,738✔
1140
                    Ok(e) => Ok(e == v),
2,538✔
1141
                    Err(_) => Ok(false),
200✔
1142
                },
1143
                _ => Ok(self.inner.expr == rhs.inner.expr),
30,642✔
1144
            }
1145
        } else {
1146
            Ok(false)
12✔
1147
        }
1148
    }
33,392✔
1149

1150
    #[inline]
1151
    pub fn __abs__(&self) -> Self {
362✔
1152
        self.inner.abs().into()
362✔
1153
    }
362✔
1154

1155
    #[inline]
1156
    pub fn __pos__(&self) -> Self {
4✔
1157
        self.clone()
4✔
1158
    }
4✔
1159

1160
    pub fn __neg__(&self) -> Self {
484✔
1161
        Self {
484✔
1162
            inner: ParameterExpression::new(-&self.inner.expr, self.inner.name_map.clone()),
484✔
1163
        }
484✔
1164
    }
484✔
1165

1166
    pub fn __add__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
2,318,300✔
1167
        if let Ok(rhs) = Self::extract_coerce(rhs) {
2,318,300✔
1168
            Ok(self.inner.add(&rhs.inner)?.into())
2,318,288✔
1169
        } else {
1170
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1171
                "Unsupported data type for __add__",
12✔
1172
            ))
12✔
1173
        }
1174
    }
2,318,300✔
1175

1176
    pub fn __radd__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
64,000✔
1177
        if let Ok(lhs) = Self::extract_coerce(lhs) {
64,000✔
1178
            Ok(lhs.inner.add(&self.inner)?.into())
63,988✔
1179
        } else {
1180
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1181
                "Unsupported data type for __radd__",
12✔
1182
            ))
12✔
1183
        }
1184
    }
64,000✔
1185

1186
    pub fn __sub__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
18,462✔
1187
        if let Ok(rhs) = Self::extract_coerce(rhs) {
18,462✔
1188
            Ok(self.inner.sub(&rhs.inner)?.into())
18,450✔
1189
        } else {
1190
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1191
                "Unsupported data type for __sub__",
12✔
1192
            ))
12✔
1193
        }
1194
    }
18,462✔
1195

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

1206
    pub fn __mul__<'py>(&self, rhs: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
2,408,100✔
1207
        let py = rhs.py();
2,408,100✔
1208
        if let Ok(rhs) = Self::extract_coerce(rhs) {
2,408,100✔
1209
            match self.inner.mul(&rhs.inner) {
2,400,744✔
1210
                Ok(result) => PyParameterExpression::from(result).into_bound_py_any(py),
2,400,742✔
1211
                Err(e) => Err(PyErr::from(e)),
2✔
1212
            }
1213
        } else {
1214
            PyNotImplemented::get(py).into_bound_py_any(py)
7,356✔
1215
        }
1216
    }
2,408,100✔
1217

1218
    pub fn __rmul__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
12,064✔
1219
        if let Ok(lhs) = Self::extract_coerce(lhs) {
12,064✔
1220
            Ok(lhs.inner.mul(&self.inner)?.into())
12,052✔
1221
        } else {
1222
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1223
                "Unsupported data type for __rmul__",
12✔
1224
            ))
12✔
1225
        }
1226
    }
12,064✔
1227

1228
    pub fn __truediv__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
8,530✔
1229
        if let Ok(rhs) = Self::extract_coerce(rhs) {
8,530✔
1230
            Ok(self.inner.div(&rhs.inner)?.into())
8,518✔
1231
        } else {
1232
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1233
                "Unsupported data type for __truediv__",
12✔
1234
            ))
12✔
1235
        }
1236
    }
8,530✔
1237

1238
    pub fn __rtruediv__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
4,070✔
1239
        if let Ok(lhs) = Self::extract_coerce(lhs) {
4,070✔
1240
            Ok(lhs.inner.div(&self.inner)?.into())
4,058✔
1241
        } else {
1242
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1243
                "Unsupported data type for __rtruediv__",
12✔
1244
            ))
12✔
1245
        }
1246
    }
4,070✔
1247

1248
    pub fn __pow__(&self, rhs: &Bound<PyAny>, _modulo: Option<i32>) -> PyResult<Self> {
1,998✔
1249
        if let Ok(rhs) = Self::extract_coerce(rhs) {
1,998✔
1250
            Ok(self.inner.pow(&rhs.inner)?.into())
1,986✔
1251
        } else {
1252
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1253
                "Unsupported data type for __pow__",
12✔
1254
            ))
12✔
1255
        }
1256
    }
1,998✔
1257

1258
    pub fn __rpow__(&self, lhs: &Bound<PyAny>, _modulo: Option<i32>) -> PyResult<Self> {
1,042✔
1259
        if let Ok(lhs) = Self::extract_coerce(lhs) {
1,042✔
1260
            Ok(lhs.inner.pow(&self.inner)?.into())
1,030✔
1261
        } else {
1262
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1263
                "Unsupported data type for __rpow__",
12✔
1264
            ))
12✔
1265
        }
1266
    }
1,042✔
1267

1268
    pub fn __int__(&self) -> PyResult<i64> {
176✔
1269
        match self.inner.try_to_value(false)? {
176✔
1270
            Value::Complex(_) => Err(PyTypeError::new_err(
42✔
1271
                "Cannot cast complex parameter to int.",
42✔
1272
            )),
42✔
1273
            Value::Real(r) => {
90✔
1274
                let rounded = r.floor();
90✔
1275
                Ok(rounded as i64)
90✔
1276
            }
1277
            Value::Int(i) => Ok(i),
42✔
1278
        }
1279
    }
176✔
1280

1281
    pub fn __float__(&self) -> PyResult<f64> {
672✔
1282
        match self.inner.try_to_value(false)? {
672✔
1283
            Value::Complex(c) => {
44✔
1284
                if c.im.abs() > SYMEXPR_EPSILON {
44✔
1285
                    Err(PyTypeError::new_err(
44✔
1286
                        "Could not cast complex parameter expression to float.",
44✔
1287
                    ))
44✔
1288
                } else {
1289
                    Ok(c.re)
×
1290
                }
1291
            }
1292
            Value::Real(r) => Ok(r),
114✔
1293
            Value::Int(i) => Ok(i as f64),
56✔
1294
        }
1295
    }
672✔
1296

1297
    pub fn __complex__(&self) -> PyResult<Complex64> {
77,856✔
1298
        match self.inner.try_to_value(false)? {
77,856✔
1299
            Value::Complex(c) => Ok(c),
24,298✔
1300
            Value::Real(r) => Ok(Complex64::new(r, 0.)),
41,586✔
1301
            Value::Int(i) => Ok(Complex64::new(i as f64, 0.)),
11,970✔
1302
        }
1303
    }
77,856✔
1304

1305
    pub fn __str__(&self) -> String {
336,816✔
1306
        self.to_string()
336,816✔
1307
    }
336,816✔
1308

1309
    pub fn __hash__(&self, py: Python) -> PyResult<u64> {
9,164,748✔
1310
        match self.inner.try_to_value(false) {
9,164,748✔
1311
            // if a value, we promise to match the hash of the raw value!
1312
            Ok(value) => {
2✔
1313
                let py_hash = BUILTIN_HASH.get_bound(py);
2✔
1314
                match value {
2✔
1315
                    Value::Complex(c) => py_hash.call1((c,))?.extract::<u64>(),
×
1316
                    Value::Real(r) => py_hash.call1((r,))?.extract::<u64>(),
2✔
1317
                    Value::Int(i) => py_hash.call1((i,))?.extract::<u64>(),
×
1318
                }
1319
            }
1320
            Err(_) => {
1321
                let mut hasher = DefaultHasher::new();
9,164,746✔
1322
                self.inner.expr.string_id().hash(&mut hasher);
9,164,746✔
1323
                Ok(hasher.finish())
9,164,746✔
1324
            }
1325
        }
1326
    }
9,164,748✔
1327

1328
    fn __getstate__(&self) -> PyResult<(Vec<OPReplay>, Option<ParameterValueType>)> {
7,884✔
1329
        // We distinguish in two cases:
1330
        //  (a) This is indeed an expression which can be rebuild from the QPY replay. This means
1331
        //      the replay is *not empty* and it contains all symbols.
1332
        //  (b) This expression is in fact only a Value or a Symbol. In this case, the QPY replay
1333
        //      will be empty and we instead pass a `ParameterValueType` for reconstruction.
1334
        let qpy = self._qpy_replay()?;
7,884✔
1335
        if !qpy.is_empty() {
7,884✔
1336
            Ok((qpy, None))
7,884✔
1337
        } else {
1338
            let value = ParameterValueType::extract_from_expr(&self.inner.expr);
×
1339
            if value.is_none() {
×
1340
                Err(PyValueError::new_err(format!(
×
1341
                    "Failed to serialize the parameter expression: {self:?}"
×
1342
                )))
×
1343
            } else {
1344
                Ok((qpy, value))
×
1345
            }
1346
        }
1347
    }
7,884✔
1348

1349
    fn __setstate__(&mut self, state: (Vec<OPReplay>, Option<ParameterValueType>)) -> PyResult<()> {
×
1350
        // if there a replay, load from the replay
1351
        if !state.0.is_empty() {
×
1352
            let from_qpy = ParameterExpression::from_qpy(&state.0)?;
×
1353
            self.inner = from_qpy;
×
1354
        // otherwise, load from the ParameterValueType
1355
        } else if let Some(value) = state.1 {
×
1356
            let expr = ParameterExpression::from(value);
×
1357
            self.inner = expr;
×
1358
        } else {
×
1359
            return Err(PyValueError::new_err(
×
1360
                "Failed to read QPY replay or extract value.",
×
1361
            ));
×
1362
        }
1363
        Ok(())
×
1364
    }
×
1365

1366
    #[getter]
1367
    fn _qpy_replay(&self) -> PyResult<Vec<OPReplay>> {
8,186✔
1368
        let mut replay = Vec::new();
8,186✔
1369
        qpy_replay(&self.inner, &self.inner.name_map, &mut replay);
8,186✔
1370
        Ok(replay)
8,186✔
1371
    }
8,186✔
1372
}
1373

1374
/// A compile-time symbolic parameter.
1375
///
1376
/// The value of a :class:`.Parameter` must be entirely determined before a circuit begins execution.
1377
/// Typically this will mean that you should supply values for all :class:`.Parameter`\ s in a
1378
/// circuit using :meth:`.QuantumCircuit.assign_parameters`, though certain hardware vendors may
1379
/// allow you to give them a circuit in terms of these parameters, provided you also pass the values
1380
/// separately.
1381
///
1382
/// This is the atom of :class:`.ParameterExpression`, and is itself an expression.  The numeric
1383
/// value of a parameter need not be fixed while the circuit is being defined.
1384
///
1385
/// Examples:
1386
///
1387
///     Construct a variable-rotation X gate using circuit parameters.
1388
///
1389
///     .. plot::
1390
///         :alt: Circuit diagram output by the previous code.
1391
///         :include-source:
1392
///
1393
///         from qiskit.circuit import QuantumCircuit, Parameter
1394
///
1395
///         # create the parameter
1396
///         phi = Parameter("phi")
1397
///         qc = QuantumCircuit(1)
1398
///
1399
///         # parameterize the rotation
1400
///         qc.rx(phi, 0)
1401
///         qc.draw("mpl")
1402
///
1403
///         # bind the parameters after circuit to create a bound circuit
1404
///         bc = qc.assign_parameters({phi: 3.14})
1405
///         bc.measure_all()
1406
///         bc.draw("mpl")
1407
#[pyclass(sequence, subclass, module="qiskit._accelerate.circuit", extends=PyParameterExpression, name="Parameter")]
1408
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)]
1409
pub struct PyParameter {
1410
    symbol: Symbol,
1411
}
1412

1413
impl Hash for PyParameter {
1414
    fn hash<H: Hasher>(&self, state: &mut H) {
4,556,372✔
1415
        self.symbol.hash(state);
4,556,372✔
1416
    }
4,556,372✔
1417
}
1418

1419
// This needs to be implemented manually, since PyO3 does not provide this conversion
1420
// for subclasses.
1421
impl<'py> IntoPyObject<'py> for PyParameter {
1422
    type Target = PyParameter;
1423
    type Output = Bound<'py, Self::Target>;
1424
    type Error = PyErr;
1425

1426
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
9,136✔
1427
        let symbol = &self.symbol;
9,136✔
1428
        let symbol_expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
9,136✔
1429
        let expr = ParameterExpression::from_symbol_expr(symbol_expr);
9,136✔
1430
        let py_expr = PyParameterExpression::from(expr);
9,136✔
1431

1432
        Ok(Py::new(py, (self, py_expr))?.into_bound(py))
9,136✔
1433
    }
9,136✔
1434
}
1435

1436
impl PyParameter {
1437
    /// Get a Python class initialization from a symbol.
1438
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
4,672,840✔
1439
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
4,672,840✔
1440

1441
        let py_parameter = Self { symbol };
4,672,840✔
1442
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
4,672,840✔
1443

1444
        PyClassInitializer::from(py_expr).add_subclass(py_parameter)
4,672,840✔
1445
    }
4,672,840✔
1446

1447
    /// Get a reference to the underlying symbol.
1448
    pub fn symbol(&self) -> &Symbol {
4,557,314✔
1449
        &self.symbol
4,557,314✔
1450
    }
4,557,314✔
1451
}
1452

1453
#[pymethods]
×
1454
impl PyParameter {
1455
    /// Args:
1456
    ///     name: name of the parameter, used for visual representation. This can
1457
    ///         be any Unicode string, e.g. ``"Ï•"``.
1458
    ///     uuid: For advanced usage only.  Override the UUID of this parameter, in order to make it
1459
    ///         compare equal to some other parameter object.  By default, two parameters with the
1460
    ///         same name do not compare equal to help catch shadowing bugs when two circuits
1461
    ///         containing the same named parameters are spurious combined.  Setting the ``uuid``
1462
    ///         field when creating two parameters to the same thing (along with the same name)
1463
    ///         allows them to be equal.  This is useful during serialization and deserialization.
1464
    #[new]
1465
    #[pyo3(signature = (name, uuid=None))]
1466
    fn py_new(
83,026✔
1467
        py: Python<'_>,
83,026✔
1468
        name: String,
83,026✔
1469
        uuid: Option<Py<PyAny>>,
83,026✔
1470
    ) -> PyResult<PyClassInitializer<Self>> {
83,026✔
1471
        let uuid = uuid_from_py(py, uuid)?;
83,026✔
1472
        let symbol = Symbol::new(name.as_str(), uuid, None);
83,026✔
1473
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
83,026✔
1474

1475
        let py_parameter = Self { symbol };
83,026✔
1476
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
83,026✔
1477

1478
        Ok(PyClassInitializer::from(py_expr).add_subclass(py_parameter))
83,026✔
1479
    }
83,026✔
1480

1481
    /// Returns the name of the :class:`.Parameter`.
1482
    #[getter]
1483
    fn name<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
21,344✔
1484
        PyString::new(py, &self.symbol.repr(false))
21,344✔
1485
    }
21,344✔
1486

1487
    /// Returns the :class:`~uuid.UUID` of the :class:`Parameter`.
1488
    ///
1489
    /// In advanced use cases, this property can be passed to the
1490
    /// :class:`.Parameter` constructor to produce an instance that compares
1491
    /// equal to another instance.
1492
    #[getter]
1493
    fn uuid(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
1,368✔
1494
        uuid_to_py(py, self.symbol.uuid)
1,368✔
1495
    }
1,368✔
1496

1497
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
64✔
1498
        let str = format!("Parameter({})", self.symbol.repr(false),);
64✔
1499
        PyString::new(py, str.as_str())
64✔
1500
    }
64✔
1501

1502
    pub fn __getnewargs__(&self) -> (String, u128) {
24,132✔
1503
        (self.symbol.repr(false), self.symbol.uuid.as_u128())
24,132✔
1504
    }
24,132✔
1505

1506
    pub fn __getstate__(&self) -> (String, u128) {
24,132✔
1507
        (self.symbol.repr(false), self.symbol.uuid.as_u128())
24,132✔
1508
    }
24,132✔
1509

1510
    pub fn __setstate__(&mut self, state: (String, u128)) {
4✔
1511
        let name = state.0.as_str();
4✔
1512
        let uuid = Uuid::from_u128(state.1);
4✔
1513
        let symbol = Symbol::new(name, Some(uuid), None);
4✔
1514
        self.symbol = symbol;
4✔
1515
    }
4✔
1516

1517
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1518
        // Parameter is immutable. Note that this **cannot** be deferred to the parent class
1519
        // since PyO3 would then always return the parent type.
1520
        slf
×
1521
    }
×
1522

1523
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
18✔
1524
        // Everything inside a Parameter is immutable. Note that this **cannot** be deferred to the
1525
        // parent class since PyO3 would then always return the parent type.
1526
        slf
18✔
1527
    }
18✔
1528

1529
    #[pyo3(name = "subs")]
1530
    #[pyo3(signature = (parameter_map, allow_unknown_parameters=false))]
1531
    pub fn py_subs<'py>(
416✔
1532
        &self,
416✔
1533
        py: Python<'py>,
416✔
1534
        parameter_map: HashMap<PyParameter, Bound<'py, PyAny>>,
416✔
1535
        allow_unknown_parameters: bool,
416✔
1536
    ) -> PyResult<Bound<'py, PyAny>> {
416✔
1537
        // We implement this method on this class, and do not defer to the parent, such
1538
        // that x.subs({x: y}) remains a Parameter, and is not upgraded to an expression.
1539
        // Also this should be faster than going via ParameterExpression, which constructs
1540
        // intermediary HashMaps we don't need here.
1541
        match parameter_map.get(self) {
416✔
1542
            None => {
1543
                if allow_unknown_parameters {
4✔
1544
                    self.clone().into_bound_py_any(py)
2✔
1545
                } else {
1546
                    Err(CircuitError::new_err(
2✔
1547
                        "Cannot bind parameters not present in parameter.",
2✔
1548
                    ))
2✔
1549
                }
1550
            }
1551
            Some(replacement) => {
412✔
1552
                if allow_unknown_parameters || parameter_map.len() == 1 {
412✔
1553
                    Ok(replacement.clone())
412✔
1554
                } else {
1555
                    Err(CircuitError::new_err(
×
1556
                        "Cannot bind parameters not present in parameter.",
×
1557
                    ))
×
1558
                }
1559
            }
1560
        }
1561
    }
416✔
1562

1563
    #[pyo3(name = "bind")]
1564
    #[pyo3(signature = (parameter_values, allow_unknown_parameters=false))]
1565
    pub fn py_bind<'py>(
146✔
1566
        &self,
146✔
1567
        py: Python<'py>,
146✔
1568
        parameter_values: HashMap<PyParameter, Bound<'py, PyAny>>,
146✔
1569
        allow_unknown_parameters: bool,
146✔
1570
    ) -> PyResult<Bound<'py, PyAny>> {
146✔
1571
        // Returns PyAny to cover Parameter and ParameterExpression(value).
1572
        match parameter_values.get(self) {
146✔
1573
            None => {
1574
                if allow_unknown_parameters {
×
1575
                    self.clone().into_bound_py_any(py)
×
1576
                } else {
1577
                    Err(CircuitError::new_err(
×
1578
                        "Cannot bind parameters not present in parameter.",
×
1579
                    ))
×
1580
                }
1581
            }
1582
            Some(replacement) => {
146✔
1583
                if allow_unknown_parameters || parameter_values.len() == 1 {
146✔
1584
                    let expr = PyParameterExpression::extract_coerce(replacement)?;
146✔
1585
                    if let SymbolExpr::Value(_) = &expr.inner.expr {
146✔
1586
                        expr.clone().into_bound_py_any(py)
146✔
1587
                    } else {
1588
                        Err(PyValueError::new_err("Invalid binding value."))
×
1589
                    }
1590
                } else {
1591
                    Err(CircuitError::new_err(
×
1592
                        "Cannot bind parameters not present in parameter.",
×
1593
                    ))
×
1594
                }
1595
            }
1596
        }
1597
    }
146✔
1598

1599
    #[pyo3(name = "bind_all")]
1600
    #[pyo3(signature = (values, *))]
1601
    pub fn py_bind_all<'py>(
4✔
1602
        slf_: Bound<'py, Self>,
4✔
1603
        values: Bound<'py, PyAny>,
4✔
1604
    ) -> PyResult<Bound<'py, PyAny>> {
4✔
1605
        values.get_item(slf_)
4✔
1606
    }
4✔
1607

1608
    #[pyo3(name = "assign")]
1609
    pub fn py_assign<'py>(
408✔
1610
        &self,
408✔
1611
        py: Python<'py>,
408✔
1612
        parameter: PyParameter,
408✔
1613
        value: &Bound<'py, PyAny>,
408✔
1614
    ) -> PyResult<Bound<'py, PyAny>> {
408✔
1615
        if value.downcast::<PyParameterExpression>().is_ok() {
408✔
1616
            let map = [(parameter, value.clone())].into_iter().collect();
408✔
1617
            self.py_subs(py, map, false)
408✔
1618
        } else if value.extract::<Value>().is_ok() {
×
1619
            let map = [(parameter, value.clone())].into_iter().collect();
×
1620
            self.py_bind(py, map, false)
×
1621
        } else {
1622
            Err(PyValueError::new_err(
×
1623
                "Unexpected value in assign: {replacement:?}",
×
1624
            ))
×
1625
        }
1626
    }
408✔
1627
}
1628

1629
/// An element of a :class:`.ParameterVector`.
1630
///
1631
/// .. note::
1632
///     There is very little reason to ever construct this class directly.  Objects of this type are
1633
///     automatically constructed efficiently as part of creating a :class:`.ParameterVector`.
1634
#[pyclass(sequence, subclass, module="qiskit._accelerate.circuit", extends=PyParameter, name="ParameterVectorElement")]
1635
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
1636
pub struct PyParameterVectorElement {
1637
    symbol: Symbol,
1638
}
1639

1640
impl<'py> IntoPyObject<'py> for PyParameterVectorElement {
1641
    type Target = PyParameterVectorElement;
1642
    type Output = Bound<'py, Self::Target>;
1643
    type Error = PyErr;
1644

1645
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
176✔
1646
        let symbol = &self.symbol;
176✔
1647
        let py_param = PyParameter::from_symbol(symbol.clone());
176✔
1648
        let py_element = py_param.add_subclass(self);
176✔
1649

1650
        Ok(Py::new(py, py_element)?.into_bound(py))
176✔
1651
    }
176✔
1652
}
1653

1654
impl PyParameterVectorElement {
1655
    pub fn symbol(&self) -> &Symbol {
16,460✔
1656
        &self.symbol
16,460✔
1657
    }
16,460✔
1658

1659
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
4,556,088✔
1660
        let py_element = Self {
4,556,088✔
1661
            symbol: symbol.clone(),
4,556,088✔
1662
        };
4,556,088✔
1663
        let py_parameter = PyParameter::from_symbol(symbol);
4,556,088✔
1664

1665
        py_parameter.add_subclass(py_element)
4,556,088✔
1666
    }
4,556,088✔
1667
}
1668

1669
#[pymethods]
×
1670
impl PyParameterVectorElement {
1671
    #[new]
1672
    #[pyo3(signature = (vector, index, uuid=None))]
1673
    pub fn py_new(
27,860✔
1674
        py: Python<'_>,
27,860✔
1675
        vector: Py<PyAny>,
27,860✔
1676
        index: u32,
27,860✔
1677
        uuid: Option<Py<PyAny>>,
27,860✔
1678
    ) -> PyResult<PyClassInitializer<Self>> {
27,860✔
1679
        let vector_name = vector.getattr(py, "name")?.extract::<String>(py)?;
27,860✔
1680
        let uuid = uuid_from_py(py, uuid)?.unwrap_or(Uuid::new_v4());
27,860✔
1681

1682
        let symbol = Symbol::py_new(
27,860✔
1683
            &vector_name,
27,860✔
1684
            Some(uuid.as_u128()),
27,860✔
1685
            Some(index),
27,860✔
1686
            Some(vector.clone_ref(py)),
27,860✔
1687
        )?;
×
1688

1689
        let py_parameter = PyParameter::from_symbol(symbol.clone());
27,860✔
1690
        let py_element = Self { symbol };
27,860✔
1691

1692
        Ok(py_parameter.add_subclass(py_element))
27,860✔
1693
    }
27,860✔
1694

1695
    pub fn __getnewargs__(&self, py: Python) -> PyResult<(Py<PyAny>, u32, Option<Py<PyAny>>)> {
8✔
1696
        let vector = self
8✔
1697
            .symbol
8✔
1698
            .vector
8✔
1699
            .clone()
8✔
1700
            .expect("vector element should have a vector");
8✔
1701
        let index = self
8✔
1702
            .symbol
8✔
1703
            .index
8✔
1704
            .expect("vector element should have an index");
8✔
1705
        let uuid = uuid_to_py(py, self.symbol.uuid)?;
8✔
1706
        Ok((vector, index, Some(uuid)))
8✔
1707
    }
8✔
1708

1709
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
48✔
1710
        let str = format!("ParameterVectorElement({})", self.symbol.repr(false),);
48✔
1711
        PyString::new(py, str.as_str())
48✔
1712
    }
48✔
1713

1714
    pub fn __getstate__(&self, py: Python) -> PyResult<(Py<PyAny>, u32, Option<Py<PyAny>>)> {
8✔
1715
        self.__getnewargs__(py)
8✔
1716
    }
8✔
1717

1718
    pub fn __setstate__(
×
1719
        &mut self,
×
1720
        py: Python,
×
1721
        state: (Py<PyAny>, u32, Option<Py<PyAny>>),
×
1722
    ) -> PyResult<()> {
×
1723
        let vector = state.0;
×
1724
        let index = state.1;
×
1725
        let vector_name = vector.getattr(py, "name")?.extract::<String>(py)?;
×
1726
        let uuid = uuid_from_py(py, state.2)?.map(|id| id.as_u128());
×
1727
        self.symbol = Symbol::py_new(&vector_name, uuid, Some(index), Some(vector))?;
×
1728
        Ok(())
×
1729
    }
×
1730

1731
    /// Get the index of this element in the parent vector.
1732
    #[getter]
1733
    pub fn index(&self) -> u32 {
336✔
1734
        self.symbol
336✔
1735
            .index
336✔
1736
            .expect("A vector element should have an index")
336✔
1737
    }
336✔
1738

1739
    /// Get the parent vector instance.
1740
    #[getter]
1741
    pub fn vector(&self) -> Py<PyAny> {
668✔
1742
        self.symbol
668✔
1743
            .clone()
668✔
1744
            .vector
668✔
1745
            .expect("A vector element should have a vector")
668✔
1746
    }
668✔
1747

1748
    /// For backward compatibility only. This should not be used and we ought to update those
1749
    /// usages!
1750
    #[getter]
1751
    pub fn _vector(&self) -> Py<PyAny> {
×
1752
        self.vector()
×
1753
    }
×
1754

1755
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1756
        // ParameterVectorElement is immutable.
1757
        slf
×
1758
    }
×
1759

1760
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
208✔
1761
        // Everything a ParameterVectorElement contains is immutable.
1762
        slf
208✔
1763
    }
208✔
1764
}
1765

1766
/// Try to extract a Uuid from a Python object, which could be a Python UUID or int.
1767
fn uuid_from_py(py: Python<'_>, uuid: Option<Py<PyAny>>) -> PyResult<Option<Uuid>> {
110,886✔
1768
    if let Some(val) = uuid {
110,886✔
1769
        // construct from u128
1770
        let as_u128 = if let Ok(as_u128) = val.extract::<u128>(py) {
28,030✔
1771
            as_u128
4✔
1772
        // construct from Python UUID type
1773
        } else if val.bind(py).is_exact_instance(UUID.get_bound(py)) {
28,026✔
1774
            val.getattr(py, "int")?.extract::<u128>(py)?
28,026✔
1775
        // invalid format
1776
        } else {
1777
            return Err(PyTypeError::new_err("not a UUID!"));
×
1778
        };
1779
        Ok(Some(Uuid::from_u128(as_u128)))
28,030✔
1780
    } else {
1781
        Ok(None)
82,856✔
1782
    }
1783
}
110,886✔
1784

1785
/// Convert a Rust Uuid object to a Python UUID object.
1786
fn uuid_to_py(py: Python<'_>, uuid: Uuid) -> PyResult<Py<PyAny>> {
1,376✔
1787
    let uuid = uuid.as_u128();
1,376✔
1788
    let kwargs = [("int", uuid)].into_py_dict(py)?;
1,376✔
1789
    Ok(UUID.get_bound(py).call((), Some(&kwargs))?.unbind())
1,376✔
1790
}
1,376✔
1791

1792
/// Extract a [Symbol] for a Python object, which could either be a Parameter or a
1793
/// ParameterVectorElement.
1794
fn symbol_from_py_parameter(param: &Bound<'_, PyAny>) -> PyResult<Symbol> {
74✔
1795
    if let Ok(element) = param.extract::<PyParameterVectorElement>() {
74✔
1796
        Ok(element.symbol.clone())
×
1797
    } else if let Ok(parameter) = param.extract::<PyParameter>() {
74✔
1798
        Ok(parameter.symbol.clone())
74✔
1799
    } else {
1800
        Err(PyValueError::new_err("Could not extract parameter"))
×
1801
    }
1802
}
74✔
1803

1804
/// A singular parameter value used for QPY serialization. This covers anything
1805
/// but a [PyParameterExpression], which is represented by [None] in the serialization.
1806
#[derive(IntoPyObject, FromPyObject, Clone, Debug)]
1807
pub enum ParameterValueType {
1808
    Int(i64),
1809
    Float(f64),
1810
    Complex(Complex64),
1811
    Parameter(PyParameter),
1812
    VectorElement(PyParameterVectorElement),
1813
}
1814

1815
impl ParameterValueType {
1816
    fn extract_from_expr(expr: &SymbolExpr) -> Option<ParameterValueType> {
18,190✔
1817
        if let Some(value) = expr.eval(true) {
18,190✔
1818
            match value {
7,472✔
1819
                Value::Int(i) => Some(ParameterValueType::Int(i)),
100✔
1820
                Value::Real(r) => Some(ParameterValueType::Float(r)),
7,364✔
1821
                Value::Complex(c) => Some(ParameterValueType::Complex(c)),
8✔
1822
            }
1823
        } else if let SymbolExpr::Symbol(symbol) = expr {
10,718✔
1824
            match symbol.index {
9,310✔
1825
                None => {
1826
                    let param = PyParameter {
9,134✔
1827
                        symbol: symbol.as_ref().clone(),
9,134✔
1828
                    };
9,134✔
1829
                    Some(ParameterValueType::Parameter(param))
9,134✔
1830
                }
1831
                Some(_) => {
1832
                    let param = PyParameterVectorElement {
176✔
1833
                        symbol: symbol.as_ref().clone(),
176✔
1834
                    };
176✔
1835
                    Some(ParameterValueType::VectorElement(param))
176✔
1836
                }
1837
            }
1838
        } else {
1839
            // ParameterExpressions have the value None, as they must be constructed
1840
            None
1,408✔
1841
        }
1842
    }
18,190✔
1843
}
1844

1845
impl From<ParameterValueType> for ParameterExpression {
1846
    fn from(value: ParameterValueType) -> Self {
×
1847
        match value {
×
1848
            ParameterValueType::Parameter(param) => {
×
1849
                let expr = SymbolExpr::Symbol(Arc::new(param.symbol));
×
1850
                Self::from_symbol_expr(expr)
×
1851
            }
1852
            ParameterValueType::VectorElement(param) => {
×
1853
                let expr = SymbolExpr::Symbol(Arc::new(param.symbol));
×
1854
                Self::from_symbol_expr(expr)
×
1855
            }
1856
            ParameterValueType::Int(i) => {
×
1857
                let expr = SymbolExpr::Value(Value::Int(i));
×
1858
                Self::from_symbol_expr(expr)
×
1859
            }
1860
            ParameterValueType::Float(f) => {
×
1861
                let expr = SymbolExpr::Value(Value::Real(f));
×
1862
                Self::from_symbol_expr(expr)
×
1863
            }
1864
            ParameterValueType::Complex(c) => {
×
1865
                let expr = SymbolExpr::Value(Value::Complex(c));
×
1866
                Self::from_symbol_expr(expr)
×
1867
            }
1868
        }
1869
    }
×
1870
}
1871

1872
#[pyclass(module = "qiskit._accelerate.circuit")]
×
1873
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
1874
#[repr(u8)]
1875
pub enum OpCode {
1876
    ADD = 0,
1877
    SUB = 1,
1878
    MUL = 2,
1879
    DIV = 3,
1880
    POW = 4,
1881
    SIN = 5,
1882
    COS = 6,
1883
    TAN = 7,
1884
    ASIN = 8,
1885
    ACOS = 9,
1886
    EXP = 10,
1887
    LOG = 11,
1888
    SIGN = 12,
1889
    GRAD = 13, // for backward compatibility, unused in Rust's ParameterExpression
1890
    CONJ = 14,
1891
    SUBSTITUTE = 15, // for backward compatibility, unused in Rust's ParameterExpression
1892
    ABS = 16,
1893
    ATAN = 17,
1894
    RSUB = 18,
1895
    RDIV = 19,
1896
    RPOW = 20,
1897
}
1898

1899
impl From<OpCode> for u8 {
1900
    fn from(value: OpCode) -> Self {
×
1901
        value as u8
×
1902
    }
×
1903
}
1904

1905
unsafe impl ::bytemuck::CheckedBitPattern for OpCode {
1906
    type Bits = u8;
1907

1908
    #[inline(always)]
1909
    fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
×
1910
        *bits <= 20
×
1911
    }
×
1912
}
1913

1914
unsafe impl ::bytemuck::NoUninit for OpCode {}
1915

1916
#[pymethods]
×
1917
impl OpCode {
1918
    #[new]
1919
    fn py_new(value: u8) -> PyResult<Self> {
×
1920
        let code: OpCode = ::bytemuck::checked::try_cast(value)
×
1921
            .map_err(|_| ParameterError::InvalidU8ToOpCode(value))?;
×
1922
        Ok(code)
×
1923
    }
×
1924

1925
    fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
560✔
1926
        if let Ok(code) = other.downcast::<OpCode>() {
560✔
1927
            *code.borrow() == *self
560✔
1928
        } else {
1929
            false
×
1930
        }
1931
    }
560✔
1932

1933
    fn __hash__(&self) -> u8 {
2,088✔
1934
        *self as u8
2,088✔
1935
    }
2,088✔
1936

1937
    fn __getnewargs__(&self) -> (u8,) {
8,964✔
1938
        (*self as u8,)
8,964✔
1939
    }
8,964✔
1940
}
1941

1942
// enum for QPY replay
1943
#[pyclass(sequence, module = "qiskit._accelerate.circuit")]
1944
#[derive(Clone, Debug)]
1945
pub struct OPReplay {
1946
    pub op: OpCode,
1947
    pub lhs: Option<ParameterValueType>,
1948
    pub rhs: Option<ParameterValueType>,
1949
}
1950

1951
#[pymethods]
×
1952
impl OPReplay {
1953
    #[new]
1954
    pub fn py_new(
×
1955
        op: OpCode,
×
1956
        lhs: Option<ParameterValueType>,
×
1957
        rhs: Option<ParameterValueType>,
×
1958
    ) -> OPReplay {
×
1959
        OPReplay { op, lhs, rhs }
×
1960
    }
×
1961

1962
    #[getter]
1963
    fn op(&self) -> OpCode {
1,194✔
1964
        self.op
1,194✔
1965
    }
1,194✔
1966

1967
    #[getter]
1968
    fn lhs(&self) -> Option<ParameterValueType> {
630✔
1969
        self.lhs.clone()
630✔
1970
    }
630✔
1971

1972
    #[getter]
1973
    fn rhs(&self) -> Option<ParameterValueType> {
630✔
1974
        self.rhs.clone()
630✔
1975
    }
630✔
1976

1977
    fn __getnewargs__(
8,964✔
1978
        &self,
8,964✔
1979
    ) -> (
8,964✔
1980
        OpCode,
8,964✔
1981
        Option<ParameterValueType>,
8,964✔
1982
        Option<ParameterValueType>,
8,964✔
1983
    ) {
8,964✔
1984
        (self.op, self.lhs.clone(), self.rhs.clone())
8,964✔
1985
    }
8,964✔
1986
}
1987

1988
/// Internal helper. Extract one part of the expression tree, keeping the name map up to date.
1989
///
1990
/// Example: Given expr1 + expr2, each being [PyParameterExpression], we need the ability to
1991
/// extract one of the expressions with the proper name map.
1992
///
1993
/// Args:
1994
///     - joint_parameter_expr: The full expression, e.g. expr1 + expr2.
1995
///     - sub_expr: The sub expression, on whose symbols we restrict the name map.
1996
fn filter_name_map(
18,190✔
1997
    sub_expr: &SymbolExpr,
18,190✔
1998
    name_map: &HashMap<String, Symbol>,
18,190✔
1999
) -> ParameterExpression {
18,190✔
2000
    let sub_symbols: HashSet<&Symbol> = sub_expr.iter_symbols().collect();
18,190✔
2001
    let restricted_name_map: HashMap<String, Symbol> = name_map
18,190✔
2002
        .iter()
18,190✔
2003
        .filter(|(_, symbol)| sub_symbols.contains(*symbol))
23,318✔
2004
        .map(|(name, symbol)| (name.clone(), symbol.clone()))
18,190✔
2005
        .collect();
18,190✔
2006

2007
    ParameterExpression {
18,190✔
2008
        expr: sub_expr.clone(),
18,190✔
2009
        name_map: restricted_name_map,
18,190✔
2010
    }
18,190✔
2011
}
18,190✔
2012

2013
pub fn qpy_replay(
26,376✔
2014
    expr: &ParameterExpression,
26,376✔
2015
    name_map: &HashMap<String, Symbol>,
26,376✔
2016
    replay: &mut Vec<OPReplay>,
26,376✔
2017
) {
26,376✔
2018
    match &expr.expr {
26,376✔
2019
        SymbolExpr::Value(_) | SymbolExpr::Symbol(_) => {
16,782✔
2020
            // nothing to do here, we only need to traverse instructions
16,782✔
2021
        }
16,782✔
2022
        SymbolExpr::Unary { op, expr } => {
998✔
2023
            let op = match op {
998✔
2024
                symbol_expr::UnaryOp::Abs => OpCode::ABS,
6✔
2025
                symbol_expr::UnaryOp::Acos => OpCode::ACOS,
6✔
2026
                symbol_expr::UnaryOp::Asin => OpCode::ASIN,
6✔
2027
                symbol_expr::UnaryOp::Atan => OpCode::ATAN,
6✔
2028
                symbol_expr::UnaryOp::Conj => OpCode::CONJ,
6✔
2029
                symbol_expr::UnaryOp::Cos => OpCode::COS,
6✔
2030
                symbol_expr::UnaryOp::Exp => OpCode::EXP,
6✔
2031
                symbol_expr::UnaryOp::Log => OpCode::LOG,
6✔
2032
                symbol_expr::UnaryOp::Neg => OpCode::MUL,
922✔
2033
                symbol_expr::UnaryOp::Sign => OpCode::SIGN,
4✔
2034
                symbol_expr::UnaryOp::Sin => OpCode::SIN,
18✔
2035
                symbol_expr::UnaryOp::Tan => OpCode::TAN,
6✔
2036
            };
2037
            // TODO filter shouldn't be necessary for unary ops
2038
            let lhs = filter_name_map(expr, name_map);
998✔
2039

2040
            // recurse on the instruction
2041
            qpy_replay(&lhs, name_map, replay);
998✔
2042

2043
            let lhs_value = ParameterValueType::extract_from_expr(expr);
998✔
2044

2045
            // MUL is special: we implement ``neg`` as multiplication by -1
2046
            if let OpCode::MUL = &op {
998✔
2047
                replay.push(OPReplay {
922✔
2048
                    op,
922✔
2049
                    lhs: lhs_value,
922✔
2050
                    rhs: Some(ParameterValueType::Int(-1)),
922✔
2051
                });
922✔
2052
            } else {
922✔
2053
                replay.push(OPReplay {
76✔
2054
                    op,
76✔
2055
                    lhs: lhs_value,
76✔
2056
                    rhs: None,
76✔
2057
                });
76✔
2058
            }
76✔
2059
        }
2060
        SymbolExpr::Binary { op, lhs, rhs } => {
8,596✔
2061
            let lhs_value = ParameterValueType::extract_from_expr(lhs);
8,596✔
2062
            let rhs_value = ParameterValueType::extract_from_expr(rhs);
8,596✔
2063

2064
            // recurse on the parameter expressions
2065
            let lhs = filter_name_map(lhs, name_map);
8,596✔
2066
            let rhs = filter_name_map(rhs, name_map);
8,596✔
2067
            qpy_replay(&lhs, name_map, replay);
8,596✔
2068
            qpy_replay(&rhs, name_map, replay);
8,596✔
2069

2070
            // add the expression to the replay
2071
            match lhs_value {
8,342✔
2072
                None
2073
                | Some(ParameterValueType::Parameter(_))
2074
                | Some(ParameterValueType::VectorElement(_)) => {
2075
                    let op = match op {
1,278✔
2076
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
744✔
2077
                        symbol_expr::BinaryOp::Sub => OpCode::SUB,
416✔
2078
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
86✔
2079
                        symbol_expr::BinaryOp::Div => OpCode::DIV,
12✔
2080
                        symbol_expr::BinaryOp::Pow => OpCode::POW,
20✔
2081
                    };
2082
                    replay.push(OPReplay {
1,278✔
2083
                        op,
1,278✔
2084
                        lhs: lhs_value,
1,278✔
2085
                        rhs: rhs_value,
1,278✔
2086
                    });
1,278✔
2087
                }
2088
                _ => {
2089
                    let op = match op {
7,318✔
2090
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
578✔
2091
                        symbol_expr::BinaryOp::Sub => OpCode::RSUB,
120✔
2092
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
6,612✔
2093
                        symbol_expr::BinaryOp::Div => OpCode::RDIV,
4✔
2094
                        symbol_expr::BinaryOp::Pow => OpCode::RPOW,
4✔
2095
                    };
2096
                    if let OpCode::ADD | OpCode::MUL = op {
7,318✔
2097
                        replay.push(OPReplay {
7,190✔
2098
                            op,
7,190✔
2099
                            lhs: lhs_value,
7,190✔
2100
                            rhs: rhs_value,
7,190✔
2101
                        });
7,190✔
2102
                    } else {
7,190✔
2103
                        // this covers RSUB, RDIV, RPOW, hence we swap lhs and rhs
128✔
2104
                        replay.push(OPReplay {
128✔
2105
                            op,
128✔
2106
                            lhs: rhs_value,
128✔
2107
                            rhs: lhs_value,
128✔
2108
                        });
128✔
2109
                    }
128✔
2110
                }
2111
            }
2112
        }
2113
    }
2114
}
26,376✔
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