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

Qiskit / qiskit / 12969952034

26 Jan 2025 12:56AM UTC coverage: 88.93% (-0.01%) from 88.941%
12969952034

Pull #13736

github

web-flow
Merge 14225cb4c into 86f203d0a
Pull Request #13736: Remove sort_key attribute from DAGNode

32 of 32 new or added lines in 4 files covered. (100.0%)

16 existing lines in 3 files now uncovered.

79396 of 89279 relevant lines covered (88.93%)

351684.28 hits per line

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

77.46
/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::circuit_instruction::{CircuitInstruction, OperationFromPython};
18
use crate::imports::QUANTUM_CIRCUIT;
19
use crate::operations::{Operation, Param};
20
use crate::TupleLikeArg;
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 numpy::IntoPyArray;
28
use numpy::PyArray2;
29
use pyo3::exceptions::PyValueError;
30
use pyo3::prelude::*;
31
use pyo3::types::PyTuple;
32
use pyo3::IntoPyObjectExt;
33
use pyo3::{intern, PyObject, PyResult};
34

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

154
    fn __eq__(slf: PyRef<Self>, py: Python, other: &Bound<PyAny>) -> PyResult<bool> {
58,216✔
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.downcast::<Self>() else {
58,216✔
163
            return Ok(false);
690✔
164
        };
165
        let borrowed_other = other.borrow();
57,526✔
166
        let other_super = borrowed_other.as_ref();
57,526✔
167
        let super_ = slf.as_ref();
57,526✔
168

57,526✔
169
        if super_.py_nid() != other_super.py_nid() {
57,526✔
170
            return Ok(false);
690✔
171
        }
56,836✔
172
        if !slf
56,836✔
173
            .instruction
56,836✔
174
            .operation
56,836✔
175
            .py_eq(py, &borrowed_other.instruction.operation)?
56,836✔
176
        {
177
            return Ok(false);
×
178
        }
56,836✔
179
        let params_eq = if slf.instruction.operation.try_standard_gate().is_some() {
56,836✔
180
            let mut params_eq = true;
44,938✔
181
            for (a, b) in slf
44,938✔
182
                .instruction
44,938✔
183
                .params
44,938✔
184
                .iter()
44,938✔
185
                .zip(borrowed_other.instruction.params.iter())
44,938✔
186
            {
187
                let res = match [a, b] {
25,954✔
188
                    [Param::Float(float_a), Param::Float(float_b)] => {
25,846✔
189
                        relative_eq!(float_a, float_b, max_relative = 1e-10)
25,846✔
190
                    }
191
                    [Param::ParameterExpression(param_a), Param::ParameterExpression(param_b)] => {
108✔
192
                        param_a.bind(py).eq(param_b)?
108✔
193
                    }
194
                    [Param::Obj(param_a), Param::Obj(param_b)] => param_a.bind(py).eq(param_b)?,
×
195
                    _ => false,
×
196
                };
197
                if !res {
25,954✔
198
                    params_eq = false;
×
199
                    break;
×
200
                }
25,954✔
201
            }
202
            params_eq
44,938✔
203
        } else {
204
            // We've already evaluated the parameters are equal here via the Python space equality
205
            // check so if we're not comparing standard gates and we've reached this point we know
206
            // the parameters are already equal.
207
            true
11,898✔
208
        };
209

210
        Ok(params_eq
56,836✔
211
            && slf
56,836✔
212
                .instruction
56,836✔
213
                .qubits
56,836✔
214
                .bind(py)
56,836✔
215
                .eq(borrowed_other.instruction.qubits.clone_ref(py))?
56,836✔
216
            && slf
56,836✔
217
                .instruction
56,836✔
218
                .clbits
56,836✔
219
                .bind(py)
56,836✔
220
                .eq(borrowed_other.instruction.clbits.clone_ref(py))?)
56,836✔
221
    }
58,216✔
222

223
    #[pyo3(signature = (instruction, /, *, deepcopy=false))]
224
    #[staticmethod]
