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

Qiskit / qiskit / 21716415231

05 Feb 2026 02:31PM UTC coverage: 87.967% (-0.005%) from 87.972%
21716415231

push

github

web-flow
Specify precise file dependencies in `Makefile` (#14504)

* Specify precise file dependencies in `Makefile`

This refactors the C components of the Makefile significantly to better
isolate three types of logic:

- what files actually need to be built and output
- how to build each file
- where each file should be "installed" for the output distribution

This is motivated because the `Makefile` was already missing several
include files and directories that needed to be copied over or created,
and we intend to make further changes to the C library distribution
logic in the future.

When the `include/qiskit/` distribution subdirectory was added, the rules
that construct it and the contained files were appended to existing
rules for `qiskit.h`, which meant that make did not understand the true
dependency chain, and so did not reliably consider the sources dirty
because it didn't know about them. This meant that it might not create
necessary intermediate states, and might not update changed files in a
pre-built directory.

We also remove the top-level `CMakeLists.txt` file because the top-level
of the repository doesn't actually use CMake at all; we previously only
used it to feed in the otherwise out-of-tree locations of the Qiskit
library and include files.  This moves all the CMake logic into `test/c`
as an isolated project, and has the Makefile (which is what actually
arranges for the files to exist in the first place) fill in the search
locations for them.

I used the LLM below to help remind me of GNU Make options and built-in
variables.

AI disclosure: IBM Bob 0.0.14 with claude-3-5-sonnet-20241022

* Move C coverage logic into `Makefile`

The logic of running the C test suite with coverage enabled was
duplicated between the `Makefile` and the Coverage runner, making it
harder to keep in sync.  This makes sure they'll be unified.

* Correct newly non-nested output directory

* Avoid comments within recipes

* Add `clib-dev` recipe

This allows build... (continued)

99964 of 113638 relevant lines covered (87.97%)

1159837.06 hits per line

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

87.17
/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 https://www.apache.org/licenses/LICENSE-2.0.
8
//
9
// Any modifications or derivative works of this code must retain this
10
// copyright notice, and modified files need to carry a notice indicating
11
// that they have been altered from the originals.
12

13
use hashbrown::hash_map::Entry;
14
use hashbrown::{HashMap, HashSet};
15
use num_complex::Complex64;
16
use pyo3::exceptions::{PyRuntimeError, PyTypeError, PyValueError, PyZeroDivisionError};
17
use pyo3::types::{IntoPyDict, PyComplex, PyFloat, PyInt, PyNotImplemented, PySet, PyString};
18
use std::sync::Arc;
19
use thiserror::Error;
20
use uuid::Uuid;
21

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

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

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

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

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

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

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

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

101
impl PartialEq for ParameterExpression {
102
    fn eq(&self, other: &Self) -> bool {
1,400✔
103
        self.expr.eq(&other.expr)
1,400✔
104
    }
1,400✔
105
}
106

107
impl Eq for ParameterExpression {}
108

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

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

134
impl ParameterExpression {
135
    pub fn qpy_replay(&self) -> Vec<OPReplay> {
184✔
136
        let mut replay = Vec::new();
184✔
137
        qpy_replay(self, &self.name_map, &mut replay);
184✔
138
        replay
184✔
139
    }
184✔
140
}
141
// This needs to be implemented manually, because PyO3 does not provide built-in
142
// conversions for the subclasses of ParameterExpression in Python. Specifically
143
// the Python classes Parameter and ParameterVector are subclasses of
144
// ParameterExpression and the default trait impl would not handle the specialization
145
// there.
146
impl<'py> IntoPyObject<'py> for ParameterExpression {
147
    type Target = PyParameterExpression;
148
    type Output = Bound<'py, Self::Target>;
149
    type Error = PyErr;
150

151
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
10✔
152
        let expr = PyParameterExpression::from(self.clone());
10✔
153
        expr.into_pyobject(py)
10✔
154
    }
10✔
155
}
156

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

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

179
    /// Construct from a [Symbol].
180
    pub fn from_symbol(symbol: Symbol) -> Self {
78,015✔
181
        Self {
78,015✔
182
            expr: SymbolExpr::Symbol(Arc::new(symbol.clone())),
78,015✔
183
            name_map: [(symbol.repr(false), symbol)].into(),
78,015✔
184
        }
78,015✔
185
    }
78,015✔
186

187
    /// Try casting to a [Symbol].
188
    ///
189
    /// This only succeeds if the underlying expression is, in fact, only a symbol.
190
    pub fn try_to_symbol(&self) -> Result<Symbol, ParameterError> {
274,838✔
191
        if let SymbolExpr::Symbol(symbol) = &self.expr {
274,838✔
192
            Ok(symbol.as_ref().clone())
264,728✔
193
        } else {
194
            Err(ParameterError::NotASymbol)
10,110✔
195
        }
196
    }
274,838✔
197

198
    /// Try casting to a [Symbol], returning a reference.
199
    ///
200
    /// This only succeeds if the underlying expression is, in fact, only a symbol.
201
    pub fn try_to_symbol_ref(&self) -> Result<&Symbol, ParameterError> {
×
202
        if let SymbolExpr::Symbol(symbol) = &self.expr {
×
203
            Ok(symbol.as_ref())
×
204
        } else {
205
            Err(ParameterError::NotASymbol)
×
206
        }
207
    }
×
208

209
    /// Try casting to a [Value].
210
    ///
211
    /// Attempt to evaluate the expression recursively and return a [Value] if fully bound.
212
    ///
213
    /// # Arguments
214
    ///
215
    /// * strict - If ``true``, only allow returning a value if all symbols are bound. If
216
    ///   ``false``, allow casting expressions to values, even though symbols might still exist.
217
    ///   For example, ``0 * x`` will return ``0`` for ``strict=false`` and otherwise return
218
    ///   an error.
219
    pub fn try_to_value(&self, strict: bool) -> Result<Value, ParameterError> {
9,652,054✔
220
        if strict && !self.name_map.is_empty() {
9,652,054✔
221
            let free_symbols = self.expr.iter_symbols().cloned().collect();
73,790✔
222
            return Err(ParameterError::UnboundParameters(free_symbols));
73,790✔
223
        }
9,578,264✔
224

225
        match self.expr.eval(true) {
9,578,264✔
226
            Some(value) => {
412,992✔
227
                // we try to restrict complex to real, if possible
228
                if let Value::Complex(c) = value {
412,992✔
229
                    if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON).contains(&c.im)
41,958✔
230
                    {
231
                        return Ok(Value::Real(c.re));
×
232
                    }
41,958✔
233
                }
371,034✔
234
                Ok(value)
412,992✔
235
            }
236
            None => {
237
                let free_symbols = self.expr.iter_symbols().cloned().collect();
9,165,272✔
238
                Err(ParameterError::UnboundParameters(free_symbols))
9,165,272✔
239
            }
240
        }
241
    }
9,652,054✔
242

243
    /// Construct from a [SymbolExpr].
244
    ///
245
    /// This populates the name map with the symbols in the expression.
246
    pub fn from_symbol_expr(expr: SymbolExpr) -> Self {
4,767,566✔
247
        let name_map = expr.name_map();
4,767,566✔
248
        Self { expr, name_map }
4,767,566✔
249
    }
4,767,566✔
250

251
    /// Initialize from an f64.
252
    pub fn from_f64(value: f64) -> Self {
11,886✔
253
        Self {
11,886✔
254
            expr: SymbolExpr::Value(Value::Real(value)),
11,886✔
255
            name_map: HashMap::new(),
11,886✔
256
        }
11,886✔
257
    }
11,886✔
258

259
    /// Load from a sequence of [OPReplay]s. Used in serialization.
260
    pub fn from_qpy(
180✔
261
        replay: &[OPReplay],
180✔
262
        subs_operations: Option<Vec<(usize, HashMap<Symbol, ParameterExpression>)>>,
180✔
263
    ) -> Result<Self, ParameterError> {
180✔
264
        // the stack contains the latest lhs and rhs values
265
        let mut stack: Vec<ParameterExpression> = Vec::new();
180✔
266
        let subs_operations = subs_operations.unwrap_or_default();
180✔
267
        let mut current_sub_operation = subs_operations.len(); // we avoid using a queue since we only make one pass anyway
180✔
268
        for (i, inst) in replay.iter().enumerate() {
388✔
269
            let OPReplay { op, lhs, rhs } = inst;
388✔
270

271
            // put the values on the stack, if they exist
272
            if let Some(value) = lhs {
388✔
273
                stack.push(value.clone().into());
274✔
274
            }
274✔
275
            if let Some(value) = rhs {
388✔
276
                stack.push(value.clone().into());
270✔
277
            }
270✔
278

279
            // if we need two operands, pop rhs from the stack
280
            let rhs = if BINARY_OPS.contains(op) {
388✔
281
                Some(stack.pop().expect("Pop from empty stack"))
364✔
282
            } else {
283
                None
24✔
284
            };
285

286
            // pop lhs from the stack, this we always need
287
            let lhs = stack.pop().expect("Pop from empty stack");
388✔
288

289
            // apply the operation and put the result onto the stack for the next replay
290
            let result: ParameterExpression = match op {
388✔
291
                OpCode::ADD => lhs.add(&rhs.unwrap())?,
104✔
292
                OpCode::MUL => lhs.mul(&rhs.unwrap())?,
180✔
293
                OpCode::SUB => lhs.sub(&rhs.unwrap())?,
8✔
294
                OpCode::RSUB => rhs.unwrap().sub(&lhs)?,
48✔
295
                OpCode::POW => lhs.pow(&rhs.unwrap())?,
8✔
296
                OpCode::RPOW => rhs.unwrap().pow(&lhs)?,
4✔
297
                OpCode::DIV => lhs.div(&rhs.unwrap())?,
8✔
298
                OpCode::RDIV => rhs.unwrap().div(&lhs)?,
4✔
299
                OpCode::ABS => lhs.abs(),
2✔
300
                OpCode::SIN => lhs.sin(),
6✔
301
                OpCode::ASIN => lhs.asin(),
2✔
302
                OpCode::COS => lhs.cos(),
2✔
303
                OpCode::ACOS => lhs.acos(),
2✔
304
                OpCode::TAN => lhs.tan(),
2✔
305
                OpCode::ATAN => lhs.atan(),
2✔
306
                OpCode::CONJ => lhs.conjugate(),
2✔
307
                OpCode::LOG => lhs.log(),
2✔
308
                OpCode::EXP => lhs.exp(),
2✔
309
                OpCode::SIGN => lhs.sign(),
×
310
                OpCode::GRAD | OpCode::SUBSTITUTE => {
311
                    panic!("GRAD and SUBSTITUTE are not supported.")
×
312
                }
313
            };
314
            stack.push(result);
388✔
315
            //now check whether any substitutions need to be applied at this stage
316
            while current_sub_operation > 0 && subs_operations[current_sub_operation - 1].0 == i + 1
388✔
317
            {
318
                if let Some(exp) = stack.pop() {
×
319
                    let sub_exp = exp.subs(&subs_operations[current_sub_operation - 1].1, false)?;
×
320
                    stack.push(sub_exp);
×
321
                }
×
322
                current_sub_operation -= 1;
×
323
            }
324
        }
325

326
        // once we're done, just return the last element in the stack
327
        Ok(stack
180✔
328
            .pop()
180✔
329
            .expect("Invalid QPY replay encountered during deserialization: empty OPReplay."))
180✔
330
    }
180✔
331

332
    pub fn iter_symbols(&self) -> impl Iterator<Item = &Symbol> + '_ {
462,850✔
333
        self.name_map.values()
462,850✔
334
    }
462,850✔
335

336
    /// Get the number of [Symbol]s in the expression.
337
    pub fn num_symbols(&self) -> usize {
6✔
338
        self.name_map.len()
6✔
339
    }
6✔
340

