Repository: quantumlib/Cirq

Issue:
Calling EngineProcessor.list_calibrations gives error
**Description of the issue**

Calling `EngineProcessor.list_calibrations` gives an error.

**How to reproduce the issue**

```python
import cirq

engine = cirq.google.Engine(project_id=PROJECT_ID,
                            proto_version=cirq.google.ProtoVersion.V2)
engine_processor = engine.get_processor('rainbow')
calibrations = engine_processor.list_calibrations()
```
Output:
```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~/Projects/Cirq/cirq/google/api/v2/program.py in grid_qubit_from_proto_id(proto_id)
    102         row, col = parts
--> 103         return devices.GridQubit(row=int(row), col=int(col))
    104     except ValueError:

ValueError: invalid literal for int() with base 10: 'q3'

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
<ipython-input-2-69914ce7bc88> in <module>
      4                             proto_version=cirq.google.ProtoVersion.V2)
      5 engine_processor = engine.get_processor('rainbow')
----> 6 calibrations = engine_processor.list_calibrations()

~/Projects/Cirq/cirq/google/engine/engine_processor.py in list_calibrations(self, earliest_timestamp_seconds, latest_timestamp_seconds)
    158         response = self.context.client.list_calibrations(
    159             self.project_id, self.processor_id, filter_str)
--> 160         return [self._to_calibration(c.data) for c in list(response)]
    161 
    162     def get_calibration(self, calibration_timestamp_seconds: int

~/Projects/Cirq/cirq/google/engine/engine_processor.py in <listcomp>(.0)
    158         response = self.context.client.list_calibrations(
    159             self.project_id, self.processor_id, filter_str)
--> 160         return [self._to_calibration(c.data) for c in list(response)]
    161 
    162     def get_calibration(self, calibration_timestamp_seconds: int

~/Projects/Cirq/cirq/google/engine/engine_processor.py in _to_calibration(calibration_any)
    130         metrics = v2.metrics_pb2.MetricsSnapshot.FromString(
    131             calibration_any.value)
--> 132         return calibration.Calibration(metrics)
    133 
    134     def list_calibrations(self,

~/Projects/Cirq/cirq/google/engine/calibration.py in __init__(self, calibration)
     53     def __init__(self, calibration: v2.metrics_pb2.MetricsSnapshot) -> None:
     54         self.timestamp = calibration.timestamp_ms
---> 55         self._metric_dict = self._compute_metric_dict(calibration.metrics)
     56 
     57     def _compute_metric_dict(

~/Projects/Cirq/cirq/google/engine/calibration.py in _compute_metric_dict(self, metrics)
     68             ]
     69             if metric.targets:
---> 70                 qubits = tuple(
     71                     v2.grid_qubit_from_proto_id(t) for t in metric.targets)
     72                 results[name][qubits] = flat_values

~/Projects/Cirq/cirq/google/engine/calibration.py in <genexpr>(.0)
     69             if metric.targets:
     70                 qubits = tuple(
---> 71                     v2.grid_qubit_from_proto_id(t) for t in metric.targets)
     72                 results[name][qubits] = flat_values
     73             else:

~/Projects/Cirq/cirq/google/api/v2/program.py in grid_qubit_from_proto_id(proto_id)
    103         return devices.GridQubit(row=int(row), col=int(col))
    104     except ValueError:
--> 105         raise ValueError(
    106             'GridQubit proto id must be of the form <int>_<int> but was {}'.
    107             format(proto_id))

ValueError: GridQubit proto id must be of the form <int>_<int> but was q3_2
```

**Cirq version**
0.9.0.dev



Discussion:
I reproduced this - it is pretty serious, so we should fix this as soon as we can.
Based on internal discussions we need to prepare that sometimes the id comes in `q<int>_<int>` format.

Resolving patch:
diff --git a/cirq/google/api/v2/program.py b/cirq/google/api/v2/program.py
--- a/cirq/google/api/v2/program.py
+++ b/cirq/google/api/v2/program.py
@@ -11,7 +11,7 @@
 # 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 re
 from typing import TYPE_CHECKING
 
 from cirq import devices, ops
@@ -19,6 +19,8 @@
 if TYPE_CHECKING:
     import cirq
 
+GRID_QUBIT_ID_PATTERN = r'^q?(-?\d+)_(-?\d+)$'
+
 
 def qubit_to_proto_id(q: 'cirq.Qid') -> str:
     """Return a proto id for a `cirq.Qid`.
@@ -93,13 +95,14 @@ def grid_qubit_from_proto_id(proto_id: str) -> 'cirq.GridQubit':
     Raises:
         ValueError: If the string not of the correct format.
     """
-    parts = proto_id.split('_')
-    if len(parts) != 2:
+
+    match = re.match(GRID_QUBIT_ID_PATTERN, proto_id)
+    if match is None:
         raise ValueError(
             'GridQubit proto id must be of the form <int>_<int> but was {}'.
             format(proto_id))
     try:
-        row, col = parts
+        row, col = match.groups()
         return devices.GridQubit(row=int(row), col=int(col))
     except ValueError:
         raise ValueError(


Test patch:
diff --git a/cirq/google/api/v2/program_test.py b/cirq/google/api/v2/program_test.py
--- a/cirq/google/api/v2/program_test.py
+++ b/cirq/google/api/v2/program_test.py
@@ -50,6 +50,8 @@ def test_grid_qubit_from_proto_id():
     assert v2.grid_qubit_from_proto_id('1_2') == cirq.GridQubit(1, 2)
     assert v2.grid_qubit_from_proto_id('10_2') == cirq.GridQubit(10, 2)
     assert v2.grid_qubit_from_proto_id('-1_2') == cirq.GridQubit(-1, 2)
+    assert v2.grid_qubit_from_proto_id('q-1_2') == cirq.GridQubit(-1, 2)
+    assert v2.grid_qubit_from_proto_id('q1_2') == cirq.GridQubit(1, 2)
 
 
 def test_grid_qubit_from_proto_id_invalid():
@@ -57,6 +59,12 @@ def test_grid_qubit_from_proto_id_invalid():
         _ = v2.grid_qubit_from_proto_id('3_3_3')
     with pytest.raises(ValueError, match='a_2'):
         _ = v2.grid_qubit_from_proto_id('a_2')
+    with pytest.raises(ValueError, match='q1_q2'):
+        v2.grid_qubit_from_proto_id('q1_q2')
+    with pytest.raises(ValueError, match='q-1_q2'):
+        v2.grid_qubit_from_proto_id('q-1_q2')
+    with pytest.raises(ValueError, match='-1_q2'):
+        v2.grid_qubit_from_proto_id('-1_q2')
 
 
 def test_line_qubit_from_proto_id():