Repository: rigetticomputing/pyquil
Pull request #276 (2018-01-30 21:20:30)

feature/device-class-enhancements (#275)
KrausModel (and seed dicts) fixtures.

Create fixtures out of ARCH_QPU for dict and ISA object.

Renamed arch_qpu to isa, added device_raw and device_fixture fixtures, and test_device test.

device.raw -> ._raw, and ._isa/._noise_model -> .isa/.noise_model, and modifications to the docs.

Rebase issues

Fixed tests

pep8

ntezak: @pkaralekas @ecp-rigetti could one of you review? It would nice to get this merged before I open my PR with the noise model refactor.
ecp-rigetti: Oh, I thought I already gave it a thumbs up.
cbosborn: @ntezak I will add your suggested docs shortly, let's make sure not to merge until that's done.
cbosborn: @pkaralekas Great suggestions. I've implemented, please take a look. However before this gets merged, @ntezak we'll need to address a question I have about the precise form of the `kraus_ops` and how they are treated by `KrausModel`'s `from_dict` and `to_dict`.
pkaralekas: Fixes #275 

Patch:
diff --git a/pyquil/device.py b/pyquil/device.py
index 26c6f42..543edf1 100644
--- a/pyquil/device.py
+++ b/pyquil/device.py
@@ -270,15 +270,25 @@ class NoiseModel(_NoiseModel):
 class Device(object):
     """
     A device (quantum chip) that can accept programs. Only devices that are online will actively be
-    accepting new programs.
+    accepting new programs. In addition to the ``self._raw`` attribute, two other attributes are
+    optionally constructed from the entries in ``self._raw`` -- ``isa`` and ``noise_model`` -- which
+    should conform to the dictionary format required by the ``.from_dict()`` methods for ``ISA``
+    and ``NoiseModel``, respectively.
+
+    :ivar dict _raw: Raw JSON response from the server with additional information about the device.
+    :ivar ISA isa: The instruction set architecture (ISA) for the device.
+    :ivar NoiseModel noise_model: The noise model for the device.
     """
     def __init__(self, name, raw):
         """
         :param name: name of the device
-        :param raw: raw JSON response from the server with additional information about this device
+        :param raw: raw JSON response from the server with additional information about this device.
         """
         self.name = name
-        self.raw = raw
+        self._raw = raw
+        self.isa = ISA.from_dict(raw['isa']) if 'isa' in raw and raw['isa'] != {} else None
+        self.noise_model = NoiseModel.from_dict(raw['noise_model']) if 'noise_model' in raw \
+            and raw['noise_model'] != {} else None
 
     def is_online(self):
         """
@@ -286,7 +296,7 @@ class Device(object):
 
         :rtype: bool
         """
-        return self.raw['is_online']
+        return self._raw['is_online']
 
     def is_retuning(self):
         """
@@ -294,15 +304,15 @@ class Device(object):
 
         :rtype: bool
         """
-        return self.raw['is_retuning']
+        return self._raw['is_retuning']
 
     @property
     def status(self):
         """Returns a string describing the device's status
 
-            - online: The device is online and ready for use
-            - retuning : The device is not accepting new jobs because it is re-calibrating
-            - offline: The device is not available for use, potentially because you don't
+            - **online**: The device is online and ready for use
+            - **retuning** : The device is not accepting new jobs because it is re-calibrating
+            - **offline**: The device is not available for use, potentially because you don't
               have the right permissions.
         """
         if self.is_online():


Test patch:
diff --git a/pyquil/tests/test_device.py b/pyquil/tests/test_device.py
index 8ade86b..317fd74 100644
--- a/pyquil/tests/test_device.py
+++ b/pyquil/tests/test_device.py
@@ -1,68 +1,124 @@
 from collections import OrderedDict
 
 import numpy as np
+import pytest
 
-from pyquil.device import KrausModel, ISA, Qubit, Edge, NoiseModel
-
-ARCH_QPU = {
-    "id": {
-        "name": "Mixed architecture chip",
-        "version": "0.0",
-        "timestamp": "20180104103600"
-    },
-    "logical-hardware": [
-        [
-            {
-                "qubit-id": 0,
-                "type": "Xhalves"
-            },
-            {
-                "qubit-id": 1
-            },
-            {
-                "qubit-id": 2
-            }
-        ],
-        [
-            {
-                "action-qubits": [0, 1],
-                "type": "CZ"
-            },
-            {
-                "action-qubits": [1, 2],
-                "type": "ISWAP"
-            },
-            {
-                "action-qubits": [2, 0],
-                "type": "CPHASE"
-            }
+from pyquil.device import Device, KrausModel, ISA, Qubit, Edge, NoiseModel
+
+DEVICE_FIXTURE_NAME = 'mixed_architecture_chip'
+
+
+@pytest.fixture
+def isa_dict():
+    return {
+        'id': {
+            'name': DEVICE_FIXTURE_NAME,
+            'version': '0.0',
+            'timestamp': '20180104103600'
+        },
+        'logical-hardware': [
+            [
+                {
+                    'qubit-id': 0,
+                    'type': 'Xhalves'
+                },
+                {
+                    'qubit-id': 1
+                },
+                {
+                    'qubit-id': 2
+                }
+            ],
+            [
+                {
+                    'action-qubits': [0, 1],
+                    'type': 'CZ'
+                },
+                {
+                    'action-qubits': [1, 2],
+                    'type': 'ISWAP'
+                },
+                {
+                    'action-qubits': [2, 0],
+                    'type': 'CPHASE'
+                }
+            ]
         ]
-    ]
-}
+    }
+
+
+@pytest.fixture
+def kraus_model_I_dict():
+    return {'gate': 'I',
+            'fidelity': 1.0,
+            'kraus_ops': [[[[1.]], [[1.0]]]],
+            'targets': (0, 1),
+            'params': (5.0,)}
+
+
+@pytest.fixture
+def kraus_model_RX90_dict():
+    return {'gate': 'RX',
+            'fidelity': 1.0,
+            'kraus_ops': [[[[1.]], [[1.0]]]],
+            'targets': (0,),
+            'params': (np.pi / 2.,)}
 
 
-def test_isa():
-    isa = ISA.from_dict(ARCH_QPU)
+@pytest.fixture
+def noise_model_dict():
+    return {'gates': [{'gate': 'I',
+                       'params': (5.0,),
+                       'targets': (0, 1),
+                       'kraus_ops': [[[[1.]], [[1.0]]]],
+                       'fidelity': 1.0},
+                      {'gate': 'RX',
+                       'params': (np.pi / 2.,),
+                       'targets': (0,),
+                       'kraus_ops': [[[[1.]], [[1.0]]]],
+                       'fidelity': 1.0}],
+            'assignment_probs': {'1': [[1.0, 0.0], [0.0, 1.0]],
+                                 '0': [[1.0, 0.0], [0.0, 1.0]]},
+            'isa_name': DEVICE_FIXTURE_NAME}
+
+
+@pytest.fixture
+def device_raw(isa_dict, noise_model_dict):
+    return {'isa': isa_dict,
+            'noise_model': noise_model_dict,
+            'is_online': True,
+            'is_retuning': False}
+
+
+def test_isa(isa_dict):
+    isa = ISA.from_dict(isa_dict)
     assert isa == ISA(
-        name=ARCH_QPU["id"]["name"],
-        version=ARCH_QPU["id"]["version"],
-        timestamp=ARCH_QPU["id"]["timestamp"],
+        name=isa_dict['id']['name'],
+        version=isa_dict['id']['version'],
+        timestamp=isa_dict['id']['timestamp'],
         qubits=[
-            Qubit(id=0, type="Xhalves", dead=None),
+            Qubit(id=0, type='Xhalves', dead=None),
             Qubit(id=1, type=None, dead=None),
             Qubit(id=2, type=None, dead=None),
         ],
         edges=[
-            Edge(targets=[0, 1], type="CZ", dead=None),
-            Edge(targets=[1, 2], type="ISWAP", dead=None),
-            Edge(targets=[2, 0], type="CPHASE", dead=None),
+            Edge(targets=[0, 1], type='CZ', dead=None),
+            Edge(targets=[1, 2], type='ISWAP', dead=None),
+            Edge(targets=[2, 0], type='CPHASE', dead=None),
         ])
     d = isa.to_dict()
-    assert d == ARCH_QPU
+    assert d == isa_dict
 
 
-def test_kraus_model():
-    km = KrausModel('I', (5.,), (0, 1), [np.array([[1 + 1j]])], 1.0)
+def test_kraus_model(kraus_model_I_dict):
+    km = KrausModel.from_dict(kraus_model_I_dict)
+    assert km == KrausModel(
+        gate=kraus_model_I_dict['gate'],
+        params=kraus_model_I_dict['params'],
+        targets=kraus_model_I_dict['targets'],
+        kraus_ops=[KrausModel.unpack_kraus_matrix(kraus_op)
+                   for kraus_op in kraus_model_I_dict['kraus_ops']],
+        fidelity=kraus_model_I_dict['fidelity'])
     d = km.to_dict()
     assert d == OrderedDict([
         ('gate', km.gate),
@@ -71,14 +127,40 @@ def test_kraus_model():
         ('kraus_ops', [[[[1.]], [[1.0]]]]),
         ('fidelity', 1.0)
     ])
-    assert KrausModel.from_dict(d) == km
 
 
-def test_noise_model():
-    km1 = KrausModel('I', (5.,), (0, 1), [np.array([[1 + 1j]])], 1.0)
-    km2 = KrausModel('RX', (np.pi / 2,), (0,), [np.array([[1 + 1j]])], 1.0)
-    nm = NoiseModel("my_qpu", [km1, km2], {0: np.eye(2), 1: np.eye(2)})
+def test_noise_model(kraus_model_I_dict, kraus_model_RX90_dict):
+    noise_model_dict = {'gates': [kraus_model_I_dict,
+                                  kraus_model_RX90_dict],
+                        'assignment_probs': {'1': [[1.0, 0.0], [0.0, 1.0]],
+                                             '0': [[1.0, 0.0], [0.0, 1.0]]},
+                        'isa_name': DEVICE_FIXTURE_NAME}
+
+    nm = NoiseModel.from_dict(noise_model_dict)
+    km1 = KrausModel.from_dict(kraus_model_I_dict)
+    km2 = KrausModel.from_dict(kraus_model_RX90_dict)
+    assert nm == NoiseModel(isa_name=DEVICE_FIXTURE_NAME,
+                            gates=[km1, km2],
+                            assignment_probs={0: np.eye(2), 1: np.eye(2)})
+    assert nm.gates_by_name('I') == [km1]
+    assert nm.gates_by_name('RX') == [km2]
+    assert nm.to_dict() == noise_model_dict
+
+
+def test_device(isa_dict, noise_model_dict):
+    device_raw = {'isa': isa_dict,
+                  'noise_model': noise_model_dict,
+                  'is_online': True,
+                  'is_retuning': False}
+
+    device = Device(DEVICE_FIXTURE_NAME, device_raw)
+    assert device.name == DEVICE_FIXTURE_NAME
+    assert device.is_online()
+    assert not device.is_retuning()
 
-    assert nm == NoiseModel.from_dict(nm.to_dict())
-    assert nm.gates_by_name("I") == [km1]
-    assert nm.gates_by_name("RX") == [km2]
+    isa = ISA.from_dict(isa_dict)
+    noise_model = NoiseModel.from_dict(noise_model_dict)
+    assert isinstance(device.isa, ISA)
+    assert device.isa == isa
+    assert isinstance(device.noise_model, NoiseModel)
+    assert device.noise_model == noise_model