341
    /// Whether the expression represents a complex number. None if cannot be determined.
342
    pub fn is_complex(&self) -> Option<bool> {
23,198✔
343
        self.expr.is_complex()
23,198✔
344
    }
23,198✔
345

346
    /// Whether the expression represents a int. None if cannot be determined.
347
    pub fn is_int(&self) -> Option<bool> {
85,722✔
348
        self.expr.is_int()
85,722✔
349
    }
85,722✔
350

351
    /// Add an expression; ``self + rhs``.
352
    pub fn add(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,391,380✔
353
        let name_map = self.merged_name_map(rhs)?;
2,391,380✔
354
        Ok(Self {
2,391,378✔
355
            expr: &self.expr + &rhs.expr,
2,391,378✔
356
            name_map,
2,391,378✔
357
        })
2,391,378✔
358
    }
2,391,380✔
359

360
    /// Multiply with an expression; ``self * rhs``.
361
    pub fn mul(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
2,415,426✔
362
        let name_map = self.merged_name_map(rhs)?;
2,415,426✔
363
        Ok(Self {
2,415,424✔
364
            expr: &self.expr * &rhs.expr,
2,415,424✔
365
            name_map,
2,415,424✔
366
        })
2,415,424✔
367
    }
2,415,426✔
368

369
    /// Subtract another expression; ``self - rhs``.
370
    pub fn sub(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
24,124✔
371
        let name_map = self.merged_name_map(rhs)?;
24,124✔
372
        Ok(Self {
24,122✔
373
            expr: &self.expr - &rhs.expr,
24,122✔
374
            name_map,
24,122✔
375
        })
24,122✔
376
    }
24,124✔
377

378
    /// Divide by another expression; ``self / rhs``.
379
    pub fn div(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
13,956✔
380
        if rhs.expr.is_zero() {
13,956✔
381
            return Err(ParameterError::ZeroDivisionError);
1,012✔
382
        }
12,944✔
383

384
        let name_map = self.merged_name_map(rhs)?;
12,944✔
385
        Ok(Self {
12,942✔
386
            expr: &self.expr / &rhs.expr,
12,942✔
387
            name_map,
12,942✔
388
        })
12,942✔
389
    }
13,956✔
390

391
    /// Raise this expression to a power; ``self ^ rhs``.
392
    pub fn pow(&self, rhs: &ParameterExpression) -> Result<Self, ParameterError> {
3,016✔
393
        let name_map = self.merged_name_map(rhs)?;
3,016✔
394
        Ok(Self {
3,016✔
395
            expr: self.expr.pow(&rhs.expr),
3,016✔
396
            name_map,
3,016✔
397
        })
3,016✔
398
    }
3,016✔
399

400
    /// Apply the sine to this expression; ``sin(self)``.
401
    pub fn sin(&self) -> Self {
362✔
402
        Self {
362✔
403
            expr: self.expr.sin(),
362✔
404
            name_map: self.name_map.clone(),
362✔
405
        }
362✔
406
    }
362✔
407

408
    /// Apply the cosine to this expression; ``cos(self)``.
409
    pub fn cos(&self) -> Self {
352✔
410
        Self {
352✔
411
            expr: self.expr.cos(),
352✔
412
            name_map: self.name_map.clone(),
352✔
413
        }
352✔
414
    }
352✔
415

416
    /// Apply the tangent to this expression; ``tan(self)``.
417
    pub fn tan(&self) -> Self {
352✔
418
        Self {
352✔
419
            expr: self.expr.tan(),
352✔
420
            name_map: self.name_map.clone(),
352✔
421
        }
352✔
422
    }
352✔
423

424
    /// Apply the arcsine to this expression; ``asin(self)``.
425
    pub fn asin(&self) -> Self {
100✔
426
        Self {
100✔
427
            expr: self.expr.asin(),
100✔
428
            name_map: self.name_map.clone(),
100✔
429
        }
100✔
430
    }
100✔
431

432
    /// Apply the arccosine to this expression; ``acos(self)``.
433
    pub fn acos(&self) -> Self {
100✔
434
        Self {
100✔
435
            expr: self.expr.acos(),
100✔
436
            name_map: self.name_map.clone(),
100✔
437
        }
100✔
438
    }
100✔
439

440
    /// Apply the arctangent to this expression; ``atan(self)``.
441
    pub fn atan(&self) -> Self {
100✔
442
        Self {
100✔
443
            expr: self.expr.atan(),
100✔
444
            name_map: self.name_map.clone(),
100✔
445
        }
100✔
446
    }
100✔
447

448
    /// Exponentiate this expression; ``exp(self)``.
449
    pub fn exp(&self) -> Self {
350✔
450
        Self {
350✔
451
            expr: self.expr.exp(),
350✔
452
            name_map: self.name_map.clone(),
350✔
453
        }
350✔
454
    }
350✔
455

456
    /// Take the (natural) logarithm of this expression; ``log(self)``.
457
    pub fn log(&self) -> Self {
266✔
458
        Self {
266✔
459
            expr: self.expr.log(),
266✔
460
            name_map: self.name_map.clone(),
266✔
461
        }
266✔
462
    }
266✔
463

464
    /// Take the absolute value of this expression; ``|self|``.
465
    pub fn abs(&self) -> Self {
374✔
466
        Self {
374✔
467
            expr: self.expr.abs(),
374✔
468
            name_map: self.name_map.clone(),
374✔
469
        }
374✔
470
    }
374✔
471

472
    /// Return the sign of this expression; ``sign(self)``.
473
    pub fn sign(&self) -> Self {
10✔
474
        Self {
10✔
475
            expr: self.expr.sign(),
10✔
476
            name_map: self.name_map.clone(),
10✔
477
        }
10✔
478
    }
10✔
479

480
    /// Complex conjugate the expression.
481
    pub fn conjugate(&self) -> Self {
1,832✔
482
        Self {
1,832✔
483
            expr: self.expr.conjugate(),
1,832✔
484
            name_map: self.name_map.clone(),
1,832✔
485
        }
1,832✔
486
    }
1,832✔
487

488
    /// negate the expression.
489
    pub fn neg(&self) -> Self {
×
490
        Self {
×
491
            expr: -&self.expr,
×
492
            name_map: self.name_map.clone(),
×
493
        }
×
494
    }
×
495

496
    /// Compute the derivative of the expression with respect to the provided symbol.
497
    ///
498
    /// Note that this keeps the name map unchanged. Meaning that computing the derivative
499
    /// of ``x`` will yield ``1`` but the expression still owns the symbol ``x``. This is
500
    /// done such that we can still bind the value ``x`` in an automated process.
501
    pub fn derivative(&self, param: &Symbol) -> Result<Self, ParameterError> {
74✔
502
        Ok(Self {
503
            expr: self
74✔
504
                .expr
74✔
505
                .derivative(param)
74✔
506
                .map_err(ParameterError::DerivativeNotSupported)?,
74✔
507
            name_map: self.name_map.clone(),
70✔
508
        })
509
    }
74✔
510

511
    /// Substitute symbols with [ParameterExpression]s.
512
    ///
513
    /// # Arguments
514
    ///
515
    /// * map - A hashmap with [Symbol] keys and [ParameterExpression]s to replace these
516
    ///   symbols with.
517
    /// * allow_unknown_parameters - If `false`, returns an error if any symbol in the
518
    ///   hashmap is not present in the expression. If `true`, unknown symbols are ignored.
519
    ///   Setting to `true` is slightly faster as it does not involve additional checks.
520
    ///
521
    /// # Returns
522
    ///
523
    /// * `Ok(Self)` - A parameter expression with the substituted expressions.
524
    /// * `Err(ParameterError)` - An error if the subtitution failed.
525
    pub fn subs(
241,966✔
526
        &self,
241,966✔
527
        map: &HashMap<Symbol, Self>,
241,966✔
528
        allow_unknown_parameters: bool,
241,966✔
529
    ) -> Result<Self, ParameterError> {
241,966✔
530
        // Build the outgoing name map. In the process we check for any duplicates.
531
        let mut name_map: HashMap<String, Symbol> = HashMap::new();
241,966✔
532
        let mut symbol_map: HashMap<Symbol, SymbolExpr> = HashMap::new();
241,966✔
533

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

540
            if difference.peek().is_some() {
36,726✔
541
                let different_symbols = difference.map(|s| (**s).clone()).collect();
2✔
542
                return Err(ParameterError::UnknownParameters(different_symbols));
2✔
543
            }
36,724✔
544
        }
205,240✔
545

546
        for (name, symbol) in self.name_map.iter() {
251,254✔
547
            // check if the symbol will get replaced
548
            if let Some(replacement) = map.get(symbol) {
251,254✔
549
                // If yes, update the name_map. This also checks for duplicates.
550
                for (replacement_name, replacement_symbol) in replacement.name_map.iter() {
39,216✔
551
                    if let Some(duplicate) = name_map.get(replacement_name) {
39,216✔
552
                        // If a symbol with the same name already exists, check whether it is
553
                        // the same symbol (fine) or a different symbol with the same name (conflict)!
554
                        if duplicate != replacement_symbol {
36✔
555
                            return Err(ParameterError::NameConflict);
2✔
556
                        }
34✔
557
                    } else {
39,180✔
558
                        // SAFETY: We know the key does not exist yet.
39,180✔
559
                        unsafe {
39,180✔
560
                            name_map.insert_unique_unchecked(
39,180✔
561
                                replacement_name.clone(),
39,180✔
562
                                replacement_symbol.clone(),
39,180✔
563
                            )
39,180✔
564
                        };
39,180✔
565
                    }
39,180✔
566
                }
567

568
                // If we got until here, there were no duplicates, so we are safe to
569
                // add this symbol to the internal replacement map.
570
                symbol_map.insert(symbol.clone(), replacement.expr.clone());
38,110✔
571
            } else {
572
                // no replacement for this symbol, carry on
573
                match name_map.entry(name.clone()) {
213,142✔
574
                    Entry::Occupied(duplicate) => {
18✔
575
                        if duplicate.get() != symbol {
18✔
576
                            return Err(ParameterError::NameConflict);
×
577
                        }
18✔
578
                    }
579
                    Entry::Vacant(e) => {
213,124✔
580
                        e.insert(symbol.clone());
213,124✔
581
                    }
213,124✔
582
                }
583
            }
584
        }
585

586
        let res = self.expr.subs(&symbol_map);
241,962✔
587
        Ok(Self {
241,962✔
588
            expr: res,
241,962✔
589
            name_map,
241,962✔
590
        })
241,962✔
591
    }
241,966✔
592

593
    /// Bind symbols to values.
594
    ///
595
    /// # Arguments
596
    ///
597
    /// * map - A hashmap with [Symbol] keys and [Value]s to replace these
598
    ///   symbols with.
599
    /// * allow_unknown_parameter - If `false`, returns an error if any symbol in the
600
    ///   hashmap is not present in the expression. If `true`, unknown symbols are ignored.
601
    ///   Setting to `true` is slightly faster as it does not involve additional checks.
602
    ///
603
    /// # Returns
604
    ///
605
    /// * `Ok(Self)` - A parameter expression with the bound symbols.
606
    /// * `Err(ParameterError)` - An error if binding failed.
