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

Qiskit / qiskit / 26281746248

22 May 2026 10:10AM UTC coverage: 87.51% (+0.02%) from 87.494%
26281746248

Pull #16202

github

web-flow
Merge 40b251a61 into 376781740
Pull Request #16202: Make builtin qasm2.CustomClassical extensions Rust-native

209 of 220 new or added lines in 5 files covered. (95.0%)

10 existing lines in 2 files now uncovered.

108493 of 123978 relevant lines covered (87.51%)

957838.68 hits per line

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

93.89
/crates/qasm2/src/bytecode.rs
1
// This code is part of Qiskit.
2
//
3
// (C) Copyright IBM 2023
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 num_bigint::BigUint;
14
use pyo3::exceptions::PyTypeError;
15
use pyo3::prelude::*;
16
use pyo3::types::PyTuple;
17

18
use crate::error::QASM2ParseError;
19
use crate::expr::Expr;
20
use crate::parse::{ClbitId, CregId, GateId, ParamId, QubitId};
21
use crate::{ClassicalCallableExt, CustomClassical, CustomInstruction, lex, parse};
22

23
impl<'py> IntoPyObject<'py> for Expr {
24
    type Target = PyAny;
25
    type Output = Bound<'py, Self::Target>;
26
    type Error = PyErr;
27

28
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
450✔
29
        match self {
450✔
30
            Expr::Constant(value) => Ok(Py::new(py, ExprConstant { value })?
166✔
31
                .into_bound(py)
166✔
32
                .into_any()),
166✔
33
            Expr::Parameter(index) => Ok(Py::new(py, ExprArgument { index })?
162✔
34
                .into_bound(py)
162✔
35
                .into_any()),
162✔
36
            Expr::Negate(expr) => {
8✔
37
                let arg_py = (*expr).into_pyobject(py)?.unbind();
8✔
38
                Ok(Py::new(
8✔
39
                    py,
8✔
40
                    ExprUnary {
8✔
41
                        opcode: UnaryOpCode::Negate,
8✔
42
                        argument: arg_py,
8✔
43
                    },
8✔
NEW
44
                )?
×
45
                .into_bound(py)
8✔
46
                .into_any())
8✔
47
            }
48
            Expr::Function(func, expr) => {
40✔
49
                let arg_py = (*expr).into_pyobject(py)?.unbind();
40✔
50
                Ok(Py::new(
40✔
51
                    py,
40✔
52
                    ExprUnary {
40✔
53
                        opcode: UnaryOpCode::from(func),
40✔
54
                        argument: arg_py,
40✔
55
                    },
40✔
NEW
56
                )?
×
57
                .into_bound(py)
40✔
58
                .into_any())
40✔
59
            }
60
            Expr::Add(left, right) => {
18✔
61
                let left_py = (*left).into_pyobject(py)?.unbind();
18✔
62
                let right_py = (*right).into_pyobject(py)?.unbind();
18✔
63
                Ok(Py::new(
18✔
64
                    py,
18✔
65
                    ExprBinary {
18✔
66
                        opcode: BinaryOpCode::Add,
18✔
67
                        left: left_py,
18✔
68
                        right: right_py,
18✔
69
                    },
18✔
NEW
70
                )?
×
71
                .into_bound(py)
18✔
72
                .into_any())
18✔
73
            }
74
            Expr::Subtract(left, right) => {
8✔
75
                let left_py = (*left).into_pyobject(py)?.unbind();
8✔
76
                let right_py = (*right).into_pyobject(py)?.unbind();
8✔
77
                Ok(Py::new(
8✔
78
                    py,
8✔
79
                    ExprBinary {
8✔
80
                        opcode: BinaryOpCode::Subtract,
8✔
81
                        left: left_py,
8✔
82
                        right: right_py,
8✔
83
                    },
8✔
NEW
84
                )?
×
85
                .into_bound(py)
8✔
86
                .into_any())
8✔
87
            }
88
            Expr::Multiply(left, right) => {
14✔
89
                let left_py = (*left).into_pyobject(py)?.unbind();
14✔
90
                let right_py = (*right).into_pyobject(py)?.unbind();
14✔
91
                Ok(Py::new(
14✔
92
                    py,
14✔
93
                    ExprBinary {
14✔
94
                        opcode: BinaryOpCode::Multiply,
14✔
95
                        left: left_py,
14✔
96
                        right: right_py,
14✔
97
                    },
14✔
NEW
98
                )?
×
99
                .into_bound(py)
14✔
100
                .into_any())
14✔
101
            }
102
            Expr::Divide(left, right) => {
16✔
103
                let left_py = (*left).into_pyobject(py)?.unbind();
16✔
104
                let right_py = (*right).into_pyobject(py)?.unbind();
16✔
105
                Ok(Py::new(
16✔
106
                    py,
16✔
107
                    ExprBinary {
16✔
108
                        opcode: BinaryOpCode::Divide,
16✔
109
                        left: left_py,
16✔
110
                        right: right_py,
16✔
111
                    },
16✔
NEW
112
                )?
×
113
                .into_bound(py)
16✔
114
                .into_any())
16✔
115
            }
116
            Expr::Power(left, right) => {
6✔
117
                let left_py = (*left).into_pyobject(py)?.unbind();
6✔
118
                let right_py = (*right).into_pyobject(py)?.unbind();
6✔
119
                Ok(Py::new(
6✔
120
                    py,
6✔
121
                    ExprBinary {
6✔
122
                        opcode: BinaryOpCode::Power,
6✔
123
                        left: left_py,
6✔
124
                        right: right_py,
6✔
125
                    },
6✔
NEW
126
                )?
×
127
                .into_bound(py)
6✔
128
                .into_any())
6✔
129
            }
130
            Expr::CustomFunction(callable, exprs) => {
12✔
131
                let args_vec: Result<Vec<_>, _> = exprs
12✔
132
                    .into_iter()
12✔
133
                    .map(|arg| arg.into_pyobject(py).map(|obj| obj.unbind()))
18✔
134
                    .collect();
12✔
135
                let args_tuple = PyTuple::new(py, args_vec?)?;
12✔
136
                Ok(Py::new(
12✔
137
                    py,
12✔
138
                    ExprCustom {
12✔
139
                        callable,
12✔
140
                        arguments: args_tuple.unbind(),
12✔
141
                    },
12✔
NEW
142
                )?
×
143
                .into_bound(py)
12✔
144
                .into_any())
12✔
145
            }
146
        }
147
    }
