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

Qiskit / qiskit / 21430682342

28 Jan 2026 08:23AM UTC coverage: 87.961% (-0.02%) from 87.984%
21430682342

Pull #15277

github

web-flow
Merge dcc1b31af into 95c22e0ec
Pull Request #15277: Combine Python types in PackedInstructions

132 of 180 new or added lines in 22 files covered. (73.33%)

24 existing lines in 7 files now uncovered.

100004 of 113691 relevant lines covered (87.96%)

1161040.61 hits per line

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

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

13
use std::hash::Hasher;
14
#[cfg(feature = "cache_pygates")]
15
use std::sync::OnceLock;
16

17
use crate::TupleLikeArg;
18
use crate::circuit_data::CircuitData;
19
use crate::circuit_instruction::{CircuitInstruction, OperationFromPython, extract_params};
20
use crate::operations::{Operation, OperationRef, Param, PyOperationTypes, PythonOperation};
21

22
use ahash::AHasher;
23
use approx::relative_eq;
24
use num_complex::Complex64;
25
use rustworkx_core::petgraph::stable_graph::NodeIndex;
26

27
use crate::instruction::Instruction;
28
use numpy::IntoPyArray;
29
use numpy::PyArray2;
30
use pyo3::IntoPyObjectExt;
31
use pyo3::exceptions::PyValueError;
32
use pyo3::prelude::*;
33
use pyo3::types::PyTuple;
34
use pyo3::{PyResult, intern};
35

36
/// Parent class for DAGOpNode, DAGInNode, and DAGOutNode.
37
#[pyclass(module = "qiskit._accelerate.circuit", subclass)]
38
#[derive(Clone, Debug)]
39
pub struct DAGNode {
40
    pub node: Option<NodeIndex>,
41
}
42

43
impl DAGNode {
44
    #[inline]
45
    pub fn py_nid(&self) -> isize {
718,752✔
46
        self.node
718,752✔
47
            .map(|node| node.index().try_into().unwrap())
718,752✔
48
            .unwrap_or(-1)
718,752✔
49
    }
718,752✔
50
}
51

52
#[pymethods]
×
53
impl DAGNode {
54
    #[new]
55
    #[pyo3(signature=(nid=-1))]
56
    fn py_new(nid: isize) -> PyResult<Self> {
×
57
        Ok(DAGNode {
58
            node: match nid {
×
59
                -1 => None,
×
60
                nid => {
×
61
                    let index: usize = match nid.try_into() {
×
62
                        Ok(index) => index,
×
63
                        Err(_) => {
64
                            return Err(PyValueError::new_err(
×
65
                                "Invalid node index, must be -1 or a non-negative integer",
×
66
                            ));
×
67
                        }
68
                    };
69
                    Some(NodeIndex::new(index))
×
70
                }
71
            },
72
        })
73
    }
×
74

75
    #[getter(_node_id)]
76
    fn get_py_node_id(&self) -> isize {
248,214✔
77
        self.py_nid()
248,214✔
78
    }
248,214✔
79

80
    #[setter(_node_id)]
81
    fn set_py_node_id(&mut self, nid: isize) {
154,338✔
82
        self.node = match nid {
154,338✔
83
            -1 => None,
×
84
            nid => Some(NodeIndex::new(nid.try_into().unwrap())),
154,338✔
85
        }
86
    }
154,338✔
87

88
    fn __getstate__(&self) -> Option<usize> {
×
89
        self.node.map(|node| node.index())
×
90
    }
×
91

92
    #[pyo3(signature=(index=None))]
93
    fn __setstate__(&mut self, index: Option<usize>) {
×
94
        self.node = index.map(NodeIndex::new);
×
95
    }
×
96

97
    fn __lt__(&self, other: &DAGNode) -> bool {
×
98
        self.py_nid() < other.py_nid()
×
99
    }
×
100

101
    fn __gt__(&self, other: &DAGNode) -> bool {
×
102
        self.py_nid() > other.py_nid()
×
103
    }
×
104

105
    fn __str__(_self: &Bound<DAGNode>) -> String {
×
106
        format!("{}", _self.as_ptr() as usize)
×
107
    }
×
108

109
    fn __hash__(&self, py: Python) -> PyResult<isize> {
×
110
        self.py_nid().into_pyobject(py)?.hash()
×
111
    }
×
112
}
113