607
    pub fn bind(
414,360✔
608
        &self,
414,360✔
609
        map: &HashMap<&Symbol, Value>,
414,360✔
610
        allow_unknown_parameters: bool,
414,360✔
611
    ) -> Result<Self, ParameterError> {
414,360✔
612
        // The set of symbols we will bind. Used twice, hence pre-computed here.
613
        let bind_symbols: HashSet<&Symbol> = map.keys().cloned().collect();
414,360✔
614

615
        // If we don't allow for unknown parameters, check if there are any.
616
        if !allow_unknown_parameters {
414,360✔
617
            let existing: HashSet<&Symbol> = self.name_map.values().collect();
209,032✔
618
            let mut difference = bind_symbols.difference(&existing).peekable();
209,032✔
619

620
            if difference.peek().is_some() {
209,032✔
621
                let different_symbols = difference.map(|s| (**s).clone()).collect();
×
622
                return Err(ParameterError::UnknownParameters(different_symbols));
×
623
            }
209,032✔
624
        }
205,328✔
625

626
        // bind the symbol expression and then check the outcome for inf/nan, or numeric values
627
        let bound_expr = self.expr.bind(map);
414,360✔
628
        let bound = match bound_expr.eval(true) {
414,360✔
629
            Some(v) => match &v {
411,658✔
630
                Value::Real(r) => {
345,878✔
631
                    if r.is_infinite() {
345,878✔
632
                        Err(ParameterError::BindingInf)
1,828✔
633
                    } else if r.is_nan() {
344,050✔
634
                        Err(ParameterError::BindingNaN)
×
635
                    } else {
636
                        Ok(SymbolExpr::Value(v))
344,050✔
637
                    }
638
                }
639
                Value::Int(_) => Ok(SymbolExpr::Value(v)),
22,292✔
640
                Value::Complex(c) => {
43,488✔
641
                    if c.re.is_infinite() || c.im.is_infinite() {
43,488✔
642
                        Err(ParameterError::BindingInf)
×
643
                    } else if c.re.is_nan() || c.im.is_nan() {
43,488✔
644
                        Err(ParameterError::BindingNaN)
×
645
                    } else if (-symbol_expr::SYMEXPR_EPSILON..symbol_expr::SYMEXPR_EPSILON)
43,488✔
646
                        .contains(&c.im)
43,488✔
647
                    {
648
                        Ok(SymbolExpr::Value(Value::Real(c.re)))
1,728✔
649
                    } else {
650
                        Ok(SymbolExpr::Value(v))
41,760✔
651
                    }
652
                }
653
            },
654
            None => Ok(bound_expr),
2,702✔
655
        }?;
1,828✔
656

657
        // update the name map by removing the bound parameters
658
        let bound_name_map: HashMap<String, Symbol> = self
412,532✔
659
            .name_map
412,532✔
660
            .iter()
412,532✔
661
            .filter(|(_, symbol)| !bind_symbols.contains(symbol))
4,711,580✔
662
            .map(|(name, symbol)| (name.clone(), symbol.clone()))
412,532✔
663
            .collect();
412,532✔
664

665
        Ok(Self {
412,532✔
666
            expr: bound,
412,532✔
667
            name_map: bound_name_map,
412,532✔
668
        })
412,532✔
669
    }
414,360✔
670

671
    /// Merge name maps.
672
    ///
673
    /// # Arguments
674
    ///
675
    /// * `other` - The other parameter expression whose symbols we add to self.
676
    ///
677
    /// # Returns
678
    ///
679
    /// * `Ok(HashMap<String, Symbol>)` - The merged name map.
680
    /// * `Err(ParameterError)` - An error if there was a name conflict.
681
    fn merged_name_map(&self, other: &Self) -> Result<HashMap<String, Symbol>, ParameterError> {
4,846,890✔
682
        let mut merged = self.name_map.clone();
4,846,890✔
683
        for (name, param) in other.name_map.iter() {
7,052,148✔
684
            match merged.get(name) {
7,051,068✔
685
                Some(existing_param) => {
3,990,500✔
686
                    if param != existing_param {
3,990,500✔
687
                        return Err(ParameterError::NameConflict);
8✔
688
                    }
3,990,492✔
689
                }
690
                None => {
3,060,568✔
691
                    // SAFETY: We ensured the key is unique
3,060,568✔
692
                    let _ = unsafe { merged.insert_unique_unchecked(name.clone(), param.clone()) };
3,060,568✔
693
                }
3,060,568✔
694
            }
695
        }
696
        Ok(merged)
4,846,882✔
697
    }
4,846,890✔
698
}
699

700
/// A parameter expression.
701
///
702
/// This is backed by Qiskit's symbolic expression engine and a cache
703
/// for the parameters inside the expression.
704
#[pyclass(
705
    subclass,
706
    module = "qiskit._accelerate.circuit",
707
    name = "ParameterExpression"
708
)]
709
#[derive(Clone, Debug)]
710
pub struct PyParameterExpression {
711
    pub inner: ParameterExpression,
712
}
713

714
impl Default for PyParameterExpression {
715
    /// The default constructor returns zero.
716
    fn default() -> Self {
×
717
        Self {
×
718
            inner: ParameterExpression::default(),
×
719
        }
×
720
    }
×
721
}
722

723
impl fmt::Display for PyParameterExpression {
724
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336,578✔
725
        self.inner.fmt(f)
336,578✔
726
    }
336,578✔
727
}
728

729
impl From<ParameterExpression> for PyParameterExpression {
730
    fn from(value: ParameterExpression) -> Self {
12,336,178✔
731
        Self { inner: value }
12,336,178✔
732
    }
12,336,178✔
733
}
734

735
impl PyParameterExpression {
736
    /// Attempt to extract a `PyParameterExpression` from a bound `PyAny`.
737
    ///
738
    /// This will try to coerce to the strictest data type:
739
    /// Int - Real - Complex - PyParameterVectorElement - PyParameter - PyParameterExpression.
740
    ///
741
    /// # Arguments:
742
    ///
743
    /// * ob - The bound `PyAny` to extract from.
744
    ///
745
    /// # Returns
746
    ///
747
    /// * `Ok(Self)` - The extracted expression.
748
    /// * `Err(PyResult)` - An error if extraction to all above types failed.
749
    pub fn extract_coerce(ob: Borrowed<PyAny>) -> PyResult<Self> {
4,960,330✔
750
        if let Ok(i) = ob.cast::<PyInt>() {
4,960,330✔
751
            Ok(ParameterExpression::new(
134,184✔
752
                SymbolExpr::Value(Value::from(i.extract::<i64>()?)),
134,184✔
753
                HashMap::new(),
134,184✔
754
            )
755
            .into())
134,184✔
756
        } else if let Ok(r) = ob.cast::<PyFloat>() {
4,826,146✔
757
            let r: f64 = r.extract()?;
13,386✔
758
            if r.is_infinite() || r.is_nan() {
13,386✔
759
                return Err(ParameterError::InvalidValue.into());
40✔
760
            }
13,346✔
761
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(r)), HashMap::new()).into())
13,346✔
762
        } else if let Ok(c) = ob.cast::<PyComplex>() {
4,812,760✔
763
            let c: Complex64 = c.extract()?;
2,361,356✔
764
            if c.is_infinite() || c.is_nan() {
2,361,356✔
765
                return Err(ParameterError::InvalidValue.into());
×
766
            }
2,361,356✔
767
            Ok(ParameterExpression::new(SymbolExpr::Value(Value::from(c)), HashMap::new()).into())
2,361,356✔
768
        } else if let Ok(element) = ob.cast::<PyParameterVectorElement>() {
2,451,404✔
769
            Ok(ParameterExpression::from_symbol(element.borrow().symbol.clone()).into())
50,858✔
770
        } else if let Ok(parameter) = ob.cast::<PyParameter>() {
2,400,546✔
771
            Ok(ParameterExpression::from_symbol(parameter.borrow().symbol.clone()).into())
14,362✔
772
        } else {
773
            ob.extract::<PyParameterExpression>().map_err(Into::into)
2,386,184✔
774
        }
775
    }
4,960,330✔
776

777
    pub fn coerce_into_py(&self, py: Python) -> PyResult<Py<PyAny>> {
33,152✔
778
        if let Ok(value) = self.inner.try_to_value(true) {
33,152✔
779
            match value {
14✔
780
                Value::Int(i) => Ok(PyInt::new(py, i).unbind().into_any()),
×
781
                Value::Real(r) => Ok(PyFloat::new(py, r).unbind().into_any()),
14✔
782
                Value::Complex(c) => Ok(PyComplex::from_complex_bound(py, c).unbind().into_any()),
×
783
            }
784
        } else if let Ok(symbol) = self.inner.try_to_symbol() {
33,138✔
785
            if symbol.index.is_some() {
23,210✔
786
                Ok(Py::new(py, PyParameterVectorElement::from_symbol(symbol))?.into_any())
5,936✔
787
            } else {
788
                Ok(Py::new(py, PyParameter::from_symbol(symbol))?.into_any())
17,274✔
789
            }
790
        } else {
791
            self.clone().into_py_any(py)
9,928✔
792
        }
793
    }
33,152✔
794
}
795

