Skip to content

Commit

Permalink
Revert changes not for this PR.
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinhartman committed May 8, 2024
1 parent ee6f2eb commit 7657341
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 58 deletions.
2 changes: 1 addition & 1 deletion crates/circuit/src/circuit_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@

use crate::bit_data::BitData;
use crate::circuit_instruction::CircuitInstruction;
use crate::Interner;
use crate::packed_instruction::{InstructionPacker, PackedInstruction};
use crate::slotted_cache::{CacheSlot, SlottedCache};
use crate::Interner;
use crate::{BitType, Clbit, PyNativeMapper, Qubit, SliceOrInt};

use pyo3::exceptions::{PyIndexError, PyRuntimeError, PyValueError};
Expand Down
2 changes: 1 addition & 1 deletion crates/circuit/src/packed_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
// that they have been altered from the originals.

use crate::circuit_instruction::CircuitInstruction;
use crate::Interner;
use crate::slotted_cache::CacheSlot;
use crate::Interner;
use crate::{Clbit, PyNativeMapper, Qubit};
use pyo3::exceptions::PyKeyError;
use pyo3::prelude::*;
Expand Down
208 changes: 152 additions & 56 deletions qiskit/dagcircuit/dagnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
"""Objects to represent the information at a node in the DAGCircuit."""
from __future__ import annotations

import itertools
import typing
import uuid
from collections.abc import Iterable


import qiskit._accelerate.circuit
from qiskit.circuit import (
Qubit,
Clbit,
Expand All @@ -37,11 +39,6 @@
from qiskit.dagcircuit import DAGCircuit


DAGNode = qiskit._accelerate.circuit.DAGNode
DAGOpNode = qiskit._accelerate.circuit.DAGOpNode
DAGInNode = qiskit._accelerate.circuit.DAGInNode
DAGOutNode = qiskit._accelerate.circuit.DAGOutNode

def _legacy_condition_eq(cond1, cond2, bit_indices1, bit_indices2) -> bool:
if cond1 is cond2 is None:
return True
Expand Down Expand Up @@ -167,6 +164,7 @@ def _for_loop_eq(node1, node2, bit_indices1, bit_indices2):
body2, node2.qargs, node2.cargs, bit_indices2
)


_SEMANTIC_EQ_CONTROL_FLOW = {
IfElseOp: _condition_op_eq,
WhileLoopOp: _condition_op_eq,
Expand All @@ -176,61 +174,159 @@ def _for_loop_eq(node1, node2, bit_indices1, bit_indices2):

_SEMANTIC_EQ_SYMMETRIC = frozenset({"barrier", "swap", "break_loop", "continue_loop"})

# Note: called from dag_node.rs.
def _semantic_eq(node1, node2, bit_indices1, bit_indices2):
"""
Check if DAG nodes are considered equivalent, e.g., as a node_match for
:func:`rustworkx.is_isomorphic_node_match`.
Args:
node1 (DAGOpNode, DAGInNode, DAGOutNode): A node to compare.
node2 (DAGOpNode, DAGInNode, DAGOutNode): The other node to compare.
bit_indices1 (dict): Dictionary mapping Bit instances to their index
within the circuit containing node1
bit_indices2 (dict): Dictionary mapping Bit instances to their index
within the circuit containing node2
Return:
Bool: If node1 == node2
"""
if not isinstance(node1, DAGOpNode) or not isinstance(node1, DAGOpNode):
return type(node1) is type(node2) and bit_indices1.get(node1.wire) == bit_indices2.get(
node2.wire
)
if isinstance(node1.op, ControlFlowOp) and isinstance(node2.op, ControlFlowOp):
# While control-flow operations aren't represented natively in the DAG, we have to do
# some unpleasant dispatching and very manual handling. Once they have more first-class
# support we'll still be dispatching, but it'll look more appropriate (like the dispatch
# based on `DAGOpNode`/`DAGInNode`/`DAGOutNode` that already exists) and less like we're
# duplicating code from the `ControlFlowOp` classes.
if type(node1.op) is not type(node2.op):
return False
comparer = _SEMANTIC_EQ_CONTROL_FLOW.get(type(node1.op))
if comparer is None: # pragma: no cover
raise RuntimeError(f"unhandled control-flow operation: {type(node1.op)}")
return comparer(node1, node2, bit_indices1, bit_indices2)

node1_qargs = [bit_indices1[qarg] for qarg in node1.qargs]
node1_cargs = [bit_indices1[carg] for carg in node1.cargs]
class DAGNode:
"""Parent class for DAGOpNode, DAGInNode, and DAGOutNode."""

node2_qargs = [bit_indices2[qarg] for qarg in node2.qargs]
node2_cargs = [bit_indices2[carg] for carg in node2.cargs]
__slots__ = ["_node_id"]

# For barriers, qarg order is not significant so compare as sets
if node1.op.name == node2.op.name and node1.name in _SEMANTIC_EQ_SYMMETRIC:
node1_qargs = set(node1_qargs)
node1_cargs = set(node1_cargs)
node2_qargs = set(node2_qargs)
node2_cargs = set(node2_cargs)
def __init__(self, nid=-1):
"""Create a node"""
self._node_id = nid

return (
def __lt__(self, other):
return self._node_id < other._node_id

def __gt__(self, other):
return self._node_id > other._node_id

def __str__(self):
# TODO is this used anywhere other than in DAG drawing?
# needs to be unique as it is what pydot uses to distinguish nodes
return str(id(self))

@staticmethod
def semantic_eq(node1, node2, bit_indices1, bit_indices2):
"""
Check if DAG nodes are considered equivalent, e.g., as a node_match for
:func:`rustworkx.is_isomorphic_node_match`.
Args:
node1 (DAGOpNode, DAGInNode, DAGOutNode): A node to compare.
node2 (DAGOpNode, DAGInNode, DAGOutNode): The other node to compare.
bit_indices1 (dict): Dictionary mapping Bit instances to their index
within the circuit containing node1
bit_indices2 (dict): Dictionary mapping Bit instances to their index
within the circuit containing node2
Return:
Bool: If node1 == node2
"""
if not isinstance(node1, DAGOpNode) or not isinstance(node1, DAGOpNode):
return type(node1) is type(node2) and bit_indices1.get(node1.wire) == bit_indices2.get(
node2.wire
)
if isinstance(node1.op, ControlFlowOp) and isinstance(node2.op, ControlFlowOp):
# While control-flow operations aren't represented natively in the DAG, we have to do
# some unpleasant dispatching and very manual handling. Once they have more first-class
# support we'll still be dispatching, but it'll look more appropriate (like the dispatch
# based on `DAGOpNode`/`DAGInNode`/`DAGOutNode` that already exists) and less like we're
# duplicating code from the `ControlFlowOp` classes.
if type(node1.op) is not type(node2.op):
return False
comparer = _SEMANTIC_EQ_CONTROL_FLOW.get(type(node1.op))
if comparer is None: # pragma: no cover
raise RuntimeError(f"unhandled control-flow operation: {type(node1.op)}")
return comparer(node1, node2, bit_indices1, bit_indices2)

node1_qargs = [bit_indices1[qarg] for qarg in node1.qargs]
node1_cargs = [bit_indices1[carg] for carg in node1.cargs]

node2_qargs = [bit_indices2[qarg] for qarg in node2.qargs]
node2_cargs = [bit_indices2[carg] for carg in node2.cargs]

# For barriers, qarg order is not significant so compare as sets
if node1.op.name == node2.op.name and node1.name in _SEMANTIC_EQ_SYMMETRIC:
node1_qargs = set(node1_qargs)
node1_cargs = set(node1_cargs)
node2_qargs = set(node2_qargs)
node2_cargs = set(node2_cargs)

return (
node1_qargs == node2_qargs
and node1_cargs == node2_cargs
and _legacy_condition_eq(
getattr(node1.op, "condition", None),
getattr(node2.op, "condition", None),
bit_indices1,
bit_indices2,
)
getattr(node1.op, "condition", None),
getattr(node2.op, "condition", None),
bit_indices1,
bit_indices2,
)
and node1.op == node2.op
)
)


