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

Qiskit / qiskit / 17918849044

22 Sep 2025 02:37PM UTC coverage: 88.295% (+0.02%) from 88.277%
17918849044

Pull #15048

github

web-flow
Merge 7a8321b20 into 6421d7772
Pull Request #15048: Preserve trailing singleton axes in parameter sweeps

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

5 existing lines in 3 files now uncovered.

92744 of 105039 relevant lines covered (88.29%)

934140.58 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,712✔
104
        self.expr.eq(&other.expr)
17,712✔
105
    }
17,712✔
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,123✔
175
        Self {
76,123✔
176
            expr: SymbolExpr::Symbol(Arc::new(symbol.clone())),
76,123✔
177
            name_map: [(symbol.repr(false), symbol)].into(),
76,123✔
178
        }
76,123✔
179
    }
76,123✔
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,530✔
203
        if strict && !self.name_map.is_empty() {
9,639,530✔
204
            let free_symbols = self.expr.iter_symbols().cloned().collect();
72,258✔
205
            return Err(ParameterError::UnboundParameters(free_symbols));
72,258✔
206
        }
9,567,272✔
207

208
        match self.expr.eval(true) {
9,567,272✔
209
            Some(value) => {
401,680✔
210
                // we try to restrict complex to real, if possible
211
                if let Value::Complex(c) = value {
401,680✔
212
                    if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON).contains(&c.im)
38,580✔
213
                    {
214
                        return Ok(Value::Real(c.re));
×
215
                    }
38,580✔
216
                }
363,100✔
217
                Ok(value)
401,680✔
218
            }
219
            None => {
220
                let free_symbols = self.expr.iter_symbols().cloned().collect();
9,165,592✔
221
                Err(ParameterError::UnboundParameters(free_symbols))
9,165,592✔
222
            }
223
        }
224
    }
9,639,530✔
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,036✔
230
        let name_map = expr.name_map();
4,765,036✔
231
        Self { expr, name_map }
4,765,036✔
232
    }
4,765,036✔
233

234
    /// Initialize from an f64.
235
    pub fn from_f64(value: f64) -> Self {
24,694✔
236
        Self {
24,694✔
237
            expr: SymbolExpr::Value(Value::Real(value)),
24,694✔
238
            name_map: HashMap::new(),
24,694✔
239
        }
24,694✔
240
    }
24,694✔
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,716✔
303
        self.name_map.values()
293,716✔
304
    }
293,716✔
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 {
30✔
517
                            return Err(ParameterError::NameConflict);
2✔
518
                        }
28✔
519
                    } else {
38,534✔
520
                        // SAFETY: We know the key does not exist yet.
38,534✔
521
                        unsafe {
38,534✔
522
                            name_map.insert_unique_unchecked(
38,534✔
523
                                replacement_name.clone(),
38,534✔
524
                                replacement_symbol.clone(),
38,534✔
525
                            )
38,534✔
526
                        };
38,534✔
527
                    }
