From 094469f219d210bfb5a36c00db39dddc4551f017 Mon Sep 17 00:00:00 2001 From: Casey Duckering Date: Wed, 25 Jul 2018 00:33:39 -0700 Subject: [PATCH] Add CircuitDag --- cirq/__init__.py | 2 + cirq/circuits/__init__.py | 4 + cirq/circuits/circuit_dag.py | 122 +++++++++++++++++++++ cirq/circuits/circuit_dag_test.py | 170 ++++++++++++++++++++++++++++++ runtime-requirements.txt | 1 + 5 files changed, 299 insertions(+) create mode 100644 cirq/circuits/circuit_dag.py create mode 100644 cirq/circuits/circuit_dag_test.py diff --git a/cirq/__init__.py b/cirq/__init__.py index 38d20130f5f..b449fff980b 100644 --- a/cirq/__init__.py +++ b/cirq/__init__.py @@ -30,6 +30,8 @@ from cirq.circuits import ( Circuit, + CircuitDag, + CircuitDagAbc, DropEmptyMoments, DropNegligible, ExpandComposite, diff --git a/cirq/circuits/__init__.py b/cirq/circuits/__init__.py index ce212a0e6fb..a0e93ebc333 100644 --- a/cirq/circuits/__init__.py +++ b/cirq/circuits/__init__.py @@ -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, ) diff --git a/cirq/circuits/circuit_dag.py b/cirq/circuits/circuit_dag.py new file mode 100644 index 00000000000..b8fa7e49f50 --- /dev/null +++ b/cirq/circuits/circuit_dag.py @@ -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 diff --git a/cirq/circuits/circuit_dag_test.py b/cirq/circuits/circuit_dag_test.py new file mode 100644 index 00000000000..f4453d56b30 --- /dev/null +++ b/cirq/circuits/circuit_dag_test.py @@ -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) diff --git a/runtime-requirements.txt b/runtime-requirements.txt index 7ca5348136c..4dbf9f84a88 100644 --- a/runtime-requirements.txt +++ b/runtime-requirements.txt @@ -5,3 +5,4 @@ protobuf~=3.5 requests~=2.18 sortedcontainers~=2.0 scipy~=1.1.0 +networkx