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

Qiskit / qiskit / 14603188292

22 Apr 2025 07:07PM UTC coverage: 88.19% (-0.08%) from 88.268%
14603188292

push

github

web-flow
Port classical expressions (`Expr`) to Rust. (#14068)

* Initial commit.

* Implement types.

* Wire-up Rust-based Types.

* Add more plumbing for Expr.

* Expose Op enums.

* WIP

* WIP

* Test passing.

* Refactor module organization.

* Fix clippy.

* Downcast instead of extract.

* Fix merge.

* Add missing documentation, fix opcode docs.

* Impl repr for expressions.

* Fix pickling for facade enums.

* Manually revert DAGCircuit changes.

I'll do these in a separate PR.

* Fix lint.

* Use fast-rng feature for uuid.

* Address review comments.

* Address another review comment.

* Update Cargo.lock.

678 of 804 new or added lines in 15 files covered. (84.33%)

19 existing lines in 5 files now uncovered.

74240 of 84182 relevant lines covered (88.19%)

437024.29 hits per line

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

31.2
/crates/circuit/src/classical/expr/expr.rs
1
// This code is part of Qiskit.
2
//
3
// (C) Copyright IBM 2025
4
//
5
// This code is licensed under the Apache License, Version 2.0. You may
6
// obtain a copy of this license in the LICENSE.txt file in the root directory
7
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8
//
9
// Any modifications or derivative works of this code must retain this
10
// copyright notice, and modified files need to carry a notice indicating
11
// that they have been altered from the originals.
12

13
use crate::classical::expr::{Binary, Cast, Index, Stretch, Unary, Value, Var};
14
use crate::classical::types::Type;
15
use pyo3::prelude::*;
16
use pyo3::{intern, IntoPyObjectExt};
17

18
/// A classical expression.
19
///
20
/// Variants that themselves contain [Expr]s are boxed. This is done instead
21
/// of boxing the contained [Expr]s within the specific type to reduce the
22
/// number of boxes we need (e.g. Binary would otherwise contain two boxed
23
/// expressions).
24
#[derive(Clone, Debug, PartialEq)]
25
pub enum Expr {
26
    Unary(Box<Unary>),
27
    Binary(Box<Binary>),
28
    Cast(Box<Cast>),
29
    Value(Value),
30
    Var(Var),
31
    Stretch(Stretch),
32
    Index(Box<Index>),
33
}
34

35
impl Expr {
36
    /// The const-ness of the expression.
37
    pub fn is_const(&self) -> bool {
4,452✔
38
        match self {
4,452✔
39
            Expr::Unary(u) => u.constant,
38✔
40
            Expr::Binary(b) => b.constant,
666✔
41
            Expr::Cast(c) => c.constant,
106✔
42
            Expr::Value(_) => true,
1,780✔
43
            Expr::Var(_) => false,
1,794✔
44
            Expr::Stretch(_) => true,
62✔
45
            Expr::Index(i) => i.constant,
6✔
46
        }
47
    }
4,452✔
48

49
    /// The expression's [Type].
NEW
50
    pub fn ty(&self) -> Type {
×
NEW
51
        match self {
×
NEW
52
            Expr::Unary(u) => u.ty,
×
NEW
53
            Expr::Binary(b) => b.ty,
×
NEW
54
            Expr::Cast(c) => c.ty,
×
NEW
55
            Expr::Value(v) => match v {
×
NEW
56
                Value::Duration(_) => Type::Duration,
×
NEW
57
                Value::Float { ty, .. } => *ty,
×
NEW
58
                Value::Uint { ty, .. } => *ty,
×
59
            },
NEW
60
            Expr::Var(v) => match v {
×
NEW
61
                Var::Standalone { ty, .. } => *ty,
×
NEW
62
                Var::Bit { .. } => Type::Bool,
×
NEW
63
                Var::Register { ty, .. } => *ty,
×
64
            },
NEW
65
            Expr::Stretch(_) => Type::Duration,
×
NEW
66
            Expr::Index(i) => i.ty,
×
67
        }
NEW
68
    }
×
69

70
    /// Returns an iterator over the [Var] nodes in this expression in some
71
    /// deterministic order.
NEW
72
    pub fn vars(&self) -> impl Iterator<Item = &Var> {
×
NEW
73
        VarIterator(ExprIterator { stack: vec![self] })
×
NEW
74
    }
×
75
}
76

77
/// A private iterator over the [Expr] nodes of an expression
78
/// by reference.
79
///
80
/// The first node reference returned is the [Expr] itself.
81
struct ExprIterator<'a> {
82
    stack: Vec<&'a Expr>,
83
}
84