38,534✔
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,458✔
533
            } else {
534
                // no replacement for this symbol, carry on
535
                match name_map.entry(name.clone()) {
205,486✔
536
                    Entry::Occupied(duplicate) => {
24✔
537
                        if duplicate.get() != symbol {
24✔
UNCOV
538
                            return Err(ParameterError::NameConflict);
×
539
                        }
24✔
540
                    }
541
                    Entry::Vacant(e) => {
205,462✔
542
                        e.insert(symbol.clone());
205,462✔
543
                    }
205,462✔
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,766✔
570
        &self,
402,766✔
571
        map: &HashMap<&Symbol, Value>,
402,766✔
572
        allow_unknown_parameters: bool,
402,766✔
573
    ) -> Result<Self, ParameterError> {
402,766✔
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,766✔
576

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

582
            if difference.peek().is_some() {
205,094✔
583
                let different_symbols = difference.map(|s| (**s).clone()).collect();
×
584
                return Err(ParameterError::UnknownParameters(different_symbols));
×
585
            }
205,094✔
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,766✔
590
        let bound = match bound_expr.eval(true) {
402,766✔
591
            Some(v) => match &v {
400,144✔
592
                Value::Real(r) => {
335,500✔
593
                    if r.is_infinite() {
335,500✔
594
                        Err(ParameterError::BindingInf)
1,828✔
595
                    } else if r.is_nan() {
333,672✔
596
                        Err(ParameterError::BindingNaN)
×
597
                    } else {
598
                        Ok(SymbolExpr::Value(v))
333,672✔
599
                    }
600
                }
601
                Value::Int(_) => Ok(SymbolExpr::Value(v)),
24,534✔
602
                Value::Complex(c) => {
40,110✔
603
                    if c.re.is_infinite() || c.im.is_infinite() {
40,110✔
604
                        Err(ParameterError::BindingInf)
×
605
                    } else if c.re.is_nan() || c.im.is_nan() {
40,110✔
606
                        Err(ParameterError::BindingNaN)
×
607
                    } else if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON)
40,110✔
608
                        .contains(&c.im)
40,110✔
609
                    {
610
                        Ok(SymbolExpr::Value(Value::Real(c.re)))
1,728✔
611
                    } else {
612
                        Ok(SymbolExpr::Value(v))
38,382✔
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,938✔
621
            .name_map
400,938✔
622
            .iter()
400,938✔
623
            .filter(|(_, symbol)| !bind_symbols.contains(symbol))
4,699,906✔
624
            .map(|(name, symbol)| (name.clone(), symbol.clone()))
400,938✔
625
            .collect();
400,938✔
626

627
        Ok(Self {
400,938✔
628
            expr: bound,
400,938✔
629
            name_map: bound_name_map,
400,938✔
630
        })
400,938✔
631
    }
402,766✔
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,634✔
693
        Self { inner: value }
12,333,634✔
694
    }
12,333,634✔
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,390✔
712
        if let Ok(i) = ob.downcast::<PyInt>() {
4,960,390✔
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,238✔
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,684✔
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,348✔
731
            Ok(ParameterExpression::from_symbol(element.borrow().symbol.clone()).into())
50,858✔
732
        } else if let Ok(parameter) = ob.downcast::<PyParameter>() {
2,400,490✔
733
            Ok(ParameterExpression::from_symbol(parameter.borrow().symbol.clone()).into())
14,252✔
734
        } else {
735
            ob.extract::<PyParameterExpression>()
2,386,238✔
736
        }
737
    }
4,960,390✔
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,916✔
846
        let py_parameters: Vec<Py<PyAny>> = self
135,916✔
847
            .inner
135,916✔
848
            .name_map
135,916✔
849
            .values()
135,916✔
850
            .map(|symbol| {
4,564,018✔
851
                match (&symbol.index, &symbol.vector) {
4,564,018✔
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,562✔
860
                }
861
            })
4,564,018✔
862
            .collect::<PyResult<_>>()?;
135,916✔
863
        PySet::new(py, py_parameters)
135,916✔
864
    }
135,916✔
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,398✔
1137
        if let Ok(rhs) = Self::extract_coerce(rhs) {
33,398✔
1138
            match rhs.inner.expr {
33,386✔
1139
                SymbolExpr::Value(v) => match self.inner.try_to_value(false) {
2,902✔
1140
                    Ok(e) => Ok(e == v),
2,702✔
1141
                    Err(_) => Ok(false),
200✔
1142
                },
1143
                _ => Ok(self.inner.expr == rhs.inner.expr),
30,484✔
1144
            }
1145
        } else {
1146
            Ok(false)
12✔
1147
        }
1148
    }
33,398✔
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),
23,918✔
1300
            Value::Real(r) => Ok(Complex64::new(r, 0.)),
41,318✔
1301
            Value::Int(i) => Ok(Complex64::new(i as f64, 0.)),
12,618✔
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,770✔
1310
        match self.inner.try_to_value(false) {
9,164,770✔
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,768✔
1322
                self.inner.expr.string_id().hash(&mut hasher);
9,164,768✔
1323
                Ok(hasher.finish())
9,164,768✔
1324
            }
1325
        }
1326
    }
9,164,770✔
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,858✔
1439
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
4,672,858✔
1440

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

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

1447
    /// Get a reference to the underlying symbol.
1448
    pub fn symbol(&self) -> &Symbol {
4,557,320✔
1449
        &self.symbol
4,557,320✔
1450
    }
4,557,320✔
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,038✔
1467
        py: Python<'_>,
83,038✔
1468
        name: String,
83,038✔
1469
        uuid: Option<Py<PyAny>>,
83,038✔
1470
    ) -> PyResult<PyClassInitializer<Self>> {
83,038✔
1471
        let uuid = uuid_from_py(py, uuid)?;
83,038✔
1472
        let symbol = Symbol::new(name.as_str(), uuid, None);
83,038✔
1473
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
83,038✔
1474

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

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

1481
    /// Returns the name of the :class:`.Parameter`.
1482
    #[getter]
1483
    fn name<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
21,362✔
1484
        PyString::new(py, &self.symbol.repr(false))
21,362✔
1485
    }
21,362✔
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,898✔
1768
    if let Some(val) = uuid {
110,898✔
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,868✔
1782
    }
1783
}
110,898✔
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