796
#[pymethods]
×
797
impl PyParameterExpression {
798
    /// This is a **strictly internal** constructor and **should not be used**.
799
    /// It is subject to arbitrary change in between Qiskit versions and cannot be relied on.
800
    /// Parameter expressions should always be constructed from applying operations on
801
    /// parameters, or by loading via QPY.
802
    ///
803
    /// The input values are allowed to be None for pickling purposes.
804
    #[new]
805
    #[pyo3(signature = (name_map=None, expr=None))]
806
    pub fn py_new(
70✔
807
        name_map: Option<HashMap<String, PyParameter>>,
70✔
808
        expr: Option<String>,
70✔
809
    ) -> PyResult<Self> {
70✔
810
        match (name_map, expr) {
70✔
811
            (None, None) => Ok(Self::default()),
×
812
            (Some(name_map), Some(expr)) => {
70✔
813
                // We first parse the expression and then update the symbols with the ones
814
                // the user provided. The replacement relies on the names to match.
815
                // This is hacky and we likely want a more reliably conversion from a SymPy object,
816
                // if we decide we want to continue supporting this.
817
                let expr = parse_expression(&expr)
70✔
818
                    .map_err(|_| PyRuntimeError::new_err("Failed parsing input expression"))?;
70✔
819
                let symbol_map: HashMap<String, Symbol> = name_map
70✔
820
                    .iter()
70✔
821
                    .map(|(string, param)| (string.clone(), param.symbol.clone()))
70✔
822
                    .collect();
70✔
823

824
                let replaced_expr = symbol_expr::replace_symbol(&expr, &symbol_map);
70✔
825

826
                let inner = ParameterExpression::new(replaced_expr, symbol_map);
70✔
827
                Ok(Self { inner })
70✔
828
            }
829
            _ => Err(PyValueError::new_err(
×
830
                "Pass either both a name_map and expr, or neither",
×
831
            )),
×
832
        }
833
    }
70✔
834

835
    #[allow(non_snake_case)]
836
    #[staticmethod]
837
    pub fn _Value(value: &Bound<PyAny>) -> PyResult<Self> {
34✔
838
        Self::extract_coerce(value.as_borrowed())
34✔
839
    }
34✔
840

841
    /// Check if the expression corresponds to a plain symbol.
842
    ///
843
    /// Returns:
844
    ///     ``True`` is this expression corresponds to a symbol, ``False`` otherwise.
845
    pub fn is_symbol(&self) -> bool {
480✔
846
        matches!(self.inner.expr, SymbolExpr::Symbol(_))
480✔
847
    }
480✔
848

849
    /// Cast this expression to a numeric value.
850
    ///
851
    /// Args:
852
    ///     strict: If ``True`` (default) this function raises an error if there are any
853
    ///         unbound symbols in the expression. If ``False``, this allows casting
854
    ///         if the expression represents a numeric value, regardless of unbound symbols.
855
    ///         For example ``(0 * Parameter("x"))`` is 0 but has the symbol ``x`` present.
856
    #[pyo3(signature = (strict=true))]
857
    pub fn numeric(&self, py: Python, strict: bool) -> PyResult<Py<PyAny>> {
47,702✔
858
        match self.inner.try_to_value(strict)? {
47,702✔
859
            Value::Real(r) => r.into_py_any(py),
21,302✔
860
            Value::Int(i) => i.into_py_any(py),
11,792✔
861
            Value::Complex(c) => c.into_py_any(py),
14,488✔
862
        }
863
    }
47,702✔
864

865
    /// Return a SymPy equivalent of this expression.
866
    ///
867
    /// Returns:
868
    ///     A SymPy equivalent of this expression.
869
    pub fn sympify(&self, py: Python) -> PyResult<Py<PyAny>> {
480✔
870
        let py_sympify = SYMPIFY_PARAMETER_EXPRESSION.get(py);
480✔
871
        py_sympify.call1(py, (self.clone(),))
480✔
872
    }
480✔
873

874
    /// The number of unbound parameters in the expression.
875
    ///
876
    /// This is equivalent to ``len(expr.parameters)`` but does not involve the overhead of creating
877
    /// a set and counting its length.
878
    #[getter]
879
    pub fn num_parameters(&self) -> usize {
6✔
880
        self.inner.num_symbols()
6✔
881
    }
6✔
882

883
    /// Get the parameters present in the expression.
884
    ///
885
    /// .. note::
886
    ///
887
    ///     Qiskit guarantees equality (via ``==``) of parameters retrieved from an expression
888
    ///     with the original :class:`.Parameter` objects used to create this expression,
889
    ///     but does **not guarantee** ``is`` comparisons to succeed.
890
    ///
891
    #[getter]
892
    pub fn parameters<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PySet>> {
135,748✔
893
        let py_parameters: Vec<Py<PyAny>> = self
135,748✔
894
            .inner
135,748✔
895
            .iter_symbols()
135,748✔
896
            .map(|symbol| {
4,563,778✔
897
                if symbol.is_vector_element() {
4,563,778✔
898
                    Ok(
899
                        Py::new(py, PyParameterVectorElement::from_symbol(symbol.clone()))?
4,497,290✔
900
                            .into_any(),
4,497,290✔
901
                    )
902
                } else {
903
                    Ok(Py::new(py, PyParameter::from_symbol(symbol.clone()))?.into_any())
66,488✔
904
                }
905
            })
4,563,778✔
906
            .collect::<PyResult<_>>()?;
135,748✔
907
        PySet::new(py, py_parameters)
135,748✔
908
    }
135,748✔
909

910
    /// Sine of the expression.
911
    #[inline]
912
    #[pyo3(name = "sin")]
913
    pub fn py_sin(&self) -> Self {
356✔
914
        self.inner.sin().into()
356✔
915
    }
356✔
916

917
    /// Cosine of the expression.
918
    #[inline]
919
    #[pyo3(name = "cos")]
920
    pub fn py_cos(&self) -> Self {
350✔
921
        self.inner.cos().into()
350✔
922
    }
350✔
923

924
    /// Tangent of the expression.
925
    #[inline]
926
    #[pyo3(name = "tan")]
927
    pub fn py_tan(&self) -> Self {
350✔
928
        self.inner.tan().into()
350✔
929
    }
350✔
930

931
    /// Arcsine of the expression.
932
    #[inline]
933
    pub fn arcsin(&self) -> Self {
98✔
934
        self.inner.asin().into()
98✔
935
    }
98✔
936

937
    /// Arccosine of the expression.
938
    #[inline]
939
    pub fn arccos(&self) -> Self {
98✔
940
        self.inner.acos().into()
98✔
941
    }
98✔
942

943
    /// Arctangent of the expression.
944
    #[inline]
945
    pub fn arctan(&self) -> Self {
98✔
946
        self.inner.atan().into()
98✔
947
    }
98✔
948

949
    /// Exponentiate the expression.
950
    #[inline]
951
    #[pyo3(name = "exp")]
952
    pub fn py_exp(&self) -> Self {
348✔
953
        self.inner.exp().into()
348✔
954
    }
348✔
955

956
    /// Take the natural logarithm of the expression.
957
    #[inline]
958
    #[pyo3(name = "log")]
959
    pub fn py_log(&self) -> Self {
264✔
960
        self.inner.log().into()
264✔
961
    }
264✔
962

963
    /// Take the absolute value of the expression.
964
    #[inline]
965
    #[pyo3(name = "abs")]
966
    pub fn py_abs(&self) -> Self {
10✔
967
        self.inner.abs().into()
10✔
968
    }
10✔
969

970
    /// Return the sign of the expression.
971
    #[inline]
972
    #[pyo3(name = "sign")]
973
    pub fn py_sign(&self) -> Self {
10✔
974
        self.inner.sign().into()
10✔
975
    }
10✔
976

977
    /// Return the complex conjugate of the expression.
978
    #[inline]
979
    #[pyo3(name = "conjugate")]
980
    pub fn py_conjugate(&self) -> Self {
1,830✔
981
        self.inner.conjugate().into()
1,830✔
982
    }
1,830✔
983

984
    /// Check whether the expression represents a real number.
985
    ///
986
    /// Note that this will return ``None`` if there are unbound parameters, in which case
987
    /// it cannot be determined whether the expression is real.
988
    #[inline]
989
    #[pyo3(name = "is_real")]
990
    pub fn py_is_real(&self) -> Option<bool> {
270✔
991
        self.inner.expr.is_real()
270✔
992
    }
270✔
993

994
    /// Return derivative of this expression with respect to the input parameter.
995
    ///
996
    /// Args:
997
    ///     param: The parameter with respect to which the derivative is calculated.
998
    ///
999
    /// Returns:
1000
    ///     The derivative as either a constant numeric value or a symbolic
1001
    ///     :class:`.ParameterExpression`.
1002
    pub fn gradient(&self, param: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
74✔
1003
        let symbol = symbol_from_py_parameter(param)?;
74✔
1004
        let d_expr = self.inner.derivative(&symbol)?;
74✔
1005

1006
        // try converting to value and return as built-in numeric type
1007
        match d_expr.try_to_value(false) {
70✔
1008
            Ok(val) => match val {
20✔
1009
                Value::Real(r) => r.into_py_any(param.py()),
18✔
1010
                Value::Int(i) => i.into_py_any(param.py()),
2✔
1011
                Value::Complex(c) => c.into_py_any(param.py()),
×
1012
            },
1013
            Err(_) => PyParameterExpression::from(d_expr).into_py_any(param.py()),
50✔
1014
        }
1015
    }
74✔
1016

1017
    /// Return all values in this equation.
1018
    pub fn _values(&self, py: Python) -> PyResult<Vec<Py<PyAny>>> {
78✔
1019
        self.inner
78✔
1020
            .expr
78✔
1021
            .values()
78✔
1022
            .iter()
78✔
1023
            .map(|val| match val {
78✔
1024
                Value::Real(r) => r.into_py_any(py),
34✔
1025
                Value::Int(i) => i.into_py_any(py),
10✔
1026
                Value::Complex(c) => c.into_py_any(py),
×
1027
            })
44✔
1028
            .collect()
78✔
1029
    }
78✔
1030

1031
    /// Returns a new expression with replacement parameters.
1032
    ///
1033
    /// Args:
1034
    ///     parameter_map: Mapping from :class:`.Parameter`\ s in ``self`` to the
1035
    ///         :class:`.ParameterExpression` instances with which they should be replaced.
1036
    ///     allow_unknown_parameters: If ``False``, raises an error if ``parameter_map``
1037
    ///         contains :class:`.Parameter`\ s in the keys outside those present in the expression.
1038
    ///         If ``True``, any such parameters are simply ignored.
1039
    ///
1040
    /// Raises:
1041
    ///     CircuitError:
1042
    ///         - If parameter_map contains parameters outside those in self.
1043
    ///         - If the replacement parameters in ``parameter_map`` would result in
1044
    ///           a name conflict in the generated expression.
1045
    ///
1046
    /// Returns:
1047
    ///     A new expression with the specified parameters replaced.
1048
    #[pyo3(name = "subs")]
1049
    #[pyo3(signature = (parameter_map, allow_unknown_parameters=false))]
1050
    pub fn py_subs(
228✔
1051
        &self,
228✔
1052
        parameter_map: HashMap<PyParameter, Self>,
228✔
1053
        allow_unknown_parameters: bool,
228✔
1054
    ) -> PyResult<Self> {
228✔
1055
        // reduce the map to a HashMap<Symbol, ParameterExpression>
1056
        let map = parameter_map
228✔
1057
            .into_iter()
228✔
1058
            .map(|(param, expr)| Ok((param.symbol, expr.inner)))
232✔
1059
            .collect::<PyResult<_>>()?;
228✔
1060

1061
        // apply to the inner expression
1062
        match self.inner.subs(&map, allow_unknown_parameters) {
228✔
1063
            Ok(subbed) => Ok(subbed.into()),
224✔
1064
            Err(e) => Err(e.into()),
4✔
1065
        }
1066
    }
228✔
1067

1068
    /// Binds the provided set of parameters to their corresponding values.
1069
    ///
1070
    /// Args:
1071
    ///     parameter_values: Mapping of :class:`.Parameter` instances to the numeric value to which
1072
    ///         they will be bound.
1073
    ///     allow_unknown_parameters: If ``False``, raises an error if ``parameter_values``
1074
    ///         contains :class:`.Parameter`\ s in the keys outside those present in the expression.
1075
    ///         If ``True``, any such parameters are simply ignored.
1076
    ///
1077
    /// Raises:
1078
    ///     CircuitError:
1079
    ///         - If parameter_values contains parameters outside those in self.
1080
    ///         - If a non-numeric value is passed in ``parameter_values``.
1081
    ///     ZeroDivisionError:
1082
    ///         - If binding the provided values requires division by zero.
1083
    ///
1084
    /// Returns:
1085
    ///     A new expression parameterized by any parameters which were not bound by
1086
    ///     ``parameter_values``.
1087
    #[pyo3(name = "bind")]
1088
    #[pyo3(signature = (parameter_values, allow_unknown_parameters=false))]
1089
    pub fn py_bind(
127,050✔
1090
        &self,
127,050✔
1091
        parameter_values: HashMap<PyParameter, Bound<PyAny>>,
127,050✔
1092
        allow_unknown_parameters: bool,
127,050✔
1093
    ) -> PyResult<Self> {
127,050✔
1094
        // reduce the map to a HashMap<Symbol, Value>
1095
        let map = parameter_values
127,050✔
1096
            .iter()
127,050✔
1097
            .map(|(param, value)| {
4,555,024✔
1098
                let value = value.extract()?;
4,555,024✔
1099
                Ok((param.symbol(), value))
4,555,024✔
1100
            })
4,555,024✔
1101
            .collect::<PyResult<_>>()?;
127,050✔
1102

1103
        // apply to the inner expression
1104
        match self.inner.bind(&map, allow_unknown_parameters) {
127,050✔
1105
            Ok(bound) => Ok(bound.into()),
125,222✔
1106
            Err(e) => Err(e.into()),
1,828✔
1107
        }
1108
    }
127,050✔
1109

1110
    /// Bind all of the parameters in ``self`` to numeric values in the dictionary, returning a
1111
    /// numeric value.
1112
    ///
1113
    /// This is a special case of :meth:`bind` which can reach higher performance.  It is no problem
1114
    /// for the ``values`` dictionary to contain parameters that are not used in this expression;
1115
    /// the expectation is that the same bindings dictionary will be fed to other expressions as
1116
    /// well.
1117
    ///
1118
    /// It is an error to call this method with a ``values`` dictionary that does not bind all of
1119
    /// the values, or to call this method with non-numeric values, but this is not explicitly
1120
    /// checked, since this method is intended for performance-sensitive use.  Passing an incorrect
1121
    /// dictionary may result in unexpected behavior.
1122
    ///
1123
    /// Unlike :meth:`bind`, this method will not raise an exception if non-finite floating-point
1124
    /// values are encountered.
1125
    ///
1126
    /// Args:
1127
    ///     values: mapping of parameters to numeric values.
1128
    #[pyo3(name = "bind_all")]
1129
    #[pyo3(signature = (values, *))]
1130
    pub fn py_bind_all(&self, values: Bound<PyAny>) -> PyResult<Value> {
4✔
1131
        let mut partial_map = HashMap::with_capacity(self.inner.name_map.len());
4✔
1132
        for symbol in self.inner.name_map.values() {
6✔
1133
            let py_parameter = symbol.clone().into_pyobject(values.py())?;
6✔
1134
            partial_map.insert(symbol, values.get_item(py_parameter)?.extract()?);
6✔
1135
        }
1136
        let bound = self.inner.expr.bind(&partial_map);
2✔
1137
        bound.eval(true).ok_or_else(|| {
2✔
1138
            PyTypeError::new_err(format!(
×
1139
                "binding did not produce a numeric quantity: {bound:?}"
1140
            ))
1141
        })
×
1142
    }
4✔
1143

1144
    /// Assign one parameter to a value, which can either be numeric or another parameter
1145
    /// expression.
1146
    ///
1147
    /// Args:
1148
    ///     parameter: A parameter in this expression whose value will be updated.
1149
    ///     value: The new value to bind to.
1150
    ///
1151
    /// Returns:
1152
    ///     A new expression parameterized by any parameters which were not bound by assignment.
1153
    #[pyo3(name = "assign")]
1154
    pub fn py_assign(&self, parameter: PyParameter, value: &Bound<PyAny>) -> PyResult<Self> {
174✔
1155
        if let Ok(expr) = value.cast::<Self>() {
174✔
1156
            let map = [(parameter, expr.borrow().clone())].into_iter().collect();
150✔
1157
            self.py_subs(map, false)
150✔
1158
        } else if value.extract::<Value>().is_ok() {
24✔
1159
            let map = [(parameter, value.clone())].into_iter().collect();
24✔
1160
            self.py_bind(map, false)
24✔
1161
        } else {
1162
            Err(PyValueError::new_err(
×
1163
                "Unexpected value in assign: {replacement:?}",
×
1164
            ))
×
1165
        }
1166
    }
174✔
1167

1168
    #[inline]
1169
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1170
        // ParameterExpression is immutable.
1171
        slf
×
1172
    }