85
impl<'a> Iterator for ExprIterator<'a> {
86
    type Item = &'a Expr;
87

NEW
88
    fn next(&mut self) -> Option<Self::Item> {
×
NEW
89
        let expr = self.stack.pop()?;
×
NEW
90
        match expr {
×
NEW
91
            Expr::Unary(u) => {
×
NEW
92
                self.stack.push(&u.operand);
×
NEW
93
            }
×
NEW
94
            Expr::Binary(b) => {
×
NEW
95
                self.stack.push(&b.left);
×
NEW
96
                self.stack.push(&b.right);
×
NEW
97
            }
×
NEW
98
            Expr::Cast(c) => self.stack.push(&c.operand),
×
NEW
99
            Expr::Value(_) => {}
×
NEW
100
            Expr::Var(_) => {}
×
NEW
101
            Expr::Stretch(_) => {}
×
NEW
102
            Expr::Index(i) => {
×
NEW
103
                self.stack.push(&i.index);
×
NEW
104
                self.stack.push(&i.target);
×
NEW
105
            }
×
106
        }
NEW
107
        Some(expr)
×
NEW
108
    }
×
109
}
110

111
/// A private iterator over the [Var] nodes contained within an [Expr].
112
struct VarIterator<'a>(ExprIterator<'a>);
113

114
impl<'a> Iterator for VarIterator<'a> {
115
    type Item = &'a Var;
116

NEW
117
    fn next(&mut self) -> Option<Self::Item> {
×
NEW
118
        for expr in self.0.by_ref() {
×
NEW
119
            if let Expr::Var(v) = expr {
×
NEW
120
                return Some(v);
×
NEW
121
            }
×
122
        }
NEW
123
        None
×
NEW
124
    }
×
125
}
126

127
impl From<Unary> for Expr {
NEW
128
    fn from(value: Unary) -> Self {
×
NEW
129
        Expr::Unary(Box::new(value))
×
NEW
130
    }
×
131
}
132

133
impl From<Box<Unary>> for Expr {
NEW
134
    fn from(value: Box<Unary>) -> Self {
×
NEW
135
        Expr::Unary(value)
×
NEW
136
    }
×
137
}
138

139
impl From<Binary> for Expr {
NEW
140
    fn from(value: Binary) -> Self {
×
NEW
141
        Expr::Binary(Box::new(value))
×
NEW
142
    }
×
143
}
144

145
impl From<Box<Binary>> for Expr {
NEW
146
    fn from(value: Box<Binary>) -> Self {
×
NEW
147
        Expr::Binary(value)
×
NEW
148
    }
×
149
}
150

151
impl From<Cast> for Expr {
NEW
152
    fn from(value: Cast) -> Self {
×
NEW
153
        Expr::Cast(Box::new(value))
×
NEW
154
    }
×
155
}
156

157
impl From<Box<Cast>> for Expr {
NEW
158
    fn from(value: Box<Cast>) -> Self {
×
NEW
159
        Expr::Cast(value)
×
NEW
160
    }
×
161
}
162

163
impl From<Value> for Expr {
NEW
164
    fn from(value: Value) -> Self {
×
NEW
165
        Expr::Value(value)
×
NEW
166
    }
×
167
}
168

169
impl From<Var> for Expr {
NEW
170
    fn from(value: Var) -> Self {
×
NEW
171
        Expr::Var(value)
×
NEW
172
    }
×
173
}
174

175
impl From<Stretch> for Expr {
NEW
176
    fn from(value: Stretch) -> Self {
×
NEW
177
        Expr::Stretch(value)
×
NEW
178
    }
×
179
}
180

181
impl From<Index> for Expr {
NEW
182
    fn from(value: Index) -> Self {
×
NEW
183
        Expr::Index(Box::new(value))
×
NEW
184
    }
×
185
}
186