450✔
148
}
149

150
/// The Rust parser produces an iterator of these `Bytecode` instructions, which comprise an opcode
151
/// integer for operation distinction, and a free-form tuple containing the operands.
152
#[pyclass(module = "qiskit._accelerate.qasm2", frozen, skip_from_py_object)]
153
#[derive(Clone)]
154
pub struct Bytecode {
155
    #[pyo3(get)]
156
    opcode: OpCode,
157
    #[pyo3(get)]
158
    operands: Py<PyAny>,
159
}
160

161
/// The operations that are represented by the "bytecode" passed to Python.
162
#[pyclass(module = "qiskit._accelerate.qasm2", frozen, eq, skip_from_py_object)]
×
163
#[derive(Clone, Eq, PartialEq)]
164
pub enum OpCode {
165
    // There is only a `Gate` here, not a `GateInBasis`, because in Python space we don't have the
166
    // same strict typing requirements to satisfy.
167
    Gate,
168
    ConditionedGate,
169
    Measure,
170
    ConditionedMeasure,
171
    Reset,
172
    ConditionedReset,
173
    Barrier,
174
    DeclareQreg,
175
    DeclareCreg,
176
    DeclareGate,
177
    EndDeclareGate,
178
    DeclareOpaque,
179
    SpecialInclude,
180
}
181

182
// The following structs, with `Expr` or `OpCode` in the name (but not the top-level `OpCode`
183
// above) build up the tree of symbolic expressions for the parameter applications within gate
184
// bodies.  We choose to store this in the gate classes that the Python component emits, so it can
185
// lazily create definitions as required, rather than eagerly binding them as the file is parsed.
186
//
187
// In Python space we would usually have the classes inherit from some shared subclass, but doing
188
// that makes things a little fiddlier with PyO3, and there's no real benefit for our uses.
189

190
/// A (potentially folded) floating-point constant value as part of an expression.
191
#[pyclass(module = "qiskit._accelerate.qasm2", frozen, skip_from_py_object)]
192
#[derive(Clone)]
193
pub struct ExprConstant {
194
    #[pyo3(get)]
195
    pub value: f64,
196
}
197

198
/// A reference to one of the arguments to the gate.
199
#[pyclass(module = "qiskit._accelerate.qasm2", frozen, skip_from_py_object)]
200
#[derive(Clone)]
201
pub struct ExprArgument {
202
    #[pyo3(get)]
203
    pub index: ParamId,
204
}
205