×
1173

1174
    #[inline]
1175
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
1,588✔
1176
        // Everything a ParameterExpression contains is immutable.
1177
        slf
1,588✔
1178
    }
1,588✔
1179

1180
    pub fn __eq__(&self, rhs: &Bound<PyAny>) -> PyResult<bool> {
33,466✔
1181
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
33,466✔
1182
            match rhs.inner.expr {
33,454✔
1183
                SymbolExpr::Value(v) => match self.inner.try_to_value(false) {
2,724✔
1184
                    Ok(e) => Ok(e == v),
2,510✔
1185
                    Err(_) => Ok(false),
214✔
1186
                },
1187
                _ => Ok(self.inner.expr == rhs.inner.expr),
30,730✔
1188
            }
1189
        } else {
1190
            Ok(false)
12✔
1191
        }
1192
    }
33,466✔
1193

1194
    #[inline]
1195
    pub fn __abs__(&self) -> Self {
362✔
1196
        self.inner.abs().into()
362✔
1197
    }
362✔
1198

1199
    #[inline]
1200
    pub fn __pos__(&self) -> Self {
4✔
1201
        self.clone()
4✔
1202
    }
4✔
1203

1204
    pub fn __neg__(&self) -> Self {
664✔
1205
        Self {
664✔
1206
            inner: ParameterExpression::new(-&self.inner.expr, self.inner.name_map.clone()),
664✔
1207
        }
664✔
1208
    }
664✔
1209

1210
    pub fn __add__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
2,318,296✔
1211
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
2,318,296✔
1212
            Ok(self.inner.add(&rhs.inner)?.into())
2,318,284✔
1213
        } else {
1214
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1215
                "Unsupported data type for __add__",
12✔
1216
            ))
12✔
1217
        }
1218
    }
2,318,296✔
1219

1220
    pub fn __radd__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
63,928✔
1221
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
63,928✔
1222
            Ok(lhs.inner.add(&self.inner)?.into())
63,916✔
1223
        } else {
1224
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1225
                "Unsupported data type for __radd__",
12✔
1226
            ))
12✔
1227
        }
1228
    }
63,928✔
1229

1230
    pub fn __sub__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
18,454✔
1231
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
18,454✔
1232
            Ok(self.inner.sub(&rhs.inner)?.into())
18,442✔
1233
        } else {
1234
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1235
                "Unsupported data type for __sub__",
12✔
1236
            ))
12✔
1237
        }
1238
    }
18,454✔
1239

1240
    pub fn __rsub__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
4,318✔
1241
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
4,318✔
1242
            Ok(lhs.inner.sub(&self.inner)?.into())
4,306✔
1243
        } else {
1244
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1245
                "Unsupported data type for __rsub__",
12✔
1246
            ))
12✔
1247
        }
1248
    }
4,318✔
1249

1250
    pub fn __mul__<'py>(&self, rhs: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
2,408,012✔
1251
        let py = rhs.py();
2,408,012✔
1252
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
2,408,012✔
1253
            match self.inner.mul(&rhs.inner) {
2,400,652✔
1254
                Ok(result) => PyParameterExpression::from(result).into_bound_py_any(py),
2,400,650✔
1255
                Err(e) => Err(PyErr::from(e)),
2✔
1256
            }
1257
        } else {
1258
            PyNotImplemented::get(py).into_bound_py_any(py)
7,360✔
1259
        }
1260
    }
2,408,012✔
1261

1262
    pub fn __rmul__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
12,034✔
1263
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
12,034✔
1264
            Ok(lhs.inner.mul(&self.inner)?.into())
12,022✔
1265
        } else {
1266
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1267
                "Unsupported data type for __rmul__",
12✔
1268
            ))
12✔
1269
        }
1270
    }
12,034✔
1271

1272
    pub fn __truediv__(&self, rhs: &Bound<PyAny>) -> PyResult<Self> {
8,582✔
1273
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
8,582✔
1274
            Ok(self.inner.div(&rhs.inner)?.into())
8,570✔
1275
        } else {
1276
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1277
                "Unsupported data type for __truediv__",
12✔
1278
            ))
12✔
1279
        }
1280
    }
8,582✔
1281

1282
    pub fn __rtruediv__(&self, lhs: &Bound<PyAny>) -> PyResult<Self> {
4,066✔
1283
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
4,066✔
1284
            Ok(lhs.inner.div(&self.inner)?.into())
4,054✔
1285
        } else {
1286
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1287
                "Unsupported data type for __rtruediv__",
12✔
1288
            ))
12✔
1289
        }
1290
    }
4,066✔
1291

1292
    pub fn __pow__(&self, rhs: &Bound<PyAny>, _modulo: Option<i32>) -> PyResult<Self> {
1,990✔
1293
        if let Ok(rhs) = Self::extract_coerce(rhs.as_borrowed()) {
1,990✔
1294
            Ok(self.inner.pow(&rhs.inner)?.into())
1,978✔
1295
        } else {
1296
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1297
                "Unsupported data type for __pow__",
12✔
1298
            ))
12✔
1299
        }
1300
    }
1,990✔
1301

1302
    pub fn __rpow__(&self, lhs: &Bound<PyAny>, _modulo: Option<i32>) -> PyResult<Self> {
1,038✔
1303
        if let Ok(lhs) = Self::extract_coerce(lhs.as_borrowed()) {
1,038✔
1304
            Ok(lhs.inner.pow(&self.inner)?.into())
1,026✔
1305
        } else {
1306
            Err(pyo3::exceptions::PyTypeError::new_err(
12✔
1307
                "Unsupported data type for __rpow__",
12✔
1308
            ))
12✔
1309
        }
1310
    }
1,038✔
1311

1312
    pub fn __int__(&self) -> PyResult<i64> {
176✔
1313
        match self.inner.try_to_value(false)? {
176✔
1314
            Value::Complex(_) => Err(PyTypeError::new_err(
42✔
1315
                "Cannot cast complex parameter to int.",
42✔
1316
            )),
42✔
1317
            Value::Real(r) => {
90✔
1318
                let rounded = r.floor();
90✔
1319
                Ok(rounded as i64)
90✔
1320
            }
1321
            Value::Int(i) => Ok(i),
42✔
1322
        }
1323
    }
176✔
1324

1325
    pub fn __float__(&self) -> PyResult<f64> {
668✔
1326
        match self.inner.try_to_value(false)? {
668✔
1327
            Value::Complex(c) => {
44✔
1328
                if c.im.abs() > SYMEXPR_EPSILON {
44✔
1329
                    Err(PyTypeError::new_err(
44✔
1330
                        "Could not cast complex parameter expression to float.",
44✔
1331
                    ))
44✔
1332
                } else {
1333
                    Ok(c.re)
×
1334
                }
1335
            }
1336
            Value::Real(r) => Ok(r),
114✔
1337
            Value::Int(i) => Ok(i as f64),
56✔
1338
        }
1339
    }
668✔
1340

1341
    pub fn __complex__(&self) -> PyResult<Complex64> {
77,856✔
1342
        match self.inner.try_to_value(false)? {
77,856✔
1343
            Value::Complex(c) => Ok(c),
27,296✔
1344
            Value::Real(r) => Ok(Complex64::new(r, 0.)),
40,240✔
1345
            Value::Int(i) => Ok(Complex64::new(i as f64, 0.)),
10,318✔
1346
        }
1347
    }
77,856✔
1348

1349
    pub fn __str__(&self) -> String {
336,578✔
1350
        self.to_string()
336,578✔
1351
    }
336,578✔
1352

1353
    pub fn __hash__(&self, py: Python) -> PyResult<u64> {
9,164,440✔
1354
        match self.inner.try_to_value(false) {
9,164,440✔
1355
            // if a value, we promise to match the hash of the raw value!
1356
            Ok(value) => {
2✔
1357
                let py_hash = BUILTIN_HASH.get_bound(py);
2✔
1358
                match value {
2✔
1359
                    Value::Complex(c) => py_hash.call1((c,))?.extract::<u64>(),
×
1360
                    Value::Real(r) => py_hash.call1((r,))?.extract::<u64>(),
2✔
1361
                    Value::Int(i) => py_hash.call1((i,))?.extract::<u64>(),
×
1362
                }
1363
            }
1364
            Err(_) => {
1365
                let mut hasher = DefaultHasher::new();
9,164,438✔
1366
                self.inner.expr.string_id().hash(&mut hasher);
9,164,438✔
1367
                Ok(hasher.finish())
9,164,438✔
1368
            }
1369
        }
1370
    }
9,164,440✔
1371

1372
    fn __getstate__(&self) -> PyResult<(Vec<OPReplay>, Option<ParameterValueType>)> {
8,316✔
1373
        // We distinguish in two cases:
1374
        //  (a) This is indeed an expression which can be rebuild from the QPY replay. This means
1375
        //      the replay is *not empty* and it contains all symbols.
1376
        //  (b) This expression is in fact only a Value or a Symbol. In this case, the QPY replay
1377
        //      will be empty and we instead pass a `ParameterValueType` for reconstruction.
1378
        let qpy = self._qpy_replay()?;
8,316✔
1379
        if !qpy.is_empty() {
8,316✔
1380
            Ok((qpy, None))
8,316✔
1381
        } else {
1382
            let value = ParameterValueType::extract_from_expr(&self.inner.expr);
×
1383
            if value.is_none() {
×
1384
                Err(PyValueError::new_err(format!(
×
1385
                    "Failed to serialize the parameter expression: {self:?}"
×
1386
                )))
×
1387
            } else {
1388
                Ok((qpy, value))
×
1389
            }
1390
        }
1391
    }
8,316✔
1392