114
/// Object to represent an Instruction at a node in the DAGCircuit.
115
#[pyclass(module = "qiskit._accelerate.circuit", extends=DAGNode)]
116
pub struct DAGOpNode {
117
    pub instruction: CircuitInstruction,
118
}
119

120
#[pymethods]
×
121
impl DAGOpNode {
122
    #[new]
123
    #[pyo3(signature = (op, qargs=None, cargs=None))]
124
    pub fn py_new(
56,384✔
125
        py: Python,
56,384✔
126
        op: Bound<PyAny>,
56,384✔
127
        qargs: Option<TupleLikeArg>,
56,384✔
128
        cargs: Option<TupleLikeArg>,
56,384✔
129
    ) -> PyResult<Py<Self>> {
56,384✔
130
        let py_op = op.extract::<OperationFromPython<CircuitData>>()?;
56,384✔
131
        let qargs = qargs.map_or_else(|| PyTuple::empty(py), |q| q.value);
56,384✔
132
        let cargs = cargs.map_or_else(|| PyTuple::empty(py), |c| c.value);
56,384✔
133
        let instruction = CircuitInstruction {
56,384✔
134
            operation: py_op.operation,
56,384✔
135
            qubits: qargs.unbind(),
56,384✔
136
            clbits: cargs.unbind(),
56,384✔
137
            params: py_op.params,
56,384✔
138
            label: py_op.label,
56,384✔
139
            #[cfg(feature = "cache_pygates")]
56,384✔
140
            py_op: op.unbind().into(),
56,384✔
141
        };
56,384✔
142

143
        Py::new(py, (DAGOpNode { instruction }, DAGNode { node: None }))
56,384✔
144
    }
56,384✔
145

146
    fn __hash__(slf: PyRef<'_, Self>) -> PyResult<u64> {
220,536✔
147
        let super_ = slf.as_ref();
220,536✔
148
        let mut hasher = AHasher::default();
220,536✔
149
        hasher.write_isize(super_.py_nid());
220,536✔
150
        hasher.write(slf.instruction.operation.name().as_bytes());
220,536✔
151
        Ok(hasher.finish())
220,536✔
152
    }
220,536✔
153

154
    fn __eq__(slf: PyRef<Self>, py: Python, other: &Bound<PyAny>) -> PyResult<bool> {
121,600✔
155
        // This check is more restrictive by design as it's intended to replace
156
        // object identitity for set/dict membership and not be a semantic equivalence
157
        // check. We have an implementation of that as part of `DAGCircuit.__eq__` and
158
        // this method is specifically to ensure nodes are the same. This means things
159
        // like parameter equality are stricter to reject things like
160
        // Param::Float(0.1) == Param::ParameterExpression(0.1) (if the expression was
161
        // a python parameter equivalent to a bound value).
162
        let Ok(other) = other.cast::<Self>() else {
121,600✔
163
            return Ok(false);
690✔
164
        };
165
        let borrowed_other = other.borrow();
120,910✔
166
        let other_super = borrowed_other.as_ref();
120,910✔
167
        let super_ = slf.as_ref();
120,910✔
168

169
        if super_.py_nid() != other_super.py_nid() {
120,910✔
170
            return Ok(false);
696✔
171
        }
120,214✔
172
        if !slf
120,214✔
173
            .instruction
120,214✔
174
            .operation
120,214✔
175
            .py_eq(py, &borrowed_other.instruction.operation)?
120,214✔
176
        {
177
            return Ok(false);
4✔
178
        }
120,210✔
179
        let params_eq = match (
120,210✔
180
            slf.instruction.operation.view(),
120,210✔
181
            borrowed_other.instruction.operation.view(),
120,210✔
182
        ) {
183
            (OperationRef::StandardGate(_), OperationRef::StandardGate(_))
184
            | (OperationRef::StandardInstruction(_), OperationRef::StandardInstruction(_)) => {
185
                let slf_params = slf.instruction.params_view();
119,964✔
186
                let other_params = borrowed_other.instruction.params_view();
119,964✔
187
                let mut params_eq = true;
119,964✔
188
                for (a, b) in slf_params.iter().zip(other_params) {
119,964✔
189
                    let res = match [a, b] {
62,310✔
190
                        [Param::Float(float_a), Param::Float(float_b)] => {
28,030✔
191
                            relative_eq!(float_a, float_b, max_relative = 1e-10)
28,030✔
192
                        }
193
                        [
194
                            Param::ParameterExpression(param_a),
108✔
195
                            Param::ParameterExpression(param_b),
108✔
196
                        ] => param_a == param_b,
108✔
197
                        [Param::Obj(param_a), Param::Obj(param_b)] => {
34,172✔
198
                            param_a.bind(py).eq(param_b)?
34,172✔
199
                        }
200
                        _ => false,
×
201
                    };
202
                    if !res {
62,310✔
203
                        params_eq = false;
610✔
204
                        break;
610✔
205
                    }
61,700✔
206
                }
207
                params_eq
119,964✔
208
            }
209
            (OperationRef::ControlFlow(_), OperationRef::ControlFlow(_)) => {
210
                let slf_blocks = slf.instruction.blocks_view();
140✔
211
                let other_blocks = borrowed_other.instruction.blocks_view();
140✔
212
                let mut params_eq = true;
140✔
213
                // TODO: we should be able to do the semantic-equality comparison from Rust
214
                // space in the future, without going via Python.  See gh-15267.
215
                for (a, b) in slf_blocks.iter().zip(other_blocks) {
170✔
216
                    if !a
170✔
217
                        .clone()
170✔
218
                        .into_py_quantum_circuit(py)?
170✔
219
                        .eq(b.clone().into_py_quantum_circuit(py)?)?
170✔
220
                    {
221
                        params_eq = false;
×
222
                        break;
×
223
                    }
170✔
224
                }
225
                params_eq
140✔
226
            }
227
            _ => {
228
                // For our Python object types (i.e. PyInstruction, PyGate, PyOperation), we've
229
                // already evaluated the parameters are equal here via the Python space operation
230
                // equality check.
231
                true
106✔
232
            }
233
        };
234

235
        Ok(params_eq
120,210✔
236
            && slf
119,600✔
237
                .instruction
119,600✔
238
                .qubits
119,600✔
239
                .bind(py)
119,600✔
240
                .eq(borrowed_other.instruction.qubits.clone_ref(py))?
119,600✔
241
            && slf
119,600✔
242
                .instruction
119,600✔
243
                .clbits
119,600✔
244
                .bind(py)
119,600✔
245
                .eq(borrowed_other.instruction.clbits.clone_ref(py))?)
119,600✔
246
    }
121,600✔
247

248
    #[pyo3(signature = (instruction, /, *, deepcopy=false))]
249
    #[staticmethod]
250
    fn from_instruction(
44✔
251
        py: Python,
44✔
252
        mut instruction: CircuitInstruction,
44✔
253
        deepcopy: bool,
44✔
254
    ) -> PyResult<Py<PyAny>> {
44✔
255
        if deepcopy {
44✔
256
            instruction.operation = match instruction.operation.view() {
×
257
                OperationRef::ControlFlow(cf) => cf.clone().into(),
×
NEW
258
                OperationRef::Gate(gate) => {
×
NEW
259
                    PyOperationTypes::Gate(gate.py_deepcopy(py, None)?).into()
×
260
                }
NEW
261
                OperationRef::Instruction(instruction) => {
×
NEW
262
                    PyOperationTypes::Instruction(instruction.py_deepcopy(py, None)?).into()
×
263
                }
NEW
264
                OperationRef::Operation(operation) => {
×
NEW
265
                    PyOperationTypes::Operation(operation.py_deepcopy(py, None)?).into()
×
266
                }
267
                OperationRef::StandardGate(gate) => gate.into(),
×
268
                OperationRef::StandardInstruction(instruction) => instruction.into(),
×
269
                OperationRef::Unitary(unitary) => unitary.clone().into(),
×
270
                OperationRef::PauliProductMeasurement(ppm) => ppm.clone().into(),
×
271
            };
272
            #[cfg(feature = "cache_pygates")]
273
            {
×
274
                instruction.py_op = OnceLock::new();
×
275
            }
×
276
        }
44✔
277
        let base = PyClassInitializer::from(DAGNode { node: None });
44✔
278
        let sub = base.add_subclass(DAGOpNode { instruction });
44✔
279
        Py::new(py, sub)?.into_py_any(py)
44✔
280
    }
44✔
281

282
    fn __reduce__(slf: PyRef<Self>, py: Python) -> PyResult<Py<PyAny>> {
41,134✔
283
        let state = slf.as_ref().node.map(|node| node.index());
41,134✔
284
        let temp = (
41,134✔
285
            slf.instruction.get_operation(py)?,
41,134✔
286
            &slf.instruction.qubits,
41,134✔
287
            &slf.instruction.clbits,
41,134✔
288
        );
289
        (py.get_type::<Self>(), temp, state).into_py_any(py)
41,134✔
290
    }
41,134✔
291

292
    fn __setstate__(mut slf: PyRefMut<Self>, state: &Bound<PyAny>) -> PyResult<()> {
41,134✔
293
        let index: Option<usize> = state.extract()?;
41,134✔
294
        slf.as_mut().node = index.map(NodeIndex::new);
41,134✔
295
        Ok(())
41,134✔
296
    }
41,134✔
297

298
    /// Get a `CircuitInstruction` that represents the same information as this `DAGOpNode`.  If
299
    /// `deepcopy`, any internal Python objects are deep-copied.
300
    ///
301
    /// Note: this ought to be a temporary method, while the DAG/QuantumCircuit converters still go
302
    /// via Python space; this still involves copy-out and copy-in of the data, whereas doing it all
303
    /// within Rust space could directly re-pack the instruction from a `DAGOpNode` to a
304
    /// `PackedInstruction` with no intermediate copy.
305
    #[pyo3(signature = (/, *, deepcopy=false))]
306
    fn _to_circuit_instruction(&self, py: Python, deepcopy: bool) -> PyResult<CircuitInstruction> {
2,156✔
307
        Ok(CircuitInstruction {
308
            operation: if deepcopy {
2,156✔
309
                match self.instruction.operation.view() {
×
NEW
310
                    OperationRef::Gate(gate) => {
×
NEW
311
                        PyOperationTypes::Gate(gate.py_deepcopy(py, None)?).into()
×
312
                    }
313
                    OperationRef::Instruction(instruction) => {
×
NEW
314
                        PyOperationTypes::Instruction(instruction.py_deepcopy(py, None)?).into()
×
315
                    }
NEW
316
                    OperationRef::Operation(operation) => {
×
NEW
317
                        PyOperationTypes::Operation(operation.py_deepcopy(py, None)?).into()
×
318
                    }
NEW
319
                    OperationRef::ControlFlow(cf) => cf.clone().into(),
×
320
                    OperationRef::StandardGate(gate) => gate.into(),
×
321
                    OperationRef::StandardInstruction(instruction) => instruction.into(),
×
322
                    OperationRef::Unitary(unitary) => unitary.clone().into(),
×
323
                    OperationRef::PauliProductMeasurement(ppm) => ppm.clone().into(),
×
324
                }
325
            } else {
326
                self.instruction.operation.clone()
2,156✔
327
            },
328
            qubits: self.instruction.qubits.clone_ref(py),
2,156✔
329
            clbits: self.instruction.clbits.clone_ref(py),
2,156✔
330
            params: self.instruction.params.clone(),
2,156✔
331
            label: self.instruction.label.clone(),
2,156✔
332
            #[cfg(feature = "cache_pygates")]
333
            py_op: OnceLock::new(),
2,156✔
334
        })
335
    }
2,156✔
336

337
    #[getter]
338
    fn get_op(&self, py: Python) -> PyResult<Py<PyAny>> {
1,391,176✔
339
        self.instruction.get_operation(py)
1,391,176✔
340
    }
1,391,176✔
341

342
    #[setter]
343
    fn set_op(&mut self, op: &Bound<PyAny>) -> PyResult<()> {
2✔
344
        let res = op.extract::<OperationFromPython<CircuitData>>()?;
2✔
345
        self.instruction.operation = res.operation;
2✔
346
        self.instruction.params = res.params;
2✔
347
        self.instruction.label = res.label;
2✔
348
        #[cfg(feature = "cache_pygates")]
349
        {
2✔
350
            self.instruction.py_op = op.clone().unbind().into();
2✔
351
        }
2✔
352
        Ok(())
2✔
353
    }
2✔
354

355
    #[getter]
356
    fn num_qubits(&self) -> u32 {
326✔
357
        self.instruction.operation.num_qubits()
326✔
358
    }
326✔
359

360
    #[getter]
361
    fn num_clbits(&self) -> u32 {
×
362
        self.instruction.operation.num_clbits()
×
363
    }
×
364

365
    #[getter]
366
    pub fn get_qargs(&self, py: Python) -> Py<PyTuple> {
2,012,412✔
367
        self.instruction.qubits.clone_ref(py)
2,012,412✔
368
    }
2,012,412✔
369

370
    #[setter]
371
    fn set_qargs(&mut self, qargs: Py<PyTuple>) {
217,458✔
372
        self.instruction.qubits = qargs;
217,458✔
373
    }
217,458✔
374

375
    #[getter]
376
    pub fn get_cargs(&self, py: Python) -> Py<PyTuple> {
764,698✔
377
        self.instruction.clbits.clone_ref(py)
764,698✔
378
    }
764,698✔
379

380
    #[setter]
381
    fn set_cargs(&mut self, cargs: Py<PyTuple>) {
×
382
        self.instruction.clbits = cargs;
×
383
    }
×
384

385
    /// Returns the Instruction name corresponding to the op for this node
386
    #[getter]
387
    fn get_name(&self) -> &str {
146,984✔
388
        self.instruction.operation.name()
146,984✔
389
    }
146,984✔
390

391
    #[getter]
392
    fn get_params(&self, py: Python) -> PyResult<Py<PyAny>> {
×
393
        self.instruction.get_params(py)
×
394
    }
×
395

396
    #[setter]
397
    fn set_params(&mut self, val: Bound<PyAny>) -> PyResult<()> {
×
398
        self.instruction.params = extract_params(self.instruction.op(), &val)?;
×
399
        Ok(())
×
400
    }
×
401

402
    #[getter]
403
    fn matrix<'py>(&'py self, py: Python<'py>) -> Option<Bound<'py, PyArray2<Complex64>>> {
30,094✔
404
        let matrix = self.instruction.try_matrix();
30,094✔
405
        matrix.map(|mat| mat.into_pyarray(py))
30,094✔
406
    }
30,094✔
407

408
    #[getter]
409
    fn label(&self) -> Option<&str> {
64,496✔
410
        self.instruction.label.as_ref().map(|x| x.as_str())
64,496✔
411
    }
64,496✔
412

413
    /// Is the :class:`.Operation` contained in this node a Qiskit standard gate?
414
    pub fn is_standard_gate(&self) -> bool {
×
415
        self.instruction.is_standard_gate()
×
416
    }
×
417

418
    /// Is the :class:`.Operation` contained in this node a subclass of :class:`.ControlledGate`?
419
    pub fn is_controlled_gate(&self, py: Python) -> PyResult<bool> {
×
420
        self.instruction.is_controlled_gate(py)
×
421
    }
×
422

423
    /// Is the :class:`.Operation` contained in this node a directive?
424
    pub fn is_directive(&self) -> bool {
×
425
        self.instruction.is_directive()
×
426
    }
×
427

428
    /// Is the :class:`.Operation` contained in this node a control-flow operation (i.e. an instance
429
    /// of :class:`.ControlFlowOp`)?
430
    pub fn is_control_flow(&self) -> bool {
18,222✔
431
        self.instruction.is_control_flow()
18,222✔
432
    }
18,222✔
433

434
    /// Does this node contain any :class:`.ParameterExpression` parameters?
435
    pub fn is_parameterized(&self) -> bool {
116✔
436
        self.instruction.is_parameterized()
116✔
437
    }
116✔
438

439
    #[setter]
440
    fn set_label(&mut self, val: Option<String>) {
×
441
        self.instruction.label = val.map(Box::new);
×
442
    }
×
443

444
    #[getter]
445
    fn definition<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyAny>>> {
×
446
        let definition = match self.instruction.operation.view() {
×
447
            OperationRef::StandardGate(g) => g.definition(self.instruction.params_view()),
×
448
            OperationRef::Gate(g) => g.definition(),
×
449
            OperationRef::Instruction(i) => i.definition(),
×
450
            _ => None,
×
451
        };
452
        definition
×
453
            .map(|data| data.into_py_quantum_circuit(py))
×
454
            .transpose()
×
455
    }
×
456

457
    /// Sets the Instruction name corresponding to the op for this node
458
    #[setter]
459
    fn set_name(&mut self, py: Python, new_name: Py<PyAny>) -> PyResult<()> {
×
460
        let op = self.instruction.get_operation_mut(py)?;
×
461
        op.setattr(intern!(py, "name"), new_name)?;
×
462
        self.instruction.operation = op.extract::<OperationFromPython<CircuitData>>()?.operation;
×
463
        Ok(())
×
464
    }
×
465

466
    /// Returns a representation of the DAGOpNode
467
    fn __repr__(&self, py: Python) -> PyResult<String> {
3,784✔
468
        Ok(format!(
3,784✔
469
            "DAGOpNode(op={}, qargs={}, cargs={})",
470
            self.instruction.get_operation(py)?.bind(py).repr()?,
3,784✔
471
            self.instruction.qubits.bind(py).repr()?,
3,784✔
472
            self.instruction.clbits.bind(py).repr()?
3,784✔
473
        ))
474
    }
3,784✔
475
}
476

