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

Qiskit / qiskit / 20957686885

13 Jan 2026 01:01PM UTC coverage: 88.319% (+0.005%) from 88.314%
20957686885

Pull #15560

github

web-flow
Merge 7aa439435 into 188c172c8
Pull Request #15560: Make builtin `qasm2.CustomClassical` extensions Rust-native

116 of 120 new or added lines in 5 files covered. (96.67%)

19 existing lines in 6 files now uncovered.

96822 of 109627 relevant lines covered (88.32%)

1206542.0 hits per line

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

93.28
/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 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 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
/// The Rust parser produces an iterator of these `Bytecode` instructions, which comprise an opcode
24
/// integer for operation distinction, and a free-form tuple containing the operands.
25
#[pyclass(module = "qiskit._accelerate.qasm2", frozen)]
26
#[derive(Clone)]
27
pub struct Bytecode {
28
    #[pyo3(get)]
29
    opcode: OpCode,
30
    #[pyo3(get)]
31
    operands: Py<PyAny>,
32
}
33

34
/// The operations that are represented by the "bytecode" passed to Python.
35
#[pyclass(module = "qiskit._accelerate.qasm2", frozen, eq)]
×
36
#[derive(Clone, Eq, PartialEq)]
37
pub enum OpCode {
38
    // There is only a `Gate` here, not a `GateInBasis`, because in Python space we don't have the
39
    // same strict typing requirements to satisfy.
40
    Gate,
41
    ConditionedGate,
42
    Measure,
43
    ConditionedMeasure,
44
    Reset,
45
    ConditionedReset,
46
    Barrier,
47
    DeclareQreg,
48
    DeclareCreg,
49
    DeclareGate,
50
    EndDeclareGate,
51
    DeclareOpaque,
52
    SpecialInclude,
53
}
54

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

63
/// A (potentially folded) floating-point constant value as part of an expression.
64
#[pyclass(module = "qiskit._accelerate.qasm2", frozen)]
65
#[derive(Clone)]
66
pub struct ExprConstant {
67
    #[pyo3(get)]
68
    pub value: f64,
69
}
70

71
/// A reference to one of the arguments to the gate.
72
#[pyclass(module = "qiskit._accelerate.qasm2", frozen)]
73
#[derive(Clone)]
74
pub struct ExprArgument {
75
    #[pyo3(get)]
76
    pub index: ParamId,
77
}
78

79
/// A unary operation acting on some other part of the expression tree.  This includes the `+` and
80
/// `-` unary operators, but also any of the built-in scientific-calculator functions.
81
#[pyclass(module = "qiskit._accelerate.qasm2", frozen)]
82
#[derive(Clone)]
83
pub struct ExprUnary {
84
    #[pyo3(get)]
85
    pub opcode: UnaryOpCode,
86
    #[pyo3(get)]
87
    pub argument: Py<PyAny>,
88
}
89

90
/// A binary operation acting on two other parts of the expression tree.
91
#[pyclass(module = "qiskit._accelerate.qasm2", frozen)]
92
#[derive(Clone)]
93
pub struct ExprBinary {
94
    #[pyo3(get)]
95
    pub opcode: BinaryOpCode,
96
    #[pyo3(get)]
97
    pub left: Py<PyAny>,
98
    #[pyo3(get)]
99
    pub right: Py<PyAny>,
100
}
101