1393
    fn __setstate__(&mut self, state: (Vec<OPReplay>, Option<ParameterValueType>)) -> PyResult<()> {
×
1394
        // if there a replay, load from the replay
1395
        if !state.0.is_empty() {
×
1396
            let from_qpy = ParameterExpression::from_qpy(&state.0, None)?;
×
1397
            self.inner = from_qpy;
×
1398
        // otherwise, load from the ParameterValueType
1399
        } else if let Some(value) = state.1 {
×
1400
            let expr = ParameterExpression::from(value);
×
1401
            self.inner = expr;
×
1402
        } else {
×
1403
            return Err(PyValueError::new_err(
×
1404
                "Failed to read QPY replay or extract value.",
×
1405
            ));
×
1406
        }
1407
        Ok(())
×
1408
    }
×
1409

1410
    #[getter]
1411
    fn _qpy_replay(&self) -> PyResult<Vec<OPReplay>> {
8,434✔
1412
        let mut replay = Vec::new();
8,434✔
1413
        qpy_replay(&self.inner, &self.inner.name_map, &mut replay);
8,434✔
1414
        Ok(replay)
8,434✔
1415
    }
8,434✔
1416
}
1417

1418
/// A compile-time symbolic parameter.
1419
///
1420
/// The value of a :class:`.Parameter` must be entirely determined before a circuit begins execution.
1421
/// Typically this will mean that you should supply values for all :class:`.Parameter`\ s in a
1422
/// circuit using :meth:`.QuantumCircuit.assign_parameters`, though certain hardware vendors may
1423
/// allow you to give them a circuit in terms of these parameters, provided you also pass the values
1424
/// separately.
1425
///
1426
/// This is the atom of :class:`.ParameterExpression`, and is itself an expression.  The numeric
1427
/// value of a parameter need not be fixed while the circuit is being defined.
1428
///
1429
/// Examples:
1430
///
1431
///     Construct a variable-rotation X gate using circuit parameters.
1432
///
1433
///     .. plot::
1434
///         :alt: Circuit diagram output by the previous code.
1435
///         :include-source:
1436
///
1437
///         from qiskit.circuit import QuantumCircuit, Parameter
1438
///
1439
///         # create the parameter
1440
///         phi = Parameter("phi")
1441
///         qc = QuantumCircuit(1)
1442
///
1443
///         # parameterize the rotation
1444
///         qc.rx(phi, 0)
1445
///         qc.draw("mpl")
1446
///
1447
///         # bind the parameters after circuit to create a bound circuit
1448
///         bc = qc.assign_parameters({phi: 3.14})
1449
///         bc.measure_all()
1450
///         bc.draw("mpl")
1451
#[pyclass(sequence, subclass, module="qiskit._accelerate.circuit", extends=PyParameterExpression, name="Parameter")]
1452
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)]
1453
pub struct PyParameter {
1454
    pub symbol: Symbol,
1455
}
1456

1457
impl Hash for PyParameter {
1458
    fn hash<H: Hasher>(&self, state: &mut H) {
4,556,382✔
1459
        self.symbol.hash(state);
4,556,382✔
1460
    }
4,556,382✔
1461
}
1462

1463
// This needs to be implemented manually, since PyO3 does not provide this conversion
1464
// for subclasses.
1465
impl<'py> IntoPyObject<'py> for PyParameter {
1466
    type Target = PyParameter;
1467
    type Output = Bound<'py, Self::Target>;
1468
    type Error = PyErr;
1469

1470
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
9,450✔
1471
        let symbol = &self.symbol;
9,450✔
1472
        let symbol_expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
9,450✔
1473
        let expr = ParameterExpression::from_symbol_expr(symbol_expr);
9,450✔
1474
        let py_expr = PyParameterExpression::from(expr);
9,450✔
1475

1476
        Ok(Py::new(py, (self, py_expr))?.into_bound(py))
9,450✔
1477
    }
9,450✔
1478
}
1479

1480
impl PyParameter {
1481
    /// Get a Python class initialization from a symbol.
1482
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
4,672,674✔
1483
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
4,672,674✔
1484

1485
        let py_parameter = Self { symbol };
4,672,674✔
1486
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
4,672,674✔
1487

1488
        PyClassInitializer::from(py_expr).add_subclass(py_parameter)
4,672,674✔
1489
    }
4,672,674✔
1490

1491
    /// Get a reference to the underlying symbol.
1492
    pub fn symbol(&self) -> &Symbol {
4,557,126✔
1493
        &self.symbol
4,557,126✔
1494
    }
4,557,126✔
1495
}
1496

1497
#[pymethods]
×
1498
impl PyParameter {
1499
    /// Args:
1500
    ///     name: name of the parameter, used for visual representation. This can
1501
    ///         be any Unicode string, e.g. ``"Ï•"``.
1502
    ///     uuid: For advanced usage only.  Override the UUID of this parameter, in order to make it
1503
    ///         compare equal to some other parameter object.  By default, two parameters with the
1504
    ///         same name do not compare equal to help catch shadowing bugs when two circuits
1505
    ///         containing the same named parameters are spurious combined.  Setting the ``uuid``
1506
    ///         field when creating two parameters to the same thing (along with the same name)
1507
    ///         allows them to be equal.  This is useful during serialization and deserialization.
1508
    #[new]
1509
    #[pyo3(signature = (name, uuid=None))]
1510
    fn py_new(
84,886✔
1511
        py: Python<'_>,
84,886✔
1512
        name: String,
84,886✔
1513
        uuid: Option<Py<PyAny>>,
84,886✔
1514
    ) -> PyResult<PyClassInitializer<Self>> {
84,886✔
1515
        let uuid = uuid_from_py(py, uuid)?;
84,886✔
1516
        let symbol = Symbol::new(name.as_str(), uuid, None);
84,886✔
1517
        let expr = SymbolExpr::Symbol(Arc::new(symbol.clone()));
84,886✔
1518

1519
        let py_parameter = Self { symbol };
84,886✔
1520
        let py_expr: PyParameterExpression = ParameterExpression::from_symbol_expr(expr).into();
84,886✔
1521

1522
        Ok(PyClassInitializer::from(py_expr).add_subclass(py_parameter))
84,886✔
1523
    }
84,886✔
1524

1525
    /// Returns the name of the :class:`.Parameter`.
1526
    #[getter]
1527
    fn name<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
21,222✔
1528
        PyString::new(py, &self.symbol.repr(false))
21,222✔
1529
    }
21,222✔
1530

1531
    /// Returns the :class:`~uuid.UUID` of the :class:`Parameter`.
1532
    ///
1533
    /// In advanced use cases, this property can be passed to the
1534
    /// :class:`.Parameter` constructor to produce an instance that compares
1535
    /// equal to another instance.
1536
    #[getter]
1537
    fn uuid(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
48✔
1538
        uuid_to_py(py, self.symbol.uuid)
48✔
1539
    }
48✔
1540

1541
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
64✔
1542
        let str = format!("Parameter({})", self.symbol.repr(false),);
64✔
1543
        PyString::new(py, str.as_str())
64✔
1544
    }
64✔
1545

1546
    pub fn __getnewargs__(&self) -> (String, u128) {
25,008✔
1547
        (self.symbol.repr(false), self.symbol.uuid.as_u128())
25,008✔
1548
    }
25,008✔
1549

1550
    pub fn __getstate__(&self) -> (String, u128) {
25,008✔
1551
        (self.symbol.repr(false), self.symbol.uuid.as_u128())
25,008✔
1552
    }
25,008✔
1553

1554
    pub fn __setstate__(&mut self, state: (String, u128)) {
16✔
1555
        let name = state.0.as_str();
16✔
1556
        let uuid = Uuid::from_u128(state.1);
16✔
1557
        let symbol = Symbol::new(name, Some(uuid), None);
16✔
1558
        self.symbol = symbol;
16✔
1559
    }
16✔
1560

1561
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1562
        // Parameter is immutable. Note that this **cannot** be deferred to the parent class
1563
        // since PyO3 would then always return the parent type.
1564
        slf
×
1565
    }
×
1566

1567
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
151,038✔
1568
        // Everything inside a Parameter is immutable. Note that this **cannot** be deferred to the
1569
        // parent class since PyO3 would then always return the parent type.
1570
        slf
151,038✔
1571
    }
151,038✔
1572

1573
    #[pyo3(name = "subs")]
1574
    #[pyo3(signature = (parameter_map, allow_unknown_parameters=false))]
1575
    pub fn py_subs<'py>(
416✔
1576
        &self,
416✔
1577
        py: Python<'py>,
416✔
1578
        parameter_map: HashMap<PyParameter, Bound<'py, PyAny>>,
416✔
1579
        allow_unknown_parameters: bool,
416✔
1580
    ) -> PyResult<Bound<'py, PyAny>> {
416✔
1581
        // We implement this method on this class, and do not defer to the parent, such
1582
        // that x.subs({x: y}) remains a Parameter, and is not upgraded to an expression.
1583
        // Also this should be faster than going via ParameterExpression, which constructs
1584
        // intermediary HashMaps we don't need here.
1585
        match parameter_map.get(self) {
416✔
1586
            None => {
1587
                if allow_unknown_parameters {
4✔
1588
                    self.clone().into_bound_py_any(py)
2✔
1589
                } else {
1590
                    Err(CircuitError::new_err(
2✔
1591
                        "Cannot bind parameters not present in parameter.",
2✔
1592
                    ))
2✔
1593
                }
1594
            }
1595
            Some(replacement) => {
412✔
1596
                if allow_unknown_parameters || parameter_map.len() == 1 {
412✔
1597
                    Ok(replacement.clone())
412✔
1598
                } else {
1599
                    Err(CircuitError::new_err(
×
1600
                        "Cannot bind parameters not present in parameter.",
×
1601
                    ))
×
1602
                }
1603
            }
1604
        }
1605
    }
416✔
1606

1607
    #[pyo3(name = "bind")]
1608
    #[pyo3(signature = (parameter_values, allow_unknown_parameters=false))]
1609
    pub fn py_bind<'py>(
146✔
1610
        &self,
146✔
1611
        py: Python<'py>,
146✔
1612
        parameter_values: HashMap<PyParameter, Bound<'py, PyAny>>,
146✔
1613
        allow_unknown_parameters: bool,
146✔
1614
    ) -> PyResult<Bound<'py, PyAny>> {
146✔
1615
        // Returns PyAny to cover Parameter and ParameterExpression(value).
1616
        match parameter_values.get(self) {
146✔
1617
            None => {
1618
                if allow_unknown_parameters {
×
1619
                    self.clone().into_bound_py_any(py)
×
1620
                } else {
1621
                    Err(CircuitError::new_err(
×
1622
                        "Cannot bind parameters not present in parameter.",
×
1623
                    ))
×
1624
                }
1625
            }
1626
            Some(replacement) => {
146✔
1627
                if allow_unknown_parameters || parameter_values.len() == 1 {
146✔
1628
                    let expr = PyParameterExpression::extract_coerce(replacement.as_borrowed())?;
146✔
1629
                    if let SymbolExpr::Value(_) = &expr.inner.expr {
146✔
1630
                        expr.clone().into_bound_py_any(py)
146✔
1631
                    } else {
1632
                        Err(PyValueError::new_err("Invalid binding value."))
×
1633
                    }
1634
                } else {
1635
                    Err(CircuitError::new_err(
×
1636
                        "Cannot bind parameters not present in parameter.",
×
1637
                    ))
×
1638
                }
1639
            }
1640
        }
1641
    }
146✔
1642

1643
    #[pyo3(name = "bind_all")]
1644
    #[pyo3(signature = (values, *))]
1645
    pub fn py_bind_all<'py>(
4✔
1646
        slf_: Bound<'py, Self>,
4✔
1647
        values: Bound<'py, PyAny>,
4✔
1648
    ) -> PyResult<Bound<'py, PyAny>> {
4✔
1649
        values.get_item(slf_)
4✔
1650
    }
4✔
1651

1652
    #[pyo3(name = "assign")]
1653
    pub fn py_assign<'py>(
408✔
1654
        &self,