477
/// Object to represent an incoming wire node in the DAGCircuit.
478
#[pyclass(module = "qiskit._accelerate.circuit", extends=DAGNode)]
479
pub struct DAGInNode {
480
    #[pyo3(get)]
481
    pub wire: Py<PyAny>,
482
}
483

484
impl DAGInNode {
485
    pub fn new(node: NodeIndex, wire: Py<PyAny>) -> (Self, DAGNode) {
5,845,972✔
486
        (DAGInNode { wire }, DAGNode { node: Some(node) })
5,845,972✔
487
    }
5,845,972✔
488
}
489

490
#[pymethods]
×
491
impl DAGInNode {
492
    #[new]
493
    fn py_new(wire: Py<PyAny>) -> PyResult<(Self, DAGNode)> {
18✔
494
        Ok((DAGInNode { wire }, DAGNode { node: None }))
18✔
495
    }
18✔
496

497
    fn __reduce__<'py>(slf: PyRef<'py, Self>, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
18✔
498
        let state = slf.as_ref().node.map(|node| node.index());
18✔
499
        (py.get_type::<Self>(), (&slf.wire,), state).into_pyobject(py)
18✔
500
    }
18✔
501

502
    fn __setstate__(mut slf: PyRefMut<Self>, state: &Bound<PyAny>) -> PyResult<()> {
18✔
503
        let index: Option<usize> = state.extract()?;
18✔
504
        slf.as_mut().node = index.map(NodeIndex::new);
18✔
505
        Ok(())
18✔
506
    }
