Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use dataclasses instead of namedtuples in device module #1149

Merged
merged 5 commits into from
Jan 2, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ check-format:
# The dream is to one day run mypy on the whole tree. For now, limit checks to known-good files.
.PHONY: check-types
check-types:
mypy pyquil/quilatom.py pyquil/quilbase.py pyquil/gates.py pyquil/noise.py \
pyquil/quil.py pyquil/latex pyquil/simulation pyquil/experiment
mypy pyquil/gates.py pyquil/gate_matrices.py pyquil/noise.py pyquil/numpy_simulator.py \
pyquil/quil.py pyquil/quilatom.py pyquil/quilbase.py pyquil/reference_simulator.py \
pyquil/unitary_tools.py pyquil/latex pyquil/simulation pyquil/experiment pyquil/device

.PHONY: check-style
check-style:
Expand Down
92 changes: 59 additions & 33 deletions pyquil/device/_isa.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,44 +13,73 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##############################################################################
from collections import namedtuple
from typing import Union
import sys
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union

import networkx as nx
import numpy as np

from pyquil.quilatom import Parameter, unpack_qubit
from pyquil.quilbase import Gate

THETA = Parameter("theta")
"Used as the symbolic parameter in RZ, CPHASE gates."
if sys.version_info < (3, 7):
from pyquil.external.dataclasses import dataclass
else:
from dataclasses import dataclass

DEFAULT_QUBIT_TYPE = "Xhalves"
DEFAULT_EDGE_TYPE = "CZ"
THETA = Parameter("theta")
"Used as the symbolic parameter in RZ, CPHASE gates."


Qubit = namedtuple("Qubit", ["id", "type", "dead", "gates"])
Edge = namedtuple("Edge", ["targets", "type", "dead", "gates"])
_ISA = namedtuple("_ISA", ["qubits", "edges"])
@dataclass
class MeasureInfo:
operator: Optional[str] = None
qubit: Optional[int] = None
target: Optional[Union[int, str]] = None
duration: Optional[float] = None
fidelity: Optional[float] = None

MeasureInfo = namedtuple("MeasureInfo", ["operator", "qubit", "target", "duration", "fidelity"])
GateInfo = namedtuple("GateInfo", ["operator", "parameters", "arguments", "duration", "fidelity"])

# make Qubit and Edge arguments optional
Qubit.__new__.__defaults__ = (None,) * len(Qubit._fields)
Edge.__new__.__defaults__ = (None,) * len(Edge._fields)
MeasureInfo.__new__.__defaults__ = (None,) * len(MeasureInfo._fields)
GateInfo.__new__.__defaults__ = (None,) * len(GateInfo._fields)
@dataclass
class GateInfo:
operator: Optional[str] = None
parameters: Optional[Sequence[Union[str, float]]] = None
arguments: Optional[Sequence[Union[str, float]]] = None
duration: Optional[float] = None
fidelity: Optional[float] = None


class ISA(_ISA):
@dataclass
class Qubit:
id: int
type: Optional[str] = None
dead: Optional[bool] = None
gates: Optional[Sequence[Union[GateInfo, MeasureInfo]]] = None


@dataclass
class Edge:
targets: Tuple[int, ...]
type: Optional[str] = None
dead: Optional[bool] = None
gates: Optional[Sequence[GateInfo]] = None


@dataclass
class ISA:
"""
Basic Instruction Set Architecture specification.

:ivar Sequence[Qubit] qubits: The qubits associated with the ISA.
:ivar Sequence[Edge] edges: The multi-qubit gates.
:ivar qubits: The qubits associated with the ISA.
:ivar edges: The multi-qubit gates.
"""

def to_dict(self):
qubits: Sequence[Qubit]
edges: Sequence[Edge]

def to_dict(self) -> Dict[str, Any]:
"""
Create a JSON-serializable representation of the ISA.

Expand Down Expand Up @@ -80,19 +109,17 @@ def to_dict(self):
}

:return: A dictionary representation of self.
:rtype: Dict[str, Any]
"""