225
    fn from_instruction(
136,256✔
226
        py: Python,
136,256✔
227
        mut instruction: CircuitInstruction,
136,256✔
228
        deepcopy: bool,
136,256✔
229
    ) -> PyResult<PyObject> {
136,256✔
230
        if deepcopy {
136,256✔
UNCOV
231
            instruction.operation = instruction.operation.py_deepcopy(py, None)?;
×
232
            #[cfg(feature = "cache_pygates")]
233
            {
×
234
                instruction.py_op = OnceLock::new();
×
235
            }
×
236
        }
136,256✔
237
        let base = PyClassInitializer::from(DAGNode { node: None });
136,256✔
238
        let sub = base.add_subclass(DAGOpNode { instruction });
136,256✔
239
        Py::new(py, sub)?.into_py_any(py)
136,256✔
240
    }
136,256✔
241

242
    fn __reduce__(slf: PyRef<Self>, py: Python) -> PyResult<PyObject> {
40,840✔
243
        let state = slf.as_ref().node.map(|node| node.index());
40,840✔
244
        let temp = (
40,840✔
245
            slf.instruction.get_operation(py)?,
40,840✔
246
            &slf.instruction.qubits,
40,840✔
247
            &slf.instruction.clbits,
40,840✔
248
        );
40,840✔
249
        (py.get_type::<Self>(), temp, state).into_py_any(py)
40,840✔
250
    }
40,840✔
251

252
    fn __setstate__(mut slf: PyRefMut<Self>, state: &Bound<PyAny>) -> PyResult<()> {
40,840✔
253
        let index: Option<usize> = state.extract()?;
40,840✔
254
        slf.as_mut().node = index.map(NodeIndex::new);
40,840✔
255
        Ok(())
40,840✔
256
    }
40,840✔
257

258
    /// Get a `CircuitInstruction` that represents the same information as this `DAGOpNode`.  If
259
    /// `deepcopy`, any internal Python objects are deep-copied.
260
    ///
261
    /// Note: this ought to be a temporary method, while the DAG/QuantumCircuit converters still go
262
    /// via Python space; this still involves copy-out and copy-in of the data, whereas doing it all
263
    /// within Rust space could directly re-pack the instruction from a `DAGOpNode` to a
264
    /// `PackedInstruction` with no intermediate copy.
265
    #[pyo3(signature = (/, *, deepcopy=false))]
266
    fn _to_circuit_instruction(&self, py: Python, deepcopy: bool) -> PyResult<CircuitInstruction> {
94,358✔
267
        Ok(CircuitInstruction {
94,358✔
268
            operation: if deepcopy {
94,358✔
269
                self.instruction.operation.py_deepcopy(py, None)?
×
270
            } else {
271
                self.instruction.operation.clone()
94,358✔
272
            },
273
            qubits: self.instruction.qubits.clone_ref(py),
94,358✔
274
            clbits: self.instruction.clbits.clone_ref(py),
94,358✔
275
            params: self.instruction.params.clone(),
94,358✔
276
            extra_attrs: self.instruction.extra_attrs.clone(),
94,358✔
277
            #[cfg(feature = "cache_pygates")]
94,358✔
278
            py_op: OnceLock::new(),
94,358✔
279
        })
280
    }
94,358✔
281

282
    #[getter]
283
    fn get_op(&self, py: Python) -> PyResult<PyObject> {
2,248,870✔
284
        self.instruction.get_operation(py)
2,248,870✔
285
    }
2,248,870✔
286

287
    #[setter]
288
    fn set_op(&mut self, op: &Bound<PyAny>) -> PyResult<()> {
2✔
289
        let res = op.extract::<OperationFromPython>()?;
2✔
290
        self.instruction.operation = res.operation;
2✔
291
        self.instruction.params = res.params;
2✔
292
        self.instruction.extra_attrs = res.extra_attrs;
2✔
293
        #[cfg(feature = "cache_pygates")]
2✔
294
        {
2✔
295
            self.instruction.py_op = op.clone().unbind().into();
2✔
296
        }
2✔
297
        Ok(())
2✔
298
    }
2✔
299

300
    #[getter]