408✔
1655
        py: Python<'py>,
408✔
1656
        parameter: PyParameter,
408✔
1657
        value: &Bound<'py, PyAny>,
408✔
1658
    ) -> PyResult<Bound<'py, PyAny>> {
408✔
1659
        if value.cast::<PyParameterExpression>().is_ok() {
408✔
1660
            let map = [(parameter, value.clone())].into_iter().collect();
408✔
1661
            self.py_subs(py, map, false)
408✔
1662
        } else if value.extract::<Value>().is_ok() {
×
1663
            let map = [(parameter, value.clone())].into_iter().collect();
×
1664
            self.py_bind(py, map, false)
×
1665
        } else {
1666
            Err(PyValueError::new_err(
×
1667
                "Unexpected value in assign: {replacement:?}",
×
1668
            ))
×
1669
        }
1670
    }
408✔
1671
}
1672

1673
/// An element of a :class:`.ParameterVector`.
1674
///
1675
/// .. note::
1676
///     There is very little reason to ever construct this class directly.  Objects of this type are
1677
///     automatically constructed efficiently as part of creating a :class:`.ParameterVector`.
1678
#[pyclass(sequence, subclass, module="qiskit._accelerate.circuit", extends=PyParameter, name="ParameterVectorElement")]
1679
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
1680
pub struct PyParameterVectorElement {
1681
    pub symbol: Symbol,
1682
}
1683

1684
impl<'py> IntoPyObject<'py> for PyParameterVectorElement {
1685
    type Target = PyParameterVectorElement;
1686
    type Output = Bound<'py, Self::Target>;
1687
    type Error = PyErr;
1688

1689
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
10✔
1690
        let symbol = &self.symbol;
10✔
1691
        let py_param = PyParameter::from_symbol(symbol.clone());
10✔
1692
        let py_element = py_param.add_subclass(self);
10✔
1693

1694
        Ok(Py::new(py, py_element)?.into_bound(py))
10✔
1695
    }
10✔
1696
}
1697

1698
impl PyParameterVectorElement {
1699
    pub fn symbol(&self) -> &Symbol {
16,798✔
1700
        &self.symbol
16,798✔
1701
    }
16,798✔
1702

1703
    pub fn from_symbol(symbol: Symbol) -> PyClassInitializer<Self> {
4,556,124✔
1704
        let py_element = Self {
4,556,124✔
1705
            symbol: symbol.clone(),
4,556,124✔
1706
        };
4,556,124✔
1707
        let py_parameter = PyParameter::from_symbol(symbol);
4,556,124✔
1708

1709
        py_parameter.add_subclass(py_element)
4,556,124✔
1710
    }
4,556,124✔
1711
}
1712

1713
#[pymethods]
×
1714
impl PyParameterVectorElement {
1715
    #[new]
1716
    #[pyo3(signature = (vector, index, uuid=None))]
1717
    pub fn py_new(
27,622✔
1718
        py: Python<'_>,
27,622✔
1719
        vector: Py<PyAny>,
27,622✔
1720
        index: u32,
27,622✔
1721
        uuid: Option<Py<PyAny>>,
27,622✔
1722
    ) -> PyResult<PyClassInitializer<Self>> {
27,622✔
1723
        let vector_name = vector.getattr(py, "name")?.extract::<String>(py)?;
27,622✔
1724
        let uuid = uuid_from_py(py, uuid)?.unwrap_or(Uuid::new_v4());
27,622✔
1725

1726
        let symbol = Symbol::py_new(
27,622✔
1727
            &vector_name,
27,622✔
1728
            Some(uuid.as_u128()),
27,622✔
1729
            Some(index),
27,622✔
1730
            Some(vector.clone_ref(py)),
27,622✔
1731
        )?;
×
1732

1733
        let py_parameter = PyParameter::from_symbol(symbol.clone());
27,622✔
1734
        let py_element = Self { symbol };
27,622✔
1735

1736
        Ok(py_parameter.add_subclass(py_element))
27,622✔
1737
    }
27,622✔
1738

1739
    pub fn __getnewargs__(&self, py: Python) -> PyResult<(Py<PyAny>, u32, Option<Py<PyAny>>)> {
8✔
1740
        let vector = self
8✔
1741
            .symbol
8✔
1742
            .vector
8✔
1743
            .clone()
8✔
1744
            .expect("vector element should have a vector");
8✔
1745
        let index = self
8✔
1746
            .symbol
8✔
1747
            .index
8✔
1748
            .expect("vector element should have an index");
8✔
1749
        let uuid = uuid_to_py(py, self.symbol.uuid)?;
8✔
1750
        Ok((vector, index, Some(uuid)))
8✔
1751
    }
8✔
1752

1753
    pub fn __repr__<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
48✔
1754
        let str = format!("ParameterVectorElement({})", self.symbol.repr(false),);
48✔
1755
        PyString::new(py, str.as_str())
48✔
1756
    }
48✔
1757

1758
    pub fn __getstate__(&self, py: Python) -> PyResult<(Py<PyAny>, u32, Option<Py<PyAny>>)> {
8✔
1759
        self.__getnewargs__(py)
8✔
1760
    }
8✔
1761

1762
    pub fn __setstate__(
×
1763
        &mut self,
×
1764
        py: Python,
×
1765
        state: (Py<PyAny>, u32, Option<Py<PyAny>>),
×
1766
    ) -> PyResult<()> {
×
1767
        let vector = state.0;
×
1768
        let index = state.1;
×
1769
        let vector_name = vector.getattr(py, "name")?.extract::<String>(py)?;
×
1770
        let uuid = uuid_from_py(py, state.2)?.map(|id| id.as_u128());
×
1771
        self.symbol = Symbol::py_new(&vector_name, uuid, Some(index), Some(vector))?;
×
1772
        Ok(())
×
1773
    }
×
1774

1775
    /// Get the index of this element in the parent vector.
1776
    #[getter]
1777
    pub fn index(&self) -> u32 {
12✔
1778
        self.symbol
12✔
1779
            .index
12✔
1780
            .expect("A vector element should have an index")
12✔
1781
    }
12✔
1782

1783
    /// Get the parent vector instance.
1784
    #[getter]
1785
    pub fn vector(&self) -> Py<PyAny> {
20✔
1786
        self.symbol
20✔
1787
            .clone()
20✔
1788
            .vector
20✔
1789
            .expect("A vector element should have a vector")
20✔
1790
    }
20✔
1791

1792
    /// For backward compatibility only. This should not be used and we ought to update those
1793
    /// usages!
1794
    #[getter]
1795
    pub fn _vector(&self) -> Py<PyAny> {
×
1796
        self.vector()
×
1797
    }
×
1798

1799
    fn __copy__(slf: PyRef<Self>) -> PyRef<Self> {
×
1800
        // ParameterVectorElement is immutable.
1801
        slf
×
1802
    }
×
1803

1804
    fn __deepcopy__<'py>(slf: PyRef<'py, Self>, _memo: Bound<'py, PyAny>) -> PyRef<'py, Self> {
10,988✔
1805
        // Everything a ParameterVectorElement contains is immutable.
1806
        slf
10,988✔
1807
    }
10,988✔
1808
}
1809

1810
/// Try to extract a Uuid from a Python object, which could be a Python UUID or int.
1811
fn uuid_from_py(py: Python<'_>, uuid: Option<Py<PyAny>>) -> PyResult<Option<Uuid>> {
112,508✔
1812
    if let Some(val) = uuid {
112,508✔
1813
        // construct from u128
1814
        let as_u128 = if let Ok(as_u128) = val.extract::<u128>(py) {
27,674✔
1815
            as_u128
16✔
1816
        // construct from Python UUID type
1817
        } else if val.bind(py).is_exact_instance(UUID.get_bound(py)) {
27,658✔
1818
            val.getattr(py, "int")?.extract::<u128>(py)?
27,658✔
1819
        // invalid format
1820
        } else {
1821
            return Err(PyTypeError::new_err("not a UUID!"));
×
1822
        };
1823
        Ok(Some(Uuid::from_u128(as_u128)))
27,674✔
1824
    } else {
1825
        Ok(None)
84,834✔
1826
    }
1827
}
112,508✔
1828

1829
/// Convert a Rust Uuid object to a Python UUID object.
1830
fn uuid_to_py(py: Python<'_>, uuid: Uuid) -> PyResult<Py<PyAny>> {
56✔
1831
    let uuid = uuid.as_u128();
56✔
1832
    let kwargs = [("int", uuid)].into_py_dict(py)?;
56✔
1833
    Ok(UUID.get_bound(py).call((), Some(&kwargs))?.unbind())
56✔
1834
}
56✔
1835

1836
/// Extract a [Symbol] for a Python object, which could either be a Parameter or a
1837
/// ParameterVectorElement.
1838
fn symbol_from_py_parameter(param: &Bound<'_, PyAny>) -> PyResult<Symbol> {
74✔
1839
    if let Ok(element) = param.extract::<PyParameterVectorElement>() {
74✔
1840
        Ok(element.symbol.clone())
×
1841
    } else if let Ok(parameter) = param.extract::<PyParameter>() {
74✔
1842
        Ok(parameter.symbol.clone())
74✔
1843
    } else {
1844
        Err(PyValueError::new_err("Could not extract parameter"))
×
1845
    }
1846
}
74✔
1847

1848
/// A singular parameter value used for QPY serialization. This covers anything
1849
/// but a [PyParameterExpression], which is represented by [None] in the serialization.
1850
#[derive(IntoPyObject, FromPyObject, Clone, Debug)]
1851
pub enum ParameterValueType {
1852
    Int(i64),
1853
    Float(f64),
1854
    Complex(Complex64),
1855
    Parameter(PyParameter),
1856
    VectorElement(PyParameterVectorElement),
1857
}
1858

1859
impl ParameterValueType {
1860
    fn extract_from_expr(expr: &SymbolExpr) -> Option<ParameterValueType> {
18,910✔
1861
        if let Some(value) = expr.eval(true) {
18,910✔
1862
            match value {
7,760✔
1863
                Value::Int(i) => Some(ParameterValueType::Int(i)),
100✔
1864
                Value::Real(r) => Some(ParameterValueType::Float(r)),
7,652✔
1865
                Value::Complex(c) => Some(ParameterValueType::Complex(c)),
8✔
1866
            }
1867
        } else if let SymbolExpr::Symbol(symbol) = expr {
11,150✔
1868
            match symbol.index {
9,742✔
1869
                None => {
1870
                    let param = PyParameter {
9,566✔
1871
                        symbol: symbol.as_ref().clone(),
9,566✔
1872
                    };
9,566✔
1873
                    Some(ParameterValueType::Parameter(param))
9,566✔
1874
                }
1875
                Some(_) => {
1876
                    let param = PyParameterVectorElement {
176✔
1877
                        symbol: symbol.as_ref().clone(),
176✔
1878
                    };
176✔
1879
                    Some(ParameterValueType::VectorElement(param))
176✔
1880
                }
1881
            }
1882
        } else {
1883
            // ParameterExpressions have the value None, as they must be constructed
1884
            None
1,408✔
1885
        }
1886
    }
18,910✔
1887
}
1888

1889
impl From<ParameterValueType> for ParameterExpression {
1890
    fn from(value: ParameterValueType) -> Self {
544✔
1891
        match value {
544✔
1892
            ParameterValueType::Parameter(param) => {
280✔
1893
                let expr = SymbolExpr::Symbol(Arc::new(param.symbol));
280✔
1894
                Self::from_symbol_expr(expr)
280✔
1895
            }
1896
            ParameterValueType::VectorElement(param) => {
×
1897
                let expr = SymbolExpr::Symbol(Arc::new(param.symbol));
×
1898
                Self::from_symbol_expr(expr)
×
1899
            }
1900
            ParameterValueType::Int(i) => {
44✔
1901
                let expr = SymbolExpr::Value(Value::Int(i));
44✔
1902
                Self::from_symbol_expr(expr)
44✔
1903
            }
1904
            ParameterValueType::Float(f) => {
220✔
1905
                let expr = SymbolExpr::Value(Value::Real(f));
220✔
1906
                Self::from_symbol_expr(expr)
220✔
1907
            }
1908
            ParameterValueType::Complex(c) => {
×
1909
                let expr = SymbolExpr::Value(Value::Complex(c));
×
1910
                Self::from_symbol_expr(expr)
×
1911
            }
1912
        }
1913
    }
544✔
1914
}
1915