def _maybe_configure(o, t):
# type: (Union[Qubit,Edge], str) -> dict
def _maybe_configure(o: Union[Qubit, Edge], t: str) -> Dict[str, Optional[Any]]:
appleby marked this conversation as resolved.
Show resolved Hide resolved
"""
Exclude default values from generated dictionary.

:param Union[Qubit,Edge] o: The object to serialize
:param str t: The default value for ``o.type``.
:param o: The object to serialize
:param t: The default value for ``o.type``.
:return: d
"""
d = {}
d: Dict[str, Optional[Any]] = {}
if o.gates is not None:
d["gates"] = [
{
Expand Down Expand Up @@ -127,13 +154,12 @@ def _maybe_configure(o, t):
}

@staticmethod
def from_dict(d):
def from_dict(d: Dict[str, Any]) -> "ISA":
"""
Re-create the ISA from a dictionary representation.

:param Dict[str,Any] d: The dictionary representation.
:param d: The dictionary representation.
:return: The restored ISA.
:rtype: ISA
"""
return ISA(
qubits=sorted(
Expand All @@ -150,7 +176,7 @@ def from_dict(d):
edges=sorted(
[
Edge(
targets=[int(q) for q in eid.split("-")],
targets=tuple(int(q) for q in eid.split("-")),
type=e.get("type", DEFAULT_EDGE_TYPE),
dead=e.get("dead", False),
)
Expand All @@ -161,13 +187,12 @@ def from_dict(d):
)


def gates_in_isa(isa):
def gates_in_isa(isa: ISA) -> List[Gate]:
"""
Generate the full gateset associated with an ISA.

:param ISA isa: The instruction set architecture for a QPU.
:param isa: The instruction set architecture for a QPU.
:return: A sequence of Gate objects encapsulating all gates compatible with the ISA.
:rtype: Sequence[Gate]
"""
gates = []
for q in isa.qubits:
Expand Down Expand Up @@ -211,6 +236,7 @@ def gates_in_isa(isa):
gates.append(Gate("XY", [THETA], targets))
gates.append(Gate("XY", [THETA], targets[::-1]))
continue
assert e.type is not None
if "WILDCARD" in e.type:
gates.append(Gate("_", "_", targets))
gates.append(Gate("_", "_", targets[::-1]))
Expand All @@ -220,7 +246,7 @@ def gates_in_isa(isa):
return gates


def isa_from_graph(graph: nx.Graph, oneq_type="Xhalves", twoq_type="CZ") -> ISA:
def isa_from_graph(graph: nx.Graph, oneq_type: str = "Xhalves", twoq_type: str = "CZ") -> ISA:
"""
Generate an ISA object from a NetworkX graph.

Expand All @@ -230,7 +256,7 @@ def isa_from_graph(graph: nx.Graph, oneq_type="Xhalves", twoq_type="CZ") -> ISA:
"""
all_qubits = list(range(max(graph.nodes) + 1))
qubits = [Qubit(i, type=oneq_type, dead=i not in graph.nodes) for i in all_qubits]
edges = [Edge(sorted((a, b)), type=twoq_type, dead=False) for a, b in graph.edges]
edges = [Edge(tuple(sorted((a, b))), type=twoq_type, dead=False) for a, b in graph.edges]
return ISA(qubits, edges)


Expand Down
55 changes: 29 additions & 26 deletions pyquil/device/_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
##############################################################################
import warnings
from abc import ABC, abstractmethod
from typing import List, Tuple
from typing import Any, Dict, List, Optional, Tuple, Union

import networkx as nx
import numpy as np
Expand All @@ -42,7 +42,7 @@

class AbstractDevice(ABC):
@abstractmethod
def qubits(self):
def qubits(self) -> List[int]:
"""
A sorted list of qubits in the device topology.
"""
Expand All @@ -54,7 +54,7 @@ def qubit_topology(self) -> nx.Graph:
"""

@abstractmethod
def get_isa(self, oneq_type="Xhalves", twoq_type="CZ") -> ISA:
def get_isa(self, oneq_type: str = "Xhalves", twoq_type: str = "CZ") -> ISA:
"""
Construct an ISA suitable for targeting by compilation.

Expand All @@ -65,7 +65,7 @@ def get_isa(self, oneq_type="Xhalves", twoq_type="CZ") -> ISA:
"""

@abstractmethod
def get_specs(self) -> Specs:
def get_specs(self) -> Optional[Specs]:
"""
Construct a Specs object required by compilation
"""
Expand All @@ -86,7 +86,7 @@ class Device(AbstractDevice):
:ivar NoiseModel noise_model: The noise model for the device.
"""

def __init__(self, name, raw):
def __init__(self, name: str, raw: Dict[str, Any]):
"""
:param name: name of the device
:param raw: raw JSON response from the server with additional information about this device.
Expand All @@ -102,23 +102,25 @@ def __init__(self, name, raw):
)

@property
def isa(self):
def isa(self) -> Optional[ISA]:
warnings.warn("Accessing the static ISA is deprecated. Use `get_isa`", DeprecationWarning)
return self._isa

def qubits(self):
def qubits(self) -> List[int]:
assert self._isa is not None
return sorted(q.id for q in self._isa.qubits if not q.dead)

def qubit_topology(self) -> nx.Graph:
"""
The connectivity of qubits in this device given as a NetworkX graph.
"""
assert self._isa is not None
return isa_to_graph(self._isa)

def get_specs(self):
def get_specs(self) -> Optional[Specs]:
return self.specs

def get_isa(self, oneq_type=None, twoq_type=None) -> ISA:
def get_isa(self, oneq_type: Optional[str] = None, twoq_type: Optional[str] = None) -> ISA:
"""
Construct an ISA suitable for targeting by compilation.

Expand All @@ -130,7 +132,7 @@ def get_isa(self, oneq_type=None, twoq_type=None) -> ISA:
"make an ISA with custom gate types, you'll have to do it by hand."
)

def safely_get(attr, index, default):
def safely_get(attr: str, index: Union[int, Tuple[int, ...]], default: Any) -> Any:
if self.specs is None:
return default

Expand All @@ -144,8 +146,8 @@ def safely_get(attr, index, default):
else:
return default

def qubit_type_to_gates(q):
gates = [
def qubit_type_to_gates(q: Qubit) -> List[Union[GateInfo, MeasureInfo]]:
gates: List[Union[GateInfo, MeasureInfo]] = [
MeasureInfo(
operator="MEASURE",
qubit=q.id,
Expand Down Expand Up @@ -200,9 +202,9 @@ def qubit_type_to_gates(q):
]
return gates

def edge_type_to_gates(e):
gates = []
if e is None or "CZ" in e.type:
def edge_type_to_gates(e: Edge) -> List[GateInfo]:
gates: List[GateInfo] = []
if e is None or isinstance(e.type, str) and "CZ" in e.type:
gates += [
GateInfo(
operator="CZ",
Expand All @@ -212,7 +214,7 @@ def edge_type_to_gates(e):
fidelity=safely_get("fCZs", tuple(e.targets), DEFAULT_CZ_FIDELITY),
)
]
if e is not None and "ISWAP" in e.type:
if e is None or isinstance(e.type, str) and "ISWAP" in e.type:
gates += [
GateInfo(
operator="ISWAP",
Expand All @@ -222,7 +224,7 @@ def edge_type_to_gates(e):
fidelity=safely_get("fISWAPs", tuple(e.targets), DEFAULT_ISWAP_FIDELITY),
)
]
if e is not None and "CPHASE" in e.type:
if e is None or isinstance(e.type, str) and "CPHASE" in e.type:
gates += [
GateInfo(
operator="CPHASE",
Expand All @@ -232,7 +234,7 @@ def edge_type_to_gates(e):
fidelity=safely_get("fCPHASEs", tuple(e.targets), DEFAULT_CPHASE_FIDELITY),
)
]
if e is not None and "XY" in e.type:
if e is None or isinstance(e.type, str) and "XY" in e.type:
gates += [
GateInfo(
operator="XY",
Expand All @@ -242,7 +244,7 @@ def edge_type_to_gates(e):
fidelity=safely_get("fXYs", tuple(e.targets), DEFAULT_XY_FIDELITY),
)
]
if e is not None and "WILDCARD" in e.type:
if e is None or isinstance(e.type, str) and "WILDCARD" in e.type:
gates += [
GateInfo(
operator="_",
Expand All @@ -254,6 +256,7 @@ def edge_type_to_gates(e):
]
return gates

assert self._isa is not None
qubits = [
Qubit(id=q.id, type=None, dead=q.dead, gates=qubit_type_to_gates(q))
for q in self._isa.qubits
Expand All @@ -264,10 +267,10 @@ def edge_type_to_gates(e):
]
return ISA(qubits, edges)

def __str__(self):
def __str__(self) -> str:
return "<Device {}>".format(self.name)

def __repr__(self):
def __repr__(self) -> str:
return str(self)


Expand All @@ -284,17 +287,17 @@ class NxDevice(AbstractDevice):
def __init__(self, topology: nx.Graph) -> None:
self.topology = topology

def qubit_topology(self):
def qubit_topology(self) -> nx.Graph:
return self.topology

def get_isa(self, oneq_type="Xhalves", twoq_type="CZ"):
def get_isa(self, oneq_type: str = "Xhalves", twoq_type: str = "CZ") -> ISA:
return isa_from_graph(self.topology, oneq_type=oneq_type, twoq_type=twoq_type)

def get_specs(self):
def get_specs(self) -> Specs:
return specs_from_graph(self.topology)

def qubits(self) -> List[int]:
return sorted(self.topology.nodes)

def edges(self) -> List[Tuple[int, int]]:
return sorted(tuple(sorted(pair)) for pair in self.topology.edges) # type: ignore
def edges(self) -> List[Tuple[Any, ...]]:
return sorted(tuple(sorted(pair)) for pair in self.topology.edges)
Loading