301
    fn num_qubits(&self) -> u32 {
120,252✔
302
        self.instruction.operation.num_qubits()
120,252✔
303
    }
120,252✔
304

305
    #[getter]
306
    fn num_clbits(&self) -> u32 {
×
307
        self.instruction.operation.num_clbits()
×
308
    }
×
309

310
    #[getter]
311
    pub fn get_qargs(&self, py: Python) -> Py<PyTuple> {
3,279,348✔
312
        self.instruction.qubits.clone_ref(py)
3,279,348✔
313
    }
3,279,348✔
314

315
    #[setter]
316
    fn set_qargs(&mut self, qargs: Py<PyTuple>) {
31,034✔
317
        self.instruction.qubits = qargs;
31,034✔
318
    }
31,034✔
319

320
    #[getter]
321
    pub fn get_cargs(&self, py: Python) -> Py<PyTuple> {
1,355,590✔
322
        self.instruction.clbits.clone_ref(py)
1,355,590✔
323
    }
1,355,590✔
324

325
    #[setter]
326
    fn set_cargs(&mut self, cargs: Py<PyTuple>) {
×
327
        self.instruction.clbits = cargs;
×
328
    }
×
329

330
    /// Returns the Instruction name corresponding to the op for this node
331
    #[getter]
332
    fn get_name(&self) -> &str {
883,038✔
333
        self.instruction.operation.name()
883,038✔
334
    }
883,038✔
335

336
    #[getter]
337
    fn get_params(&self) -> &[Param] {
2,510✔
338
        self.instruction.params.as_slice()
2,510✔
339
    }
2,510✔
340

341
    #[setter]
342
    fn set_params(&mut self, val: smallvec::SmallVec<[crate::operations::Param; 3]>) {
×
343
        self.instruction.params = val;
×
344
    }
×
345

346
    #[getter]
347
    fn matrix<'py>(&'py self, py: Python<'py>) -> Option<Bound<'py, PyArray2<Complex64>>> {
4,284✔
348
        let matrix = self.instruction.operation.matrix(&self.instruction.params);
4,284✔
349
        matrix.map(|mat| mat.into_pyarray(py))
4,284✔
350
    }
4,284✔
351

352
    #[getter]
353
    fn label(&self) -> Option<&str> {
102,892✔
354
        self.instruction.extra_attrs.label()
102,892✔
355
    }
102,892✔
356

357
    #[getter]
358
    fn condition(&self, py: Python) -> Option<PyObject> {
634,988✔
359
        self.instruction
634,988✔
360
            .extra_attrs
634,988✔
361
            .condition()
634,988✔
362
            .map(|x| x.clone_ref(py))
634,988✔
363
    }
634,988✔
364

365
    #[getter]
366
    fn duration(&self, py: Python) -> Option<PyObject> {
1,132✔
367
        self.instruction
1,132✔
368
            .extra_attrs
1,132✔
369
            .duration()
1,132✔
370
            .map(|x| x.clone_ref(py))
1,132✔
371
    }
1,132✔
372

373
    #[getter]
374
    fn unit(&self) -> Option<&str> {
×
375
        self.instruction.extra_attrs.unit()
×
376
    }
×
377

378
    /// Is the :class:`.Operation` contained in this node a Qiskit standard gate?
379
    pub fn is_standard_gate(&self) -> bool {
169,722✔
380
        self.instruction.is_standard_gate()
169,722✔
381
    }
169,722✔
382

383
    /// Is the :class:`.Operation` contained in this node a subclass of :class:`.ControlledGate`?
384
    pub fn is_controlled_gate(&self, py: Python) -> PyResult<bool> {
148,472✔
385
        self.instruction.is_controlled_gate(py)
148,472✔
386
    }
148,472✔
387

388
    /// Is the :class:`.Operation` contained in this node a directive?
389
    pub fn is_directive(&self) -> bool {
410,700✔
390
        self.instruction.is_directive()
410,700✔
391
    }
410,700✔
392

393
    /// Is the :class:`.Operation` contained in this node a control-flow operation (i.e. an instance
394
    /// of :class:`.ControlFlowOp`)?