18✔
507

508
    fn __hash__(slf: PyRef<'_, Self>, py: Python) -> PyResult<u64> {
3,190✔
509
        let super_ = slf.as_ref();
3,190✔
510
        let mut hasher = AHasher::default();
3,190✔
511
        hasher.write_isize(super_.py_nid());
3,190✔
512
        hasher.write_isize(slf.wire.bind(py).hash()?);
3,190✔
513
        Ok(hasher.finish())
3,190✔
514
    }
3,190✔
515

516
    fn __eq__(slf: PyRef<Self>, py: Python, other: &Bound<PyAny>) -> PyResult<bool> {
258✔
517
        match other.cast::<Self>() {
258✔
518
            Ok(other) => {
258✔
519
                let borrowed_other = other.borrow();
258✔
520
                let other_super = borrowed_other.as_ref();
258✔
521
                let super_ = slf.as_ref();
258✔
522
                Ok(super_.py_nid() == other_super.py_nid()
258✔
523
                    && slf.wire.bind(py).eq(borrowed_other.wire.clone_ref(py))?)
258✔
524
            }
525
            Err(_) => Ok(false),
×
526
        }
527
    }
258✔
528

529
    /// Returns a representation of the DAGInNode
530
    fn __repr__(&self, py: Python) -> PyResult<String> {
2✔
531
        Ok(format!("DAGInNode(wire={})", self.wire.bind(py).repr()?))
2✔
532
    }