187
impl From<Box<Index>> for Expr {
NEW
188
    fn from(value: Box<Index>) -> Self {
×
NEW
189
        Expr::Index(value)
×
NEW
190
    }
×
191
}
192

193
/// Root base class of all nodes in the expression tree.  The base case should never be
194
/// instantiated directly.
195
///
196
/// This must not be subclassed by users; subclasses form the internal data of the representation of
197
/// expressions, and it does not make sense to add more outside of Qiskit library code.
198
///
199
/// All subclasses are responsible for setting their ``type`` attribute in their ``__init__``, and
200
/// should not call the parent initializer."""
201
#[pyclass(
12✔
202
    eq,
12✔
203
    hash,
12✔
204
    subclass,
12✔
205
    frozen,
12✔
206
    name = "Expr",
12✔
207
    module = "qiskit._accelerate.circuit.classical.expr"
12✔
208
)]
12✔
209
#[derive(PartialEq, Clone, Copy, Debug, Hash)]
210
pub struct PyExpr(pub ExprKind); // ExprKind is used for fast extraction from Python
211

NEW
212
#[pymethods]
×
213
impl PyExpr {
214
    /// Call the relevant ``visit_*`` method on the given :class:`ExprVisitor`.  The usual entry
215
    /// point for a simple visitor is to construct it, and then call :meth:`accept` on the root
216
    /// object to be visited.  For example::
217
    ///
218
    ///     expr = ...
219
    ///     visitor = MyVisitor()
220
    ///     visitor.accept(expr)
221
    ///
222
    /// Subclasses of :class:`Expr` should override this to call the correct virtual method on the
223
    /// visitor.  This implements double dispatch with the visitor."""
224
    /// return visitor.visit_generic(self)
NEW
225
    fn accept<'py>(
×
NEW
226
        slf: PyRef<'py, Self>,
×
NEW
227
        visitor: &Bound<'py, PyAny>,
×
NEW
228
    ) -> PyResult<Bound<'py, PyAny>> {
×
NEW
229
        visitor.call_method1(intern!(visitor.py(), "visit_generic"), (slf,))
×
NEW
230
    }
×
231
}
232

233
/// The expression's kind, used internally during Python instance extraction to avoid
234
/// `isinstance` checks.
235
#[repr(u8)]
236
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
237
pub enum ExprKind {
238
    Unary,
239
    Binary,
240
    Value,
241
    Var,
242
    Cast,
243
    Stretch,
244
    Index,
245
}
246

247
impl<'py> IntoPyObject<'py> for Expr {
248
    type Target = PyAny;
249
    type Output = Bound<'py, PyAny>;
250
    type Error = PyErr;
251

252
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
16,252✔
253
        match self {
16,252✔
254
            Expr::Unary(u) => u.into_bound_py_any(py),
200✔
255
            Expr::Binary(b) => b.into_bound_py_any(py),
4,250✔
256
            Expr::Cast(c) => c.into_bound_py_any(py),
102✔
257
            Expr::Value(v) => v.into_bound_py_any(py),
6,860✔
258
            Expr::Var(v) => v.into_bound_py_any(py),
4,714✔
259
            Expr::Stretch(s) => s.into_bound_py_any(py),
98✔
260
            Expr::Index(i) => i.into_bound_py_any(py),
28✔
261
        }
262
    }
16,252✔
263
}
264

265
impl<'py> FromPyObject<'py> for Expr {
266
    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
5,946✔
267
        let expr: PyRef<'_, PyExpr> = ob.downcast()?.borrow();
5,946✔
268
        match expr.0 {
5,946✔
269
            ExprKind::Unary => Ok(Expr::Unary(Box::new(ob.extract()?))),
80✔
270
            ExprKind::Binary => Ok(Expr::Binary(Box::new(ob.extract()?))),
824✔
271
            ExprKind::Value => Ok(Expr::Value(ob.extract()?)),
2,668✔
272
            ExprKind::Var => Ok(Expr::Var(ob.extract()?)),
2,174✔
273
            ExprKind::Cast => Ok(Expr::Cast(Box::new(ob.extract()?))),
126✔
274
            ExprKind::Stretch => Ok(Expr::Stretch(ob.extract()?)),
62✔
275
            ExprKind::Index => Ok(Expr::Index(Box::new(ob.extract()?))),
12✔
276
        }
277
    }
5,946✔
278
}
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