395
    pub fn is_control_flow(&self) -> bool {
457,936✔
396
        self.instruction.is_control_flow()
457,936✔
397
    }
457,936✔
398

399
    /// Does this node contain any :class:`.ParameterExpression` parameters?
400
    pub fn is_parameterized(&self) -> bool {
×
401
        self.instruction.is_parameterized()
×
402
    }
×
403

404
    #[setter]
405
    fn set_label(&mut self, val: Option<String>) {
×
406
        self.instruction.extra_attrs.set_label(val);
×
407
    }
×
408

409
    #[getter]
410
    fn definition<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyAny>>> {
×
411
        self.instruction
×
412
            .operation
×
413
            .definition(&self.instruction.params)
×
414
            .map(|data| {
×
415
                QUANTUM_CIRCUIT
×
416
                    .get_bound(py)
×
417
                    .call_method1(intern!(py, "_from_circuit_data"), (data,))
×
418
            })
×
419
            .transpose()
×
420
    }
×
421

422
    /// Sets the Instruction name corresponding to the op for this node
423
    #[setter]
424
    fn set_name(&mut self, py: Python, new_name: PyObject) -> PyResult<()> {
×
425
        let op = self.instruction.get_operation_mut(py)?;
×
426
        op.setattr(intern!(py, "name"), new_name)?;
×
427
        self.instruction.operation = op.extract::<OperationFromPython>()?.operation;
×
428
        Ok(())
×
429
    }
×
430

431
    /// Returns a representation of the DAGOpNode
432
    fn __repr__(&self, py: Python) -> PyResult<String> {
4✔
433
        Ok(format!(
4✔
434
            "DAGOpNode(op={}, qargs={}, cargs={})",
4✔
435
            self.instruction.get_operation(py)?.bind(py).repr()?,
4✔
436
            self.instruction.qubits.bind(py).repr()?,
4✔
437
            self.instruction.clbits.bind(py).repr()?
4✔
438
        ))
439
    }
4✔
440
}
441

442
/// Object to represent an incoming wire node in the DAGCircuit.
443
#[pyclass(module = "qiskit._accelerate.circuit", extends=DAGNode)]
2,038✔
444
pub struct DAGInNode {
445
    #[pyo3(get)]
446
    pub wire: PyObject,
20✔
447
}
448

449
impl DAGInNode {
450
    pub fn new(node: NodeIndex, wire: PyObject) -> (Self, DAGNode) {
5,772,494✔
451
        (DAGInNode { wire }, DAGNode { node: Some(node) })
5,772,494✔
452
    }
5,772,494✔
453
}
454

455
#[pymethods]
15,426✔
456
impl DAGInNode {
457
    #[new]
458
    fn py_new(wire: PyObject) -> PyResult<(Self, DAGNode)> {
5,982✔
459
        Ok((DAGInNode { wire }, DAGNode { node: None }))
5,982✔
460
    }
5,982✔
461

462
    fn __reduce__<'py>(slf: PyRef<'py, Self>, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
5,982✔
463
        let state = slf.as_ref().node.map(|node| node.index());
5,982✔
464
        (py.get_type::<Self>(), (&slf.wire,), state).into_pyobject(py)
5,982✔
465
    }
5,982✔
466

467
    fn __setstate__(mut slf: PyRefMut<Self>, state: &Bound<PyAny>) -> PyResult<()> {
5,982✔
468
        let index: Option<usize> = state.extract()?;
5,982✔
469
        slf.as_mut().node = index.map(NodeIndex::new);
5,982✔
470
        Ok(())
5,982✔
471
    }
5,982✔
472

473
    fn __hash__(slf: PyRef<'_, Self>, py: Python) -> PyResult<u64> {
3,190✔
474
        let super_ = slf.as_ref();
3,190✔
475
        let mut hasher = AHasher::default();
3,190✔
476
        hasher.write_isize(super_.py_nid());
3,190✔
477
        hasher.write_isize(slf.wire.bind(py).hash()?);
3,190✔
478
        Ok(hasher.finish())
3,190✔
479
    }