1916
#[pyclass(module = "qiskit._accelerate.circuit")]
×
1917
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
1918
#[repr(u8)]
1919
pub enum OpCode {
1920
    ADD = 0,
1921
    SUB = 1,
1922
    MUL = 2,
1923
    DIV = 3,
1924
    POW = 4,
1925
    SIN = 5,
1926
    COS = 6,
1927
    TAN = 7,
1928
    ASIN = 8,
1929
    ACOS = 9,
1930
    EXP = 10,
1931
    LOG = 11,
1932
    SIGN = 12,
1933
    GRAD = 13, // for backward compatibility, unused in Rust's ParameterExpression
1934
    CONJ = 14,
1935
    SUBSTITUTE = 15, // for backward compatibility, unused in Rust's ParameterExpression
1936
    ABS = 16,
1937
    ATAN = 17,
1938
    RSUB = 18,
1939
    RDIV = 19,
1940
    RPOW = 20,
1941
}
1942

1943
impl From<OpCode> for u8 {
1944
    fn from(value: OpCode) -> Self {
×
1945
        value as u8
×
1946
    }
×
1947
}
1948

1949
unsafe impl ::bytemuck::CheckedBitPattern for OpCode {
1950
    type Bits = u8;
1951

1952
    #[inline(always)]
1953
    fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
388✔
1954
        *bits <= 20
388✔
1955
    }
388✔
1956
}
1957

1958
unsafe impl ::bytemuck::NoUninit for OpCode {}
1959

1960
impl OpCode {
1961
    pub fn from_u8(value: u8) -> PyResult<OpCode> {
388✔
1962
        Ok(bytemuck::checked::cast::<u8, OpCode>(value))
388✔
1963
    }
388✔
1964
}
1965

1966
#[pymethods]
×
1967
impl OpCode {
1968
    #[new]
1969
    fn py_new(value: u8) -> PyResult<Self> {
×
1970
        let code: OpCode = ::bytemuck::checked::try_cast(value)
×
1971
            .map_err(|_| ParameterError::InvalidU8ToOpCode(value))?;
×
1972
        Ok(code)
×
1973
    }
×
1974

1975
    fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
560✔
1976
        if let Ok(code) = other.cast::<OpCode>() {
560✔
1977
            *code.borrow() == *self
560✔
1978
        } else {
1979
            false
×
1980
        }
1981
    }
560✔
1982

1983
    fn __hash__(&self) -> u8 {
2,088✔
1984
        *self as u8
2,088✔
1985
    }
2,088✔
1986

1987
    fn __getnewargs__(&self) -> (u8,) {
9,396✔
1988
        (*self as u8,)
9,396✔
1989
    }
9,396✔
1990
}
1991

1992
// enum for QPY replay
1993
#[pyclass(sequence, module = "qiskit._accelerate.circuit")]
1994
#[derive(Clone, Debug)]
1995
pub struct OPReplay {
1996
    pub op: OpCode,
1997
    pub lhs: Option<ParameterValueType>,
1998
    pub rhs: Option<ParameterValueType>,
1999
}
2000

2001
#[pymethods]
×
2002
impl OPReplay {
2003
    #[new]
2004
    pub fn py_new(
×
2005
        op: OpCode,
×
2006
        lhs: Option<ParameterValueType>,
×
2007
        rhs: Option<ParameterValueType>,
×
2008
    ) -> OPReplay {
×
2009
        OPReplay { op, lhs, rhs }
×
2010
    }
×
2011

2012
    #[getter]
2013
    fn op(&self) -> OpCode {
802✔
2014
        self.op
802✔
2015
    }
802✔
2016

2017
    #[getter]
2018
    fn lhs(&self) -> Option<ParameterValueType> {
238✔
2019
        self.lhs.clone()
238✔
2020
    }
238✔
2021

2022
    #[getter]
2023
    fn rhs(&self) -> Option<ParameterValueType> {
238✔
2024
        self.rhs.clone()
238✔
2025
    }
238✔
2026

2027
    fn __getnewargs__(
9,396✔
2028
        &self,
9,396✔
2029
    ) -> (
9,396✔
2030
        OpCode,
9,396✔
2031
        Option<ParameterValueType>,
9,396✔
2032
        Option<ParameterValueType>,
9,396✔
2033
    ) {
9,396✔
2034
        (self.op, self.lhs.clone(), self.rhs.clone())
9,396✔
2035
    }
9,396✔
2036
}
2037

2038
/// Internal helper. Extract one part of the expression tree, keeping the name map up to date.
2039
///
2040
/// Example: Given expr1 + expr2, each being [PyParameterExpression], we need the ability to
2041
/// extract one of the expressions with the proper name map.
2042
///
2043
/// Args:
2044
///     - joint_parameter_expr: The full expression, e.g. expr1 + expr2.
2045
///     - sub_expr: The sub expression, on whose symbols we restrict the name map.
2046
fn filter_name_map(
18,910✔
2047
    sub_expr: &SymbolExpr,
18,910✔
2048
    name_map: &HashMap<String, Symbol>,
18,910✔
2049
) -> ParameterExpression {
18,910✔
2050
    let sub_symbols: HashSet<&Symbol> = sub_expr.iter_symbols().collect();
18,910✔
2051
    let restricted_name_map: HashMap<String, Symbol> = name_map
18,910✔
2052
        .iter()
18,910✔
2053
        .filter(|(_, symbol)| sub_symbols.contains(*symbol))
24,038✔
2054
        .map(|(name, symbol)| (name.clone(), symbol.clone()))
18,910✔
2055
        .collect();
18,910✔
2056

2057
    ParameterExpression {
18,910✔
2058
        expr: sub_expr.clone(),
18,910✔
2059
        name_map: restricted_name_map,
18,910✔
2060
    }
18,910✔
2061
}
18,910✔
2062

2063
pub fn qpy_replay(
27,528✔
2064
    expr: &ParameterExpression,
27,528✔
2065
    name_map: &HashMap<String, Symbol>,
27,528✔
2066
    replay: &mut Vec<OPReplay>,
27,528✔
2067
) {
27,528✔
2068
    match &expr.expr {
27,528✔
2069
        SymbolExpr::Value(_) | SymbolExpr::Symbol(_) => {
17,502✔
2070
            // nothing to do here, we only need to traverse instructions
17,502✔
2071
        }
17,502✔
2072
        SymbolExpr::Unary { op, expr } => {
1,142✔
2073
            let op = match op {
1,142✔
2074
                symbol_expr::UnaryOp::Abs => OpCode::ABS,
6✔
2075
                symbol_expr::UnaryOp::Acos => OpCode::ACOS,
6✔
2076
                symbol_expr::UnaryOp::Asin => OpCode::ASIN,
6✔
2077
                symbol_expr::UnaryOp::Atan => OpCode::ATAN,
6✔
2078
                symbol_expr::UnaryOp::Conj => OpCode::CONJ,
6✔
2079
                symbol_expr::UnaryOp::Cos => OpCode::COS,
6✔
2080
                symbol_expr::UnaryOp::Exp => OpCode::EXP,
6✔
2081
                symbol_expr::UnaryOp::Log => OpCode::LOG,
6✔
2082
                symbol_expr::UnaryOp::Neg => OpCode::MUL,
1,066✔
2083
                symbol_expr::UnaryOp::Sign => OpCode::SIGN,
4✔
2084
                symbol_expr::UnaryOp::Sin => OpCode::SIN,
18✔
2085
                symbol_expr::UnaryOp::Tan => OpCode::TAN,
6✔
2086
            };
2087
            // TODO filter shouldn't be necessary for unary ops
2088
            let lhs = filter_name_map(expr, name_map);
1,142✔
2089

2090
            // recurse on the instruction
2091
            qpy_replay(&lhs, name_map, replay);
1,142✔
2092

2093
            let lhs_value = ParameterValueType::extract_from_expr(expr);
1,142✔
2094

2095
            // MUL is special: we implement ``neg`` as multiplication by -1
2096
            if let OpCode::MUL = &op {
1,142✔
2097
                replay.push(OPReplay {
1,066✔
2098
                    op,
1,066✔
2099
                    lhs: lhs_value,
1,066✔
2100
                    rhs: Some(ParameterValueType::Int(-1)),
1,066✔
2101
                });
1,066✔
2102
            } else {
1,066✔
2103
                replay.push(OPReplay {
76✔
2104
                    op,
76✔
2105
                    lhs: lhs_value,
76✔
2106
                    rhs: None,
76✔
2107
                });
76✔
2108
            }
76✔
2109
        }
2110
        SymbolExpr::Binary { op, lhs, rhs } => {
8,884✔
2111
            let lhs_value = ParameterValueType::extract_from_expr(lhs);
8,884✔
2112
            let rhs_value = ParameterValueType::extract_from_expr(rhs);
8,884✔
2113

2114
            // recurse on the parameter expressions
2115
            let lhs = filter_name_map(lhs, name_map);
8,884✔
2116
            let rhs = filter_name_map(rhs, name_map);
8,884✔
2117
            qpy_replay(&lhs, name_map, replay);
8,884✔
2118
            qpy_replay(&rhs, name_map, replay);
8,884✔
2119

2120
            // add the expression to the replay
2121
            match lhs_value {
8,630✔
2122
                None
2123
                | Some(ParameterValueType::Parameter(_))
2124
                | Some(ParameterValueType::VectorElement(_)) => {
2125
                    let op = match op {
1,278✔
2126
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
744✔
2127
                        symbol_expr::BinaryOp::Sub => OpCode::SUB,
416✔
2128
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
86✔
2129
                        symbol_expr::BinaryOp::Div => OpCode::DIV,
12✔
2130
                        symbol_expr::BinaryOp::Pow => OpCode::POW,
20✔
2131
                    };
2132
                    replay.push(OPReplay {
1,278✔
2133
                        op,
1,278✔
2134
                        lhs: lhs_value,
1,278✔
2135
                        rhs: rhs_value,
1,278✔
2136
                    });
1,278✔
2137
                }
2138
                _ => {
2139
                    let op = match op {
7,606✔
2140
                        symbol_expr::BinaryOp::Add => OpCode::ADD,
578✔
2141
                        symbol_expr::BinaryOp::Sub => OpCode::RSUB,
120✔
2142
                        symbol_expr::BinaryOp::Mul => OpCode::MUL,
6,900✔
2143
                        symbol_expr::BinaryOp::Div => OpCode::RDIV,
4✔
2144
                        symbol_expr::BinaryOp::Pow => OpCode::RPOW,
4✔
2145
                    };
2146
                    if let OpCode::ADD | OpCode::MUL = op {
7,606✔
2147
                        replay.push(OPReplay {
7,478✔
2148
                            op,
7,478✔
2149
                            lhs: lhs_value,
7,478✔
2150
                            rhs: rhs_value,
7,478✔
2151
                        });
7,478✔
2152
                    } else {
7,478✔
2153
                        // this covers RSUB, RDIV, RPOW, hence we swap lhs and rhs
128✔
2154
                        replay.push(OPReplay {
128✔
2155
                            op,
128✔
2156
                            lhs: rhs_value,
128✔
2157
                            rhs: lhs_value,
128✔
2158
                        });
128✔
2159
                    }
128✔
2160
                }
2161
            }
2162
        }
2163
    }
2164
}
27,528✔
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