206
/// A unary operation acting on some other part of the expression tree.  This includes the `+` and
207
/// `-` unary operators, but also any of the built-in scientific-calculator functions.
208
#[pyclass(module = "qiskit._accelerate.qasm2", frozen, skip_from_py_object)]
209
#[derive(Clone)]
210
pub struct ExprUnary {
211
    #[pyo3(get)]
212
    pub opcode: UnaryOpCode,
213
    #[pyo3(get)]
214
    pub argument: Py<PyAny>,
215
}
216

217
/// A binary operation acting on two other parts of the expression tree.
218
#[pyclass(module = "qiskit._accelerate.qasm2", frozen, skip_from_py_object)]
219
#[derive(Clone)]
220
pub struct ExprBinary {
221
    #[pyo3(get)]
222
    pub opcode: BinaryOpCode,
223
    #[pyo3(get)]
224
    pub left: Py<PyAny>,
225
    #[pyo3(get)]
226
    pub right: Py<PyAny>,
227
}
228

229
/// Some custom callable Python function that the user told us about.
230
#[pyclass(module = "qiskit._accelerate.qasm2", frozen, skip_from_py_object)]
231
#[derive(Clone)]
232
pub struct ExprCustom {
233
    pub callable: ClassicalCallableExt,
234
    #[pyo3(get)]
235
    pub arguments: Py<PyTuple>,
236
}
237
#[pymethods]
238
impl ExprCustom {
239
    #[pyo3(signature = (*args))]
240
    fn call(&self, args: Bound<PyTuple>) -> PyResult<f64> {
20✔
241
        match &self.callable {
20✔
242
            ClassicalCallableExt::Builtin(builtin) => {
4✔
243
                let arg_vals = args.extract::<Vec<f64>>()?;
4✔
244
                let provided = arg_vals.len();
4✔
245
                builtin.call(&arg_vals).map_err(|expected| {
4✔
NEW
246
                    PyTypeError::new_err(format!(
×
247
                        "argument mismatch: expected {expected}, got {provided}"
248
                    ))
NEW
249
                })
×
250
            }
251
            ClassicalCallableExt::Py { ob, num_params: _ } => {
16✔
252
                ob.bind(args.py()).call1(args).and_then(|ob| ob.extract())
16✔
253
            }
254
        }
255
    }
20✔
256
}
257

258
/// Discriminator for the different types of unary operator.  We could have a separate class for
259
/// each of these, but this way involves fewer imports in Python, and also serves to split up the
260
/// option tree at the top level, so we don't have to test every unary operator before testing
261
/// other operations.
262
#[pyclass(module = "qiskit._accelerate.qasm2", frozen, eq, skip_from_py_object)]
×
263
#[derive(Clone, Debug, PartialEq, Eq)]
264
pub enum UnaryOpCode {
265
    Negate,
266
    Cos,
267
    Exp,
268
    Ln,
269
    Sin,
270
    Sqrt,
271
    Tan,
272
}
273

274
/// Discriminator for the different types of binary operator.  We could have a separate class for
275
/// each of these, but this way involves fewer imports in Python, and also serves to split up the
276
/// option tree at the top level, so we don't have to test every binary operator before testing
277
/// other operations.
278
#[pyclass(module = "qiskit._accelerate.qasm2", frozen, eq, skip_from_py_object)]
×
279
#[derive(Clone, Debug, PartialEq, Eq)]
280
pub enum BinaryOpCode {
281
    Add,
282
    Subtract,
283
    Multiply,
284
    Divide,
285
    Power,
286
}
287