2✔
533
}
534

535
/// Object to represent an outgoing wire node in the DAGCircuit.
536
#[pyclass(module = "qiskit._accelerate.circuit", extends=DAGNode)]
537
pub struct DAGOutNode {
538
    #[pyo3(get)]
539
    pub wire: Py<PyAny>,
540
}
541

542
impl DAGOutNode {
543
    pub fn new(node: NodeIndex, wire: Py<PyAny>) -> (Self, DAGNode) {
14,379,800✔
544
        (DAGOutNode { wire }, DAGNode { node: Some(node) })
14,379,800✔
545
    }
14,379,800✔
546
}
547

548
#[pymethods]
×
549
impl DAGOutNode {
550
    #[new]
551
    fn py_new(wire: Py<PyAny>) -> PyResult<(Self, DAGNode)> {
18✔
552
        Ok((DAGOutNode { wire }, DAGNode { node: None }))
18✔
553
    }
18✔
554

555
    fn __reduce__(slf: PyRef<Self>, py: Python) -> PyResult<Py<PyAny>> {
18✔
556
        let state = slf.as_ref().node.map(|node| node.index());
18✔
557
        (py.get_type::<Self>(), (&slf.wire,), state).into_py_any(py)
18✔
558
    }
18✔
559

560
    fn __setstate__(mut slf: PyRefMut<Self>, state: &Bound<PyAny>) -> PyResult<()> {
18✔
561
        let index: Option<usize> = state.extract()?;
18✔
562
        slf.as_mut().node = index.map(NodeIndex::new);
18✔
563
        Ok(())
18✔
564
    }
18✔
565

566
    fn __hash__(slf: PyRef<'_, Self>, py: Python) -> PyResult<u64> {
3,916✔
567
        let super_ = slf.as_ref();
3,916✔
568
        let mut hasher = AHasher::default();
3,916✔
569
        hasher.write_isize(super_.py_nid());
3,916✔
570
        hasher.write_isize(slf.wire.bind(py).hash()?);
3,916✔
571
        Ok(hasher.finish())
3,916✔
572
    }
3,916✔
573

574
    /// Returns a representation of the DAGOutNode
575
    fn __repr__(&self, py: Python) -> PyResult<String> {
2✔
576
        Ok(format!("DAGOutNode(wire={})", self.wire.bind(py).repr()?))
2✔
577
    }
2✔
578

579
    fn __eq__(slf: PyRef<Self>, py: Python, other: &Bound<PyAny>) -> PyResult<bool> {
280✔
580
        match other.cast::<Self>() {
280✔
581
            Ok(other) => {
280✔
582
                let borrowed_other = other.borrow();
280✔
583
                let other_super = borrowed_other.as_ref();
280✔
584
                let super_ = slf.as_ref();
280✔
585
                Ok(super_.py_nid() == other_super.py_nid()
280✔
586
                    && slf.wire.bind(py).eq(borrowed_other.wire.clone_ref(py))?)
280✔
587
            }
588
            Err(_) => Ok(false),
×
589
        }
590
    }
280✔
591
}
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