Repository: rigetticomputing/pyquil (python)

Issue:
Device ISA modifications
The `ISA` class currently ingests JSON of the format:
```
            {
                "id": {
                    "name": "example_qpu",
                    "version": "0.1",
                    "timestamp": "23423423"
                },
                "logical-hardware": [
                    [
                        {
                            "qubit-id": 0,
                            "type": "Xhalves",
                            "dead": False
                        },
                        {
                            "qubit-id": 1,
                            "type": "Xhalves",
                            "dead": False
                        }
                    ],
                    [
                        {
                            "action-qubits": [0, 1],
                            "type": "CZ",
                            "dead": False
                        }
                    ]
                ]
            }
```

To be compatible with other parts of the Forest stack, it should be of the form:
```
[
    {
        "0": {
            "type": "Xhalves",
            "dead": False
        },
        "1": {
            "type": "Xhalves",
            "dead": False
        },
        ... 
    },
    {
        "1-4": {
            "type": "CZ",
            "dead": False
        },
        "1-5": {
            "type": "CZ",
            "dead": False
        },
        ...
    }
]
```

In other words, the `ISA` json would be a list whose `nth` entry is a dictionary keyed by `n+1` qubit strings, and whose values are all relevant information to that `n+1` qubit set (single-qubit gate information, two-qubit gate information, etc.).

However, as a result of this change the `"id"` dictionary would be lost.

Pull request description:
feature/309/isa-modifications
Modified ISA class to remove 'id' dictionary and accommodate structural changes described in #309. Resolves #309.

Patch:
diff --git a/pyquil/device.py b/pyquil/device.py
index e6070dc..2337863 100644
--- a/pyquil/device.py
+++ b/pyquil/device.py
@@ -29,16 +29,13 @@ DEFAULT_EDGE_TYPE = "CZ"
 
 Qubit = namedtuple("Qubit", ["id", "type", "dead"])
 Edge = namedtuple("Edge", ["targets", "type", "dead"])
-_ISA = namedtuple("_ISA", ["name", "version", "timestamp", "qubits", "edges"])
+_ISA = namedtuple("_ISA", ["qubits", "edges"])
 
 
 class ISA(_ISA):
     """
     Basic Instruction Set Architecture specification.
 
-    :ivar str name: The QPU ISA name.
-    :ivar str version: The version of the ISA.
-    :ivar Union[int,float] timestamp: A timestamp of when the ISA was defined.
     :ivar Sequence[Qubit] qubits: The qubits associated with the ISA.
     :ivar Sequence[Edge] edges: The multi-qubit gates.
     """