288
/// An internal representation of the bytecode that will later be converted to the more free-form
289
/// [Bytecode] Python-space objects.  This is fairly tightly coupled to Python space; the intent is
290
/// just to communicate to Python as concisely as possible what it needs to do.  We want to have as
291
/// little work to do in Python space as possible, since everything is slower there.
292
///
293
/// In various enumeration items, we use zero-indexed numeric keys to identify the object rather
294
/// than its name.  This is much more efficient in Python-space; rather than needing to build and
295
/// lookup things in a hashmap, we can just build Python lists and index them directly, which also
296
/// has the advantage of not needing to pass strings to Python for each gate.  It also gives us
297
/// consistency with how qubits and clbits are tracked; there is no need to track both the register
298
/// name and the index separately when we can use a simple single index.
299
pub enum InternalBytecode {
300
    Gate {
301
        id: GateId,
302
        arguments: Vec<f64>,
303
        qubits: Vec<QubitId>,
304
    },
305
    ConditionedGate {
306
        id: GateId,
307
        arguments: Vec<f64>,
308
        qubits: Vec<QubitId>,
309
        creg: CregId,
310
        value: BigUint,
311
    },
312
    Measure {
313
        qubit: QubitId,
314
        clbit: ClbitId,
315
    },
316
    ConditionedMeasure {
317
        qubit: QubitId,
318
        clbit: ClbitId,
319
        creg: CregId,
320
        value: BigUint,
321
    },
322
    Reset {
323
        qubit: QubitId,
324
    },
325
    ConditionedReset {
326
        qubit: QubitId,
327
        creg: CregId,
328
        value: BigUint,
329
    },
330
    Barrier {
331
        qubits: Vec<QubitId>,
332
    },
333
    DeclareQreg {
334
        name: String,
335
        size: usize,
336
    },
337
    DeclareCreg {
338
        name: String,
339
        size: usize,
340
    },
341
    DeclareGate {
342
        name: String,
343
        num_qubits: usize,
344
    },
345
    GateInBody {
346
        id: GateId,
347
        arguments: Vec<Expr>,
348
        qubits: Vec<QubitId>,
349
    },
350
    EndDeclareGate {},
351
    DeclareOpaque {
352
        name: String,
353
        num_qubits: usize,
354
    },
355
    SpecialInclude {
356
        indices: Vec<usize>,
357
    },
358
}
359

360
impl<'py> IntoPyObject<'py> for InternalBytecode {
361
    type Target = Bytecode;
362
    type Output = Bound<'py, Self::Target>;
363
    type Error = PyErr;
364

365
    /// Convert the internal bytecode representation to a Python-space one.
366
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
6,210✔
367
        Bound::new(
6,210✔
368
            py,
6,210✔
369
            match self {
6,210✔
370
                InternalBytecode::Gate {
371
                    id,
2,204✔
372
                    arguments,
2,204✔
373
                    qubits,
2,204✔
374
                } => Bytecode {
375
                    opcode: OpCode::Gate,
2,204✔
376
                    operands: (id, arguments, qubits)
2,204✔
377
                        .into_pyobject(py)?
2,204✔
378
                        .into_any()
2,204✔
379
                        .unbind(),
2,204✔
380
                },
381
                InternalBytecode::ConditionedGate {
382
                    id,
114✔
383
                    arguments,
114✔
384
                    qubits,
114✔
385
                    creg,
114✔
386
                    value,
114✔
387
                } => Bytecode {
388
                    opcode: OpCode::ConditionedGate,
114✔
389
                    operands: (id, arguments, qubits, creg, value)
114✔
390
                        .into_pyobject(py)?
114✔
391
                        .into_any()
114✔
392
                        .unbind(),
114✔
393
                },
394
                InternalBytecode::Measure { qubit, clbit } => Bytecode {
590✔
395
                    opcode: OpCode::Measure,
590✔
396
                    operands: (qubit, clbit).into_pyobject(py)?.into_any().unbind(),
590✔
397
                },
398
                InternalBytecode::ConditionedMeasure {
399
                    qubit,
12✔
400
                    clbit,
12✔
401
                    creg,
12✔
402
                    value,
12✔
403
                } => Bytecode {
404
                    opcode: OpCode::ConditionedMeasure,
12✔
405
                    operands: (qubit, clbit, creg, value)
12✔
406
                        .into_pyobject(py)?
12✔
407
                        .into_any()
12✔
408
                        .unbind(),
12✔
409
                },
410
                InternalBytecode::Reset { qubit } => Bytecode {
6✔
411
                    opcode: OpCode::Reset,
6✔
412
                    operands: (qubit,).into_pyobject(py)?.into_any().unbind(),
6✔
413
                },
414
                InternalBytecode::ConditionedReset { qubit, creg, value } => Bytecode {
12✔
415
                    opcode: OpCode::ConditionedReset,
12✔
416
                    operands: (qubit, creg, value).into_pyobject(py)?.into_any().unbind(),
12✔
417
                },
418
                InternalBytecode::Barrier { qubits } => Bytecode {
180✔
419
                    opcode: OpCode::Barrier,
180✔
420
                    operands: (qubits,).into_pyobject(py)?.into_any().unbind(),
180✔
421
                },
422
                InternalBytecode::DeclareQreg { name, size } => Bytecode {
1,216✔
423
                    opcode: OpCode::DeclareQreg,
1,216✔
424
                    operands: (name, size).into_pyobject(py)?.into_any().unbind(),
1,216✔
425
                },
426
                InternalBytecode::DeclareCreg { name, size } => Bytecode {
864✔
427
                    opcode: OpCode::DeclareCreg,
864✔
428
                    operands: (name, size).into_pyobject(py)?.into_any().unbind(),
864✔
429
                },
430
                InternalBytecode::DeclareGate { name, num_qubits } => Bytecode {
172✔
431
                    opcode: OpCode::DeclareGate,
172✔
432
                    operands: (name, num_qubits).into_pyobject(py)?.into_any().unbind(),
172✔
433
                },
434
                InternalBytecode::GateInBody {
435
                    id,
296✔
436
                    arguments,
296✔
437
                    qubits,
296✔
438
                } => Bytecode {
439
                    // In Python space, we don't have to be worried about the types of the
440
                    // parameters changing here, so we can just use `OpCode::Gate` unlike in the
441
                    // internal bytecode.
442
                    opcode: OpCode::Gate,
296✔
443
                    operands: (id, arguments.into_pyobject(py)?, qubits)
296✔
444
                        .into_pyobject(py)?
296✔
445
                        .into_any()
296✔
446
                        .unbind(),
296✔
447
                },
448
                InternalBytecode::EndDeclareGate {} => Bytecode {
449
                    opcode: OpCode::EndDeclareGate,
172✔
450
                    operands: ().into_pyobject(py)?.into_any().unbind(),
172✔
451
                },
452
                InternalBytecode::DeclareOpaque { name, num_qubits } => Bytecode {
36✔
453
                    opcode: OpCode::DeclareOpaque,
36✔
454
                    operands: (name, num_qubits).into_pyobject(py)?.into_any().unbind(),
36✔
455
                },
456
                InternalBytecode::SpecialInclude { indices } => Bytecode {
336✔
457
                    opcode: OpCode::SpecialInclude,
336✔
458
                    operands: (indices,).into_pyobject(py)?.into_any().unbind(),
336✔
459
                },
460
            },
461
        )
462
    }