102
/// Some custom callable Python function that the user told us about.
103
#[pyclass(module = "qiskit._accelerate.qasm2", frozen)]
104
#[derive(Clone)]
105
pub struct ExprCustom {
106
    pub callable: ClassicalCallableExt,
107
    #[pyo3(get)]
108
    pub arguments: Py<PyTuple>,
109
}
110
#[pymethods]
111
impl ExprCustom {
112
    #[pyo3(signature = (*args))]
113
    fn call(&self, args: Bound<PyTuple>) -> PyResult<f64> {
20✔
114
        match &self.callable {
20✔
115
            ClassicalCallableExt::Builtin(builtin) => builtin
4✔
116
                .call(args.extract::<Vec<f64>>()?.as_slice())
4✔
117
                .map_err(|actual| {
4✔
NEW
118
                    PyTypeError::new_err(format!(
×
NEW
119
                        "argument mismatch: expected {}, got {}",
×
NEW
120
                        builtin.num_params(),
×
121
                        actual
122
                    ))
NEW
123
                }),
×
124
            ClassicalCallableExt::Py { ob, num_params: _ } => {
16✔
125
                ob.bind(args.py()).call1(args).and_then(|ob| ob.extract())
16✔
126
            }
127
        }
128
    }
20✔
129
}
130

131
/// Discriminator for the different types of unary operator.  We could have a separate class for
132
/// each of these, but this way involves fewer imports in Python, and also serves to split up the
133
/// option tree at the top level, so we don't have to test every unary operator before testing
134
/// other operations.
135
#[pyclass(module = "qiskit._accelerate.qasm2", frozen, eq)]
×
136
#[derive(Clone, PartialEq, Eq)]
137
pub enum UnaryOpCode {
138
    Negate,
139
    Cos,
140
    Exp,
141
    Ln,
142
    Sin,
143
    Sqrt,
144
    Tan,
145
}
146

147
/// Discriminator for the different types of binary operator.  We could have a separate class for
148
/// each of these, but this way involves fewer imports in Python, and also serves to split up the
149
/// option tree at the top level, so we don't have to test every binary operator before testing
150
/// other operations.
151
#[pyclass(module = "qiskit._accelerate.qasm2", frozen, eq)]
×
152
#[derive(Clone, PartialEq, Eq)]
153
pub enum BinaryOpCode {
154
    Add,
155
    Subtract,
156
    Multiply,
157
    Divide,
158
    Power,
159
}
160

161
/// An internal representation of the bytecode that will later be converted to the more free-form
162
/// [Bytecode] Python-space objects.  This is fairly tightly coupled to Python space; the intent is
163
/// just to communicate to Python as concisely as possible what it needs to do.  We want to have as
164
/// little work to do in Python space as possible, since everything is slower there.
165
///
166
/// In various enumeration items, we use zero-indexed numeric keys to identify the object rather
167
/// than its name.  This is much more efficient in Python-space; rather than needing to build and
168
/// lookup things in a hashmap, we can just build Python lists and index them directly, which also
169
/// has the advantage of not needing to pass strings to Python for each gate.  It also gives us
170
/// consistency with how qubits and clbits are tracked; there is no need to track both the register
171
/// name and the index separately when we can use a simple single index.
172
pub enum InternalBytecode {
173
    Gate {
174
        id: GateId,
175
        arguments: Vec<f64>,
176
        qubits: Vec<QubitId>,
177
    },
178
    ConditionedGate {
179
        id: GateId,
180
        arguments: Vec<f64>,
181
        qubits: Vec<QubitId>,
182
        creg: CregId,
183
        value: BigUint,
184
    },
185
    Measure {
186
        qubit: QubitId,
187
        clbit: ClbitId,
188
    },
189
    ConditionedMeasure {
190
        qubit: QubitId,
191
        clbit: ClbitId,
192
        creg: CregId,
193
        value: BigUint,
194
    },
195
    Reset {
196
        qubit: QubitId,
197
    },
198
    ConditionedReset {
199
        qubit: QubitId,
200
        creg: CregId,
201
        value: BigUint,
202
    },
203
    Barrier {
204
        qubits: Vec<QubitId>,
205
    },
206
    DeclareQreg {
207
        name: String,
208
        size: usize,
209
    },
210
    DeclareCreg {
211
        name: String,
212
        size: usize,
213
    },
214
    DeclareGate {
215
        name: String,
216
        num_qubits: usize,
217
    },
218
    GateInBody {
219
        id: GateId,
220
        arguments: Vec<Expr>,
221
        qubits: Vec<QubitId>,
222
    },
223
    EndDeclareGate {},
224
    DeclareOpaque {
225
        name: String,
226
        num_qubits: usize,
227
    },
228
    SpecialInclude {
229
        indices: Vec<usize>,
230
    },
231
}
232