class DAGOpNode(DAGNode):
"""Object to represent an Instruction at a node in the DAGCircuit."""

__slots__ = ["op", "qargs", "cargs", "sort_key"]

def __init__(
self, op: Operation, qargs: Iterable[Qubit] = (), cargs: Iterable[Clbit] = (), dag=None
):
"""Create an Instruction node"""
super().__init__()
self.op = op
self.qargs = tuple(qargs)
self.cargs = tuple(cargs)
if dag is not None:
cache_key = (self.qargs, self.cargs)
key = dag._key_cache.get(cache_key, None)
if key is not None:
self.sort_key = key
else:
self.sort_key = ",".join(
f"{dag.find_bit(q).index:04d}" for q in itertools.chain(*cache_key)
)
dag._key_cache[cache_key] = self.sort_key
else:
self.sort_key = str(self.qargs)

@property
def name(self):
"""Returns the Instruction name corresponding to the op for this node"""
return self.op.name

@name.setter
def name(self, new_name):
"""Sets the Instruction name corresponding to the op for this node"""
self.op.name = new_name

def __repr__(self):
"""Returns a representation of the DAGOpNode"""
return f"DAGOpNode(op={self.op}, qargs={self.qargs}, cargs={self.cargs})"


class DAGInNode(DAGNode):
"""Object to represent an incoming wire node in the DAGCircuit."""

__slots__ = ["wire", "sort_key"]

def __init__(self, wire):
"""Create an incoming node"""
super().__init__()
self.wire = wire
# TODO sort_key which is used in dagcircuit.topological_nodes
# only works as str([]) for DAGInNodes. Need to figure out why.
self.sort_key = str([])

def __repr__(self):
"""Returns a representation of the DAGInNode"""
return f"DAGInNode(wire={self.wire})"


class DAGOutNode(DAGNode):
"""Object to represent an outgoing wire node in the DAGCircuit."""

__slots__ = ["wire", "sort_key"]

def __init__(self, wire):
"""Create an outgoing node"""
super().__init__()
self.wire = wire
# TODO sort_key which is used in dagcircuit.topological_nodes
# only works as str([]) for DAGOutNodes. Need to figure out why.
self.sort_key = str([])

def __repr__(self):
"""Returns a representation of the DAGOutNode"""
return f"DAGOutNode(wire={self.wire})"

0 comments on commit 7657341

Please sign in to comment.