@@ -50,48 +47,42 @@ class ISA(_ISA):
         The dictionary representation is of the form::
 
             {
-                "id": {
-                    "name": "example_qpu",
-                    "version": "0.1",
-                    "timestamp": "23423423"
+                "1Q": {
+                    "0": {
+                        "type": "Xhalves"
+                    },
+                    "1": {
+                        "type": "Xhalves",
+                        "dead": True
+                    },
+                    ...
                 },
-                "logical-hardware": [
-                    [
-                        {
-                            "qubit-id": 0,
-                            "type": "Xhalves",
-                            "dead": False
-                        },
-                        {
-                            "qubit-id": 1,
-                            "type": "Xhalves",
-                            "dead": False
-                        }
-                    ],
-                    [
-                        {
-                            "action-qubits": [0, 1],
-                            "type": "CZ",
-                            "dead": False
-                        }
-                    ]
-                ]
+                "2Q": {
+                    "1-4": {
+                        "type": "CZ"
+                    },
+                    "1-5": {
+                        "type": "CZ"
+                    },
+                    ...
+                },
+                ...
             }
 
         :return: A dictionary representation of self.
-        :rtype: Dict[str,Any]
+        :rtype: Dict[str, Any]
         """
 
-        def _maybe_configure(d, o, t):
+        def _maybe_configure(o, t):
             # type: (dict, Union[Qubit,Edge], str) -> dict
             """
             Exclude default values from generated dictionary.
 
-            :param dict d: The dictionary (which is also modified in place!).
             :param Union[Qubit,Edge] o: The object to serialize
             :param str t: The default value for ``o.type``.
             :return: d
             """
+            d = {}
             if o.type != t:
                 d["type"] = o.type
             if o.dead:
@@ -99,16 +90,9 @@ class ISA(_ISA):
             return d
 
         return {
-            "id": {
-                "name": self.name,
-                "version": self.version,
-                "timestamp": self.timestamp,
-            },
-            "logical-hardware": [
-                [_maybe_configure({"qubit-id": q.id}, q, DEFAULT_QUBIT_TYPE) for q in self.qubits],
-                [_maybe_configure({"action-qubits": a.targets}, a, DEFAULT_EDGE_TYPE)
-                 for a in self.edges],
-            ]
+            "1Q": {"{}".format(q.id): _maybe_configure(q, DEFAULT_QUBIT_TYPE) for q in self.qubits},
+            "2Q": {"{}-{}".format(*edge.targets): _maybe_configure(edge, DEFAULT_EDGE_TYPE)
+                   for edge in self.edges}
         }
 
     @staticmethod
@@ -121,17 +105,16 @@ class ISA(_ISA):
         :rtype: ISA
         """
         return ISA(
-            name=d["id"]["name"],
-            version=d["id"].get("version", "0.0"),
-            timestamp=d["id"].get("timestamp"),
-            qubits=[Qubit(id=q["qubit-id"],
-                          type=q.get("type", DEFAULT_QUBIT_TYPE),
-                          dead=q.get("dead", False))
-                    for q in d["logical-hardware"][0]],
-            edges=[Edge(targets=e["action-qubits"],
-                        type=e.get("type", DEFAULT_EDGE_TYPE),
-                        dead=e.get("dead", False))
-                   for e in d["logical-hardware"][1]],
+            qubits=sorted([Qubit(id=int(qid),
+                                 type=q.get("type", DEFAULT_QUBIT_TYPE),
+                                 dead=q.get("dead", False))
+                           for qid, q in d["1Q"].items()],
+                          key=lambda qubit: qubit.id),
+            edges=sorted([Edge(targets=[int(q) for q in eid.split('-')],
+                               type=e.get("type", DEFAULT_EDGE_TYPE),
+                               dead=e.get("dead", False))
+                          for eid, e in d["2Q"].items()],
+                         key=lambda edge: edge.targets),
         )
 
 


Test patch:
diff --git a/pyquil/tests/test_device.py b/pyquil/tests/test_device.py
index 2c538fc..c951e86 100644
--- a/pyquil/tests/test_device.py
+++ b/pyquil/tests/test_device.py
@@ -13,48 +13,28 @@ DEVICE_FIXTURE_NAME = 'mixed_architecture_chip'
 @pytest.fixture
 def isa_dict():
     return {
-        'id': {
-            'name': DEVICE_FIXTURE_NAME,
-            'version': '0.0',
-            'timestamp': '20180104103600'
+        "1Q": {
+            "0": {
+                'type': 'Xhalves'
+            },
+            "1": {},
+            "2": {},
+            "3": {
+                "dead": True
+            }
         },
-        'logical-hardware': [
-            [
-                {
-                    'qubit-id': 0,
-                    'type': 'Xhalves'
-                },
-                {
-                    'qubit-id': 1
-                },
-                {
-                    'qubit-id': 2
-                },
-                {
-                    'qubit-id': 3,
-                    'dead': True
-                }
-            ],
-            [
-                {
-                    'action-qubits': [0, 1],
-                    'type': 'CZ'
-                },
-                {
-                    'action-qubits': [1, 2],
-                    'type': 'ISWAP'
-                },
-                {
-                    'action-qubits': [2, 0],
-                    'type': 'CPHASE'
-                },
-                {
-                    'action-qubits': [0, 3],
-                    'type': 'CZ',
-                    'dead': True
-                }
-            ]
-        ]
+        "2Q": {
+            "0-1": {},
+            "1-2": {
+                "type": "ISWAP"
+            },
+            "2-0": {
+                "type": "CPHASE"
+            },
+            "0-3": {
+                "dead": True
+            }
+        }
     }
 
 
@@ -104,9 +84,6 @@ def device_raw(isa_dict, noise_model_dict):
 def test_isa(isa_dict):
     isa = ISA.from_dict(isa_dict)
     assert isa == ISA(
-        name=isa_dict['id']['name'],
-        version=isa_dict['id']['version'],
-        timestamp=isa_dict['id']['timestamp'],
         qubits=[
             Qubit(id=0, type='Xhalves', dead=False),
             Qubit(id=1, type='Xhalves', dead=False),
@@ -115,9 +92,9 @@ def test_isa(isa_dict):
         ],
         edges=[
             Edge(targets=[0, 1], type='CZ', dead=False),
+            Edge(targets=[0, 3], type='CZ', dead=True),
             Edge(targets=[1, 2], type='ISWAP', dead=False),
             Edge(targets=[2, 0], type='CPHASE', dead=False),
-            Edge(targets=[0, 3], type='CZ', dead=True),
         ])
     assert isa == ISA.from_dict(isa.to_dict())