233
impl<'py> IntoPyObject<'py> for InternalBytecode {
234
    type Target = Bytecode;
235
    type Output = Bound<'py, Self::Target>;
236
    type Error = PyErr;
237

238
    /// Convert the internal bytecode representation to a Python-space one.
239
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
6,252✔
240
        Bound::new(
6,252✔
241
            py,
6,252✔
242
            match self {
6,252✔
243
                InternalBytecode::Gate {
244
                    id,
2,204✔
245
                    arguments,
2,204✔
246
                    qubits,
2,204✔
247
                } => Bytecode {
248
                    opcode: OpCode::Gate,
2,204✔
249
                    operands: (id, arguments, qubits)
2,204✔
250
                        .into_pyobject(py)?
2,204✔
251
                        .into_any()
2,204✔
252
                        .unbind(),
2,204✔
253
                },
254
                InternalBytecode::ConditionedGate {
255
                    id,
114✔
256
                    arguments,
114✔
257
                    qubits,
114✔
258
                    creg,
114✔
259
                    value,
114✔
260
                } => Bytecode {
261
                    opcode: OpCode::ConditionedGate,
114✔
262
                    operands: (id, arguments, qubits, creg, value)
114✔
263
                        .into_pyobject(py)?
114✔
264
                        .into_any()
114✔
265
                        .unbind(),
114✔
266
                },
267
                InternalBytecode::Measure { qubit, clbit } => Bytecode {
590✔
268
                    opcode: OpCode::Measure,
590✔
269
                    operands: (qubit, clbit).into_pyobject(py)?.into_any().unbind(),
590✔
270
                },
271
                InternalBytecode::ConditionedMeasure {
272
                    qubit,
12✔
273
                    clbit,
12✔
274
                    creg,
12✔
275
                    value,
12✔
276
                } => Bytecode {
277
                    opcode: OpCode::ConditionedMeasure,
12✔
278
                    operands: (qubit, clbit, creg, value)
12✔
279
                        .into_pyobject(py)?
12✔
280
                        .into_any()
12✔
281
                        .unbind(),
12✔
282
                },
283
                InternalBytecode::Reset { qubit } => Bytecode {
6✔
284
                    opcode: OpCode::Reset,
6✔
285
                    operands: (qubit,).into_pyobject(py)?.into_any().unbind(),
6✔
286
                },
287
                InternalBytecode::ConditionedReset { qubit, creg, value } => Bytecode {
12✔
288
                    opcode: OpCode::ConditionedReset,
12✔
289
                    operands: (qubit, creg, value).into_pyobject(py)?.into_any().unbind(),
12✔
290
                },
291
                InternalBytecode::Barrier { qubits } => Bytecode {
180✔
292
                    opcode: OpCode::Barrier,
180✔
293
                    operands: (qubits,).into_pyobject(py)?.into_any().unbind(),
180✔
294
                },
295
                InternalBytecode::DeclareQreg { name, size } => Bytecode {
1,230✔
296
                    opcode: OpCode::DeclareQreg,
1,230✔
297
                    operands: (name, size).into_pyobject(py)?.into_any().unbind(),
1,230✔
298
                },
299
                InternalBytecode::DeclareCreg { name, size } => Bytecode {
892✔
300
                    opcode: OpCode::DeclareCreg,
892✔
301
                    operands: (name, size).into_pyobject(py)?.into_any().unbind(),
892✔
302
                },
303
                InternalBytecode::DeclareGate { name, num_qubits } => Bytecode {
172✔
304
                    opcode: OpCode::DeclareGate,
172✔
305
                    operands: (name, num_qubits).into_pyobject(py)?.into_any().unbind(),
172✔
306
                },
307
                InternalBytecode::GateInBody {
308
                    id,
296✔
309
                    arguments,
296✔
310
                    qubits,
296✔
311
                } => Bytecode {
312
                    // In Python space, we don't have to be worried about the types of the
313
                    // parameters changing here, so we can just use `OpCode::Gate` unlike in the
314
                    // internal bytecode.
315
                    opcode: OpCode::Gate,
296✔
316
                    operands: (id, arguments.into_pyobject(py)?, qubits)
296✔
317
                        .into_pyobject(py)?
296✔
318
                        .into_any()
296✔
319
                        .unbind(),
296✔
320
                },
321
                InternalBytecode::EndDeclareGate {} => Bytecode {
322
                    opcode: OpCode::EndDeclareGate,
172✔
323
                    operands: ().into_pyobject(py)?.into_any().unbind(),
172✔
324
                },
325
                InternalBytecode::DeclareOpaque { name, num_qubits } => Bytecode {
36✔
326
                    opcode: OpCode::DeclareOpaque,
36✔
327
                    operands: (name, num_qubits).into_pyobject(py)?.into_any().unbind(),
36✔
328
                },
329
                InternalBytecode::SpecialInclude { indices } => Bytecode {
336✔
330
                    opcode: OpCode::SpecialInclude,
336✔
331
                    operands: (indices,).into_pyobject(py)?.into_any().unbind(),
336✔
332
                },
333
            },
334
        )
335
    }
