Skip to content

Commit

Permalink
Add CircuitDag
Browse files Browse the repository at this point in the history
  • Loading branch information
cduck committed Jul 25, 2018
1 parent af39bad commit 094469f
Show file tree
Hide file tree
Showing 5 changed files with 299 additions and 0 deletions.
2 changes: 2 additions & 0 deletions cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@

from cirq.circuits import (
Circuit,
CircuitDag,
CircuitDagAbc,
DropEmptyMoments,
DropNegligible,
ExpandComposite,
Expand Down
4 changes: 4 additions & 0 deletions cirq/circuits/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
from cirq.circuits.circuit import (
Circuit,
)
from cirq.circuits.circuit_dag import (
CircuitDag,
CircuitDagAbc,
)
from cirq.circuits.drop_empty_moments import (
DropEmptyMoments,
)
Expand Down
122 changes: 122 additions & 0 deletions cirq/circuits/circuit_dag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Copyright 2018 The ops Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Any, Generic, Iterator, Type, TypeVar

import networkx

from cirq import ops, devices, abc
from cirq.circuits import circuit


T = TypeVar('T')

class Unique(Generic[T]):
def __init__(self, val: T) -> None:
self.val = val

def __repr__(self):
return 'Unique({}, {!r})'.format(id(self), self.val)


TSelf = TypeVar('TSelf', bound='CircuitDagAbc')

class CircuitDagAbc(networkx.DiGraph, metaclass=abc.ABCMeta):
"""A representation of a Circuit as a directed acyclic graph. Subclass
CircuitDagAbc and implement can_reorder()."""

def __init__(self,
incoming_graph_data: Any = None,
device: devices.Device = devices.UnconstrainedDevice
) -> None:
super().__init__(incoming_graph_data)
self.device = device

@abc.abstractmethod
def can_reorder(self,
op1: ops.Operation,
op2: ops.Operation) -> bool:
"""Returns true if the order that op1 and op2 are applied in a circuit
does not matter."""

@staticmethod
def make_node(op: ops.Operation) -> Unique:
return Unique(op)

@classmethod
def from_circuit(cls: Type[TSelf], circuit: circuit.Circuit) -> TSelf:
return cls.from_ops(circuit.all_operations(), device=circuit.device)

@classmethod
def from_ops(cls: Type[TSelf],
*operations: ops.OP_TREE,
device: devices.Device = devices.UnconstrainedDevice
) -> TSelf:
dag = cls(device=device)
for op in ops.flatten_op_tree(operations):
dag.append(op)
return dag

def append(self, op: ops.Operation) -> None:
new_node = self.make_node(op)
self.add_edges_from([(node, new_node)
for node in self.nodes
if not self.can_reorder(node.val, new_node.val)])
self.add_node(new_node)

def all_operations(self) -> Iterator[ops.Operation]:
if len(self.nodes) <= 0:
return
g = self.copy()

def get_root_node(some_node):
pred = g.pred
while len(pred[some_node]) > 0:
some_node = next(iter(pred[some_node]))
return some_node

def get_first_node():
return get_root_node(next(iter(g.nodes)))

def get_next_node(succ):
if len(succ) > 0:
return get_root_node(next(iter(succ)))
else:
return get_first_node()

node = get_first_node()
while True:
yield node.val
succ = g.succ[node]
g.remove_node(node)

if len(g.nodes) <= 0:
return

node = get_next_node(succ)

def to_circuit(self) -> circuit.Circuit:
return circuit.Circuit.from_ops(
self.all_operations(),
strategy=circuit.InsertStrategy.EARLIEST,
device=self.device)


class CircuitDag(CircuitDagAbc):
"""A representation of a Circuit as a directed acyclic graph. Gate can
reorder only if they don't share qubits."""
def can_reorder(self,
op1: ops.Operation,
op2: ops.Operation) -> bool:
return len(set(op1.qubits) & set(op2.qubits)) == 0
170 changes: 170 additions & 0 deletions cirq/circuits/circuit_dag_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# Copyright 2018 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import networkx

import cirq


def test_wrapper_eq():
q0, q1 = cirq.LineQubit.range(2)
eq = cirq.testing.EqualsTester()
eq.add_equality_group(cirq.CircuitDag.make_node(cirq.X(q0)))
eq.add_equality_group(cirq.CircuitDag.make_node(cirq.X(q0)))
eq.add_equality_group(cirq.CircuitDag.make_node(cirq.Y(q0)))
eq.add_equality_group(cirq.CircuitDag.make_node(cirq.X(q1)))


def test_wrapper_repr():
q0 = cirq.LineQubit(0)

node = cirq.CircuitDag.make_node(cirq.X(q0))
assert (repr(node) ==
'Unique(' + str(id(node)) + ', GateOperation(X, (LineQubit(0),)))')


def test_init():
dag = cirq.CircuitDag()
assert networkx.dag.is_directed_acyclic_graph(dag)
assert list(dag.nodes) == []
assert list(dag.edges) == []


def test_append():
q0 = cirq.LineQubit(0)
dag = cirq.CircuitDag()
dag.append(cirq.X(q0))
dag.append(cirq.Y(q0))
print(dag.edges)
assert networkx.dag.is_directed_acyclic_graph(dag)
assert len(dag.nodes) == 2
assert ([(n1.val, n2.val) for n1, n2 in dag.edges] ==
[(cirq.X(q0), cirq.Y(q0))])


def test_two_identical_ops():
q0 = cirq.LineQubit(0)
dag = cirq.CircuitDag()
dag.append(cirq.X(q0))
dag.append(cirq.Y(q0))
dag.append(cirq.X(q0))
assert networkx.dag.is_directed_acyclic_graph(dag)
assert len(dag.nodes) == 3
assert (set((n1.val, n2.val) for n1, n2 in dag.edges) ==
set(((cirq.X(q0), cirq.Y(q0)),
(cirq.X(q0), cirq.X(q0)),
(cirq.Y(q0), cirq.X(q0)))))


def test_from_ops():
q0 = cirq.LineQubit(0)
dag = cirq.CircuitDag.from_ops(
cirq.X(q0),
cirq.Y(q0))
assert networkx.dag.is_directed_acyclic_graph(dag)
assert len(dag.nodes) == 2
assert ([(n1.val, n2.val) for n1, n2 in dag.edges] ==
[(cirq.X(q0), cirq.Y(q0))])


def test_from_circuit():
q0 = cirq.LineQubit(0)
circuit = cirq.Circuit.from_ops(
cirq.X(q0),
cirq.Y(q0))
dag = cirq.CircuitDag.from_circuit(circuit)
assert networkx.dag.is_directed_acyclic_graph(dag)
assert len(dag.nodes) == 2
assert ([(n1.val, n2.val) for n1, n2 in dag.edges] ==
[(cirq.X(q0), cirq.Y(q0))])


def test_from_circuit_with_device():
q0 = cirq.GridQubit(5, 5)
circuit = cirq.Circuit.from_ops(
cirq.X(q0),
cirq.Y(q0),
device=cirq.google.Bristlecone)
dag = cirq.CircuitDag.from_circuit(circuit)
assert networkx.dag.is_directed_acyclic_graph(dag)
assert dag.device == circuit.device
assert len(dag.nodes) == 2
assert ([(n1.val, n2.val) for n1, n2 in dag.edges] ==
[(cirq.X(q0), cirq.Y(q0))])


def test_to_empty_circuit():
circuit = cirq.Circuit()
dag = cirq.CircuitDag.from_circuit(circuit)
assert networkx.dag.is_directed_acyclic_graph(dag)
assert circuit == dag.to_circuit()


def test_to_circuit():
q0 = cirq.LineQubit(0)
circuit = cirq.Circuit.from_ops(
cirq.X(q0),
cirq.Y(q0))
dag = cirq.CircuitDag.from_circuit(circuit)

assert networkx.dag.is_directed_acyclic_graph(dag)
# Only one possible output circuit for this simple case
assert circuit == dag.to_circuit()

cirq.testing.assert_allclose_up_to_global_phase(
circuit.to_unitary_matrix(),
dag.to_circuit().to_unitary_matrix(),
atol=1e-7)


def test_larger_circuit():
q0, q1, q2, q3 = cirq.google.Bristlecone.col(5)[:4]
# This circuit does not have CZ gates on adjacent qubits because the order
# dag.to_circuit() would append them is non-deterministic.
circuit = cirq.Circuit.from_ops(
cirq.X(q0),
cirq.CZ(q1, q2),
cirq.CZ(q0, q1),
cirq.Y(q0),
cirq.Z(q0),
cirq.CZ(q1, q2),
cirq.X(q0),
cirq.Y(q0),
cirq.CZ(q0, q1),
cirq.T(q3),
strategy=cirq.InsertStrategy.EARLIEST,
device=cirq.google.Bristlecone)

dag = cirq.CircuitDag.from_circuit(circuit)

assert networkx.dag.is_directed_acyclic_graph(dag)
assert circuit.device == dag.to_circuit().device
# Operation order within a moment is non-deterministic
# but text diagrams still look the same.
assert (circuit.to_text_diagram() ==
dag.to_circuit().to_text_diagram() ==
"""
(0, 5): ───X───@───Y───Z───X───Y───@───
│ │
(1, 5): ───@───@───@───────────────@───
│ │
(2, 5): ───@───────@───────────────────
(3, 5): ───T───────────────────────────
""".strip())

cirq.testing.assert_allclose_up_to_global_phase(
circuit.to_unitary_matrix(),
dag.to_circuit().to_unitary_matrix(),
atol=1e-7)
1 change: 1 addition & 0 deletions runtime-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ protobuf~=3.5
requests~=2.18
sortedcontainers~=2.0
scipy~=1.1.0
networkx

0 comments on commit 094469f

Please sign in to comment.