Repository: rigetticomputing/pyquil
Pull request #319 (2018-02-11 23:28:53)

feature/318/device-specs


Patch:
diff --git a/pyquil/device.py b/pyquil/device.py
index 2337863..5f69f28 100644
--- a/pyquil/device.py
+++ b/pyquil/device.py
@@ -30,6 +30,9 @@ DEFAULT_EDGE_TYPE = "CZ"
 Qubit = namedtuple("Qubit", ["id", "type", "dead"])
 Edge = namedtuple("Edge", ["targets", "type", "dead"])
 _ISA = namedtuple("_ISA", ["qubits", "edges"])
+QubitSpecs = namedtuple("_QubitSpecs", ["id", "fRO", "f1QRB", "T1", "T2"])
+EdgeSpecs = namedtuple("_QubitQubitSpecs", ["targets", "fBellState"])
+_Specs = namedtuple("_Specs", ["qubits_specs", "edges_specs"])
 
 
 class ISA(_ISA):
@@ -42,7 +45,7 @@ class ISA(_ISA):
 
     def to_dict(self):
         """
-        Create a JSON serializable representation of the ISA.
+        Create a JSON-serializable representation of the ISA.
 
         The dictionary representation is of the form::
 
@@ -156,6 +159,137 @@ def gates_in_isa(isa):
     return gates
 
 
+class Specs(_Specs):
+    """
+    Basic specifications for the device, such as gate fidelities and coherence times.
+
+    :ivar List[QubitSpecs] qubits_specs: The specifications associated with individual qubits.
+    :ivar List[EdgesSpecs] edges_specs: The specifications associated with edges, or qubit-qubit
+    pairs.
+    """
+    def f1QRBs(self):
+        """
+        Get a dictionary of single-qubit randomized benchmarking fidelities (normalized to unity)
+        from the specs, keyed by qubit index.
+
+        :return: A dictionary of 1QRBs, normalized to unity.
+        :rtype: Dict[int, float]
+        """
+        return {qs.id: qs.f1QRB for qs in self.qubits_specs}
+
+    def fROs(self):
+        """
+        Get a dictionary of single-qubit readout fidelities (normalized to unity)
+        from the specs, keyed by qubit index.
+
+        :return: A dictionary of RO fidelities, normalized to unity.
+        :rtype: Dict[int, float]
+        """
+        return {qs.id: qs.fRO for qs in self.qubits_specs}
+
+    def T1s(self):
+        """
+        Get a dictionary of T1s (in seconds) from the specs, keyed by qubit index.
+
+        :return: A dictionary of T1s, in seconds.
+        :rtype: Dict[int, float]
+        """
+        return {qs.id: qs.T1 for qs in self.qubits_specs}
+
+    def T2s(self):
+        """
+        Get a dictionary of T2s (in seconds) from the specs, keyed by qubit index.
+
+        :return: A dictionary of T2s, in seconds.
+        :rtype: Dict[int, float]
+        """
+        return {qs.id: qs.T2 for qs in self.qubits_specs}
+
+    def fBellStates(self):
+        """
+        Get a dictionary of two-qubit Bell state fidelities (normalized to unity)
+        from the specs, keyed by targets (qubit-qubit pairs).
+
+        :return: A dictionary of Bell state fidelities, normalized to unity.
+        :rtype: Dict[tuple(int, int), float]
+        """
+        return {tuple(es.targets): es.fBellState for es in self.edges_specs}
+
+    def to_dict(self):
+        """
+        Create a JSON-serializable representation of the device Specs.
+
+        The dictionary representation is of the form:
+
+            {
+                '1Q': {
+                    "0": {
+                        "f1QRB": 0.99,
+                        "T1": 20e-6,
+                        ...
+                    },
+                    "1": {
+                        "f1QRB": 0.989,
+                        "T1": 19e-6,
+                        ...
+                    },
+                    ...
+                },
+                '2Q': {
+                    "1-4": {
+                        "fBellState": 0.93
+                    },
+                    "1-5": {
+                        "fBellState": 0.9
+                    },
+                    ...
+                },
+                ...
+            }
+
+        :return: A dctionary representation of self.
+        :rtype: Dict[str, Any]
+        """
+        return {
+            '1Q': {
+                "{}".format(qs.id): {
+                    'f1QRB': qs.f1QRB,
+                    'fRO': qs.fRO,
+                    'T1': qs.T1,
+                    'T2': qs.T2
+                } for qs in self.qubits_specs
+            },
+            '2Q': {
+                "{}-{}".format(*es.targets): {
+                    'fBellState': es.fBellState
+                } for es in self.edges_specs
+            }
+        }
+
+    @staticmethod
+    def from_dict(d):
+        """
+        Re-create the Specs from a dictionary representation.
+
+        :param Dict[str, Any] d: The dictionary representation.
+        :return: The restored Specs.
+        :rtype: Specs
+        """
+        return Specs(
+            qubits_specs=sorted([QubitSpecs(id=int(q),
+                                            fRO=qspecs.get('fRO'),
+                                            f1QRB=qspecs.get('f1QRB'),
+                                            T1=qspecs.get('T1'),
+                                            T2=qspecs.get('T2'))
+                                 for q, qspecs in d["1Q"].items()],
+                                key=lambda qubit_specs: qubit_specs.id),
+            edges_specs=sorted([EdgeSpecs(targets=[int(q) for q in e.split('-')],
+                                          fBellState=especs.get('fBellState'))
+                                for e, especs in d["2Q"].items()],
+                               key=lambda edge_specs: edge_specs.targets)
+        )
+
+
 class Device(object):
     """
     A device (quantum chip) that can accept programs. Only devices that are online will actively be