6,252✔
336
}
337

338
/// The custom iterator object that is returned up to Python space for iteration through the
339
/// bytecode stream.  This is never constructed on the Python side; it is built in Rust space
340
/// by Python calls to [bytecode_from_string] and [bytecode_from_file].
341
#[pyclass]
342
pub struct BytecodeIterator {
343
    parser_state: parse::State,
344
    buffer: Vec<Option<InternalBytecode>>,
345
    buffer_used: usize,
346
}
347

348
impl BytecodeIterator {
349
    pub fn new(
1,208✔
350
        tokens: lex::TokenStream,
1,208✔
351
        include_path: Vec<std::path::PathBuf>,
1,208✔
352
        custom_instructions: &[CustomInstruction],
1,208✔
353
        custom_classical: &[CustomClassical],
1,208✔
354
        strict: bool,
1,208✔
355
    ) -> PyResult<Self> {
1,208✔
356
        Ok(BytecodeIterator {
357
            parser_state: parse::State::new(
1,208✔
358
                tokens,
1,208✔
359
                include_path,
1,208✔
360
                custom_instructions,
1,208✔
361
                custom_classical,
1,208✔
362
                strict,
1,208✔
363
            )
364
            .map_err(QASM2ParseError::new_err)?,
1,208✔
365
            buffer: vec![],
1,184✔
366
            buffer_used: 0,
367
        })
368
    }
1,208✔
369
}
370

371
#[pymethods]
×
372
impl BytecodeIterator {
373
    fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
2,540✔
374
        slf
2,540✔
375
    }
2,540✔
376

377
    fn __next__(&mut self, py: Python<'_>) -> PyResult<Option<Bytecode>> {
7,432✔
378
        if self.buffer_used >= self.buffer.len() {
7,432✔
379
            self.buffer.clear();
6,412✔
380
            self.buffer_used = 0;
6,412✔
381
            self.parser_state.parse_next(&mut self.buffer)?;
6,412✔
382
        }
1,020✔
383
        if self.buffer.is_empty() {
6,760✔
384
            Ok(None)
508✔
385
        } else {
386
            self.buffer_used += 1;
6,252✔
387
            Ok(self.buffer[self.buffer_used - 1]
6,252✔
388
                .take()
6,252✔
389
                .map(|bytecode| bytecode.into_pyobject(py))
6,252✔
390
                .transpose()?
6,252✔
391
                .map(|x| x.get().clone()))
6,252✔
392
        }
393
    }
7,432✔
394
}
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