3,190✔
480

481
    fn __eq__(slf: PyRef<Self>, py: Python, other: &Bound<PyAny>) -> PyResult<bool> {
260✔
482
        match other.downcast::<Self>() {
260✔
483
            Ok(other) => {
260✔
484
                let borrowed_other = other.borrow();
260✔
485
                let other_super = borrowed_other.as_ref();
260✔
486
                let super_ = slf.as_ref();
260✔
487
                Ok(super_.py_nid() == other_super.py_nid()
260✔
488
                    && slf.wire.bind(py).eq(borrowed_other.wire.clone_ref(py))?)
260✔
489
            }
490
            Err(_) => Ok(false),
×
491
        }
492
    }
260✔
493

494
    /// Returns a representation of the DAGInNode
495
    fn __repr__(&self, py: Python) -> PyResult<String> {
2✔
496
        Ok(format!("DAGInNode(wire={})", self.wire.bind(py).repr()?))
2✔
497
    }
2✔
498
}
499

500
/// Object to represent an outgoing wire node in the DAGCircuit.
501
#[pyclass(module = "qiskit._accelerate.circuit", extends=DAGNode)]
4,570✔
502
pub struct DAGOutNode {
503
    #[pyo3(get)]
504
    pub wire: PyObject,
20✔
505
}
506

507
impl DAGOutNode {
508
    pub fn new(node: NodeIndex, wire: PyObject) -> (Self, DAGNode) {
5,864,594✔
509
        (DAGOutNode { wire }, DAGNode { node: Some(node) })
5,864,594✔
510
    }
5,864,594✔
511
}
512

513
#[pymethods]
16,174✔
514
impl DAGOutNode {
515
    #[new]
516
    fn py_new(wire: PyObject) -> PyResult<(Self, DAGNode)> {
5,982✔
517
        Ok((DAGOutNode { wire }, DAGNode { node: None }))
5,982✔
518
    }
5,982✔
519

520
    fn __reduce__(slf: PyRef<Self>, py: Python) -> PyResult<PyObject> {
5,982✔
521
        let state = slf.as_ref().node.map(|node| node.index());
5,982✔
522
        (py.get_type::<Self>(), (&slf.wire,), state).into_py_any(py)
5,982✔
523
    }
5,982✔
524

525
    fn __setstate__(mut slf: PyRefMut<Self>, state: &Bound<PyAny>) -> PyResult<()> {
5,982✔
526
        let index: Option<usize> = state.extract()?;
5,982✔
527
        slf.as_mut().node = index.map(NodeIndex::new);
5,982✔
528
        Ok(())
5,982✔
529
    }
5,982✔
530

531
    fn __hash__(slf: PyRef<'_, Self>, py: Python) -> PyResult<u64> {
3,916✔
532
        let super_ = slf.as_ref();
3,916✔
533
        let mut hasher = AHasher::default();
3,916✔
534
        hasher.write_isize(super_.py_nid());
3,916✔
535
        hasher.write_isize(slf.wire.bind(py).hash()?);
3,916✔
536
        Ok(hasher.finish())
3,916✔
537
    }
3,916✔
538

539
    /// Returns a representation of the DAGOutNode
540
    fn __repr__(&self, py: Python) -> PyResult<String> {
2✔
541
        Ok(format!("DAGOutNode(wire={})", self.wire.bind(py).repr()?))
2✔
542
    }
2✔
543

544
    fn __eq__(slf: PyRef<Self>, py: Python, other: &Bound<PyAny>) -> PyResult<bool> {
282✔
545
        match other.downcast::<Self>() {
282✔
546
            Ok(other) => {
282✔
547
                let borrowed_other = other.borrow();
282✔
548
                let other_super = borrowed_other.as_ref();
282✔
549
                let super_ = slf.as_ref();
282✔
550
                Ok(super_.py_nid() == other_super.py_nid()
282✔
551
                    && slf.wire.bind(py).eq(borrowed_other.wire.clone_ref(py))?)
282✔
552
            }
553
            Err(_) => Ok(false),
×
554
        }
555
    }
282✔
556
}
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