6,210✔
463
}
464

465
/// The custom iterator object that is returned up to Python space for iteration through the
466
/// bytecode stream.  This is never constructed on the Python side; it is built in Rust space
467
/// by Python calls to [bytecode_from_string] and [bytecode_from_file].
468
#[pyclass]
469
pub struct BytecodeIterator {
470
    parser_state: parse::State,
471
    buffer: Vec<Option<InternalBytecode>>,
472
    buffer_used: usize,
473
}
474

475
impl BytecodeIterator {
476
    pub fn new(
1,196✔
477
        tokens: lex::TokenStream,
1,196✔
478
        include_path: Vec<std::path::PathBuf>,
1,196✔
479
        custom_instructions: &[CustomInstruction],
1,196✔
480
        custom_classical: &[CustomClassical],
1,196✔
481
        strict: bool,
1,196✔
482
    ) -> PyResult<Self> {
1,196✔
483
        Ok(BytecodeIterator {
484
            parser_state: parse::State::new(
1,196✔
485
                tokens,
1,196✔
486
                include_path,
1,196✔
487
                custom_instructions,
1,196✔
488
                custom_classical,
1,196✔
489
                strict,
1,196✔
490
            )
491
            .map_err(QASM2ParseError::new_err)?,
1,196✔
492
            buffer: vec![],
1,172✔
493
            buffer_used: 0,
494
        })
495
    }
1,196✔
496
}
497

498
#[pymethods]
×
499
impl BytecodeIterator {
500
    fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
2,516✔
501
        slf
2,516✔
502
    }
2,516✔
503

504
    fn __next__(&mut self, py: Python<'_>) -> PyResult<Option<Bytecode>> {
7,378✔
505
        if self.buffer_used >= self.buffer.len() {
7,378✔
506
            self.buffer.clear();
6,358✔
507
            self.buffer_used = 0;
6,358✔
508
            self.parser_state.parse_next(&mut self.buffer)?;
6,358✔
509
        }
1,020✔
510
        if self.buffer.is_empty() {
6,718✔
511
            Ok(None)
508✔
512
        } else {
513
            self.buffer_used += 1;
6,210✔
514
            Ok(self.buffer[self.buffer_used - 1]
6,210✔
515
                .take()
6,210✔
516
                .map(|bytecode| bytecode.into_pyobject(py))
6,210✔
517
                .transpose()?
6,210✔
518
                .map(|x| x.get().clone()))
6,210✔
519
        }
520
    }
7,378✔
521
}
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