@@ -178,8 +312,9 @@ class Device(object):
         self.name = name
         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
+        self.specs = Specs.from_dict(raw['specs']) if raw.get('specs') else None
+        self.noise_model = NoiseModel.from_dict(raw['noise_model']) \
+            if raw.get('noise_model') else None
 
     def is_online(self):
         """


Test patch:
diff --git a/pyquil/tests/test_device.py b/pyquil/tests/test_device.py
index c951e86..0178364 100644
--- a/pyquil/tests/test_device.py
+++ b/pyquil/tests/test_device.py
@@ -1,7 +1,8 @@
 import numpy as np
 import pytest
 
-from pyquil.device import Device, ISA, Qubit, Edge, THETA, gates_in_isa
+from pyquil.device import (Device, ISA, Qubit, Edge, Specs, QubitSpecs,
+                           EdgeSpecs, THETA, gates_in_isa)
 from pyquil.noise import NoiseModel, KrausModel
 from pyquil.gates import RZ, RX, I, CZ, ISWAP, CPHASE
 from collections import OrderedDict
@@ -38,6 +39,44 @@ def isa_dict():
     }
 
 
+@pytest.fixture
+def specs_dict():
+    return {
+        '1Q': {
+            "0": {
+                "f1QRB": 0.99,
+                "fRO": 0.93,
+                "T1": 20e-6,
+                "T2": 15e-6
+            },
+            "1": {
+                "f1QRB": 0.989,
+                "fRO": 0.92,
+                "T1": 19e-6,
+                "T2": 12e-6
+            },
+            "2": {
+                "f1QRB": 0.983,
+                "fRO": 0.95,
+                "T1": 21e-6,
+                "T2": 16e-6
+            },
+            "3": {
+                "f1QRB": 0.988,
+                "fRO": 0.94,
+                "T1": 18e-6,
+                "T2": 11e-6
+            }
+        },
+        '2Q': {
+            "0-1": {"fBellState": 0.90},
+            "1-2": {"fBellState": 0.91},
+            "2-0": {"fBellState": 0.92},
+            "0-3": {"fBellState": 0.89}
+        }
+    }
+
+
 @pytest.fixture
 def kraus_model_I_dict():
     return {'gate': 'I',
@@ -99,6 +138,32 @@ def test_isa(isa_dict):
     assert isa == ISA.from_dict(isa.to_dict())
 
 
+def test_specs(specs_dict):
+    specs = Specs.from_dict(specs_dict)
+    assert specs == Specs(
+        qubits_specs=[
+            QubitSpecs(id=0, f1QRB=0.99, fRO=0.93, T1=20e-6, T2=15e-6),
+            QubitSpecs(id=1, f1QRB=0.989, fRO=0.92, T1=19e-6, T2=12e-6),
+            QubitSpecs(id=2, f1QRB=0.983, fRO=0.95, T1=21e-6, T2=16e-6),
+            QubitSpecs(id=3, f1QRB=0.988, fRO=0.94, T1=18e-6, T2=11e-6)
+        ],
+        edges_specs=[
+            EdgeSpecs(targets=[0, 1], fBellState=0.90),
+            EdgeSpecs(targets=[0, 3], fBellState=0.89),
+            EdgeSpecs(targets=[1, 2], fBellState=0.91),
+            EdgeSpecs(targets=[2, 0], fBellState=0.92)
+        ])
+
+    assert specs == Specs.from_dict(specs.to_dict())
+
+    assert specs.f1QRBs() == {0: 0.99, 1: 0.989, 2: 0.983, 3: 0.988}
+    assert specs.fROs() == {0: 0.93, 1: 0.92, 2: 0.95, 3: 0.94}
+    assert specs.T1s() == {0: 20e-6, 1: 19e-6, 2: 21e-6, 3: 18e-6}
+    assert specs.T2s() == {0: 15e-6, 1: 12e-6, 2: 16e-6, 3: 11e-6}
+
+    assert specs.fBellStates() == {(0, 1): 0.90, (0, 3): 0.89, (1, 2): 0.91, (2, 0): 0.92}
+
+
 def test_kraus_model(kraus_model_I_dict):
     km = KrausModel.from_dict(kraus_model_I_dict)
     assert km == KrausModel(