Skip to content

Commit c7234ae

Browse files
authored
Return processor_id and project_id in get_qcs_objects_for_notebooks (#5759)
This adds two fields in the QCSObjectForNotebook This will allow us some flexibility in google notebooks so as not to specify the processor_id. This will also be a step in enabling tests for these notebooks.
1 parent a2fd902 commit c7234ae

File tree

3 files changed

+168
-55
lines changed

3 files changed

+168
-55
lines changed

cirq-google/cirq_google/engine/abstract_local_processor.py

+5
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ def __init__(
9999
if self._schedule[idx].end_time > self._schedule[idx + 1].start_time:
100100
raise ValueError('Time slots cannot overlap!')
101101

102+
@property
103+
def project_id(self) -> str:
104+
"""Project name of the processor."""
105+
return self._project_name
106+
102107
@property
103108
def processor_id(self) -> str:
104109
"""Unique string id of the processor."""

cirq-google/cirq_google/engine/qcs_notebook.py

+86-48
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,57 @@
1313
# limitations under the License.
1414

1515
import dataclasses
16-
from typing import Union, Optional
16+
from typing import cast, Optional, Sequence, Union
1717

1818
import cirq
1919

20-
from cirq_google import (
21-
PhasedFSimEngineSimulator,
22-
ProcessorSampler,
23-
Sycamore,
24-
SQRT_ISWAP_INV_PARAMETERS,
25-
PhasedFSimCharacterization,
26-
get_engine,
20+
from cirq_google import ProcessorSampler, get_engine
21+
from cirq_google.engine import (
22+
AbstractEngine,
23+
AbstractProcessor,
24+
AbstractLocalProcessor,
25+
create_noiseless_virtual_engine_from_latest_templates,
26+
EngineProcessor,
2727
)
2828

2929

3030
@dataclasses.dataclass
3131
class QCSObjectsForNotebook:
32+
"""All the objects you might need to run a notbook with QCS.
33+
34+
Contains an (Abstract) Engine, Processor, Device, and Sampler,
35+
as well as associated meta-data signed_in, processor_id, and project_id.
36+
37+
This removes the need for boiler plate in notebooks, and provides a
38+
central place to handle the various environments (testing vs production),
39+
(stand-alone vs colab vs jupyter).
40+
"""
41+
42+
engine: AbstractEngine
43+
processor: AbstractProcessor
3244
device: cirq.Device
33-
sampler: Union[PhasedFSimEngineSimulator, ProcessorSampler]
45+
sampler: ProcessorSampler
3446
signed_in: bool
35-
36-
@property
37-
def is_simulator(self):
38-
return isinstance(self.sampler, PhasedFSimEngineSimulator)
47+
processor_id: Optional[str]
48+
project_id: Optional[str]
49+
is_simulator: bool
3950

4051

41-
# Disable missing-raises-doc lint check, since pylint gets confused
42-
# by exceptions that are raised and caught within this function.
43-
# pylint: disable=missing-raises-doc
4452
def get_qcs_objects_for_notebook(
45-
project_id: Optional[str] = None, processor_id: Optional[str] = None
46-
) -> QCSObjectsForNotebook: # pragma: nocover
47-
"""Authenticates on Google Cloud, can return a Device and Simulator.
53+
project_id: Optional[str] = None, processor_id: Optional[str] = None, virtual=False
54+
) -> QCSObjectsForNotebook:
55+
"""Authenticates on Google Cloud and returns Engine related objects.
56+
57+
This function will authenticate to Google Cloud and attempt to
58+
instantiate an Engine object. If it does not succeed, it will instead
59+
return a virtual AbstractEngine that is backed by a noisy simulator.
60+
This function is designed for maximum versatility and
61+
to work in colab notebooks, as a stand-alone, and in tests.
62+
63+
Note that, if you are using this to connect to QCS and do not care about
64+
the added versatility, you may want to use `cirq_google.get_engine()` or
65+
`cirq_google.Engine()` instead to guarantee the use of a production instance
66+
and to avoid accidental use of a noisy simulator.
4867
4968
Args:
5069
project_id: Optional explicit Google Cloud project id. Otherwise,
@@ -53,9 +72,14 @@ def get_qcs_objects_for_notebook(
5372
personal project IDs in shared code.
5473
processor_id: Engine processor ID (from Cloud console or
5574
``Engine.list_processors``).
75+
virtual: If set to True, will create a noisy virtual Engine instead.
76+
This is useful for testing and simulation.
5677
5778
Returns:
58-
An instance of DeviceSamplerInfo.
79+
An instance of QCSObjectsForNotebook which contains all the objects .
80+
81+
Raises:
82+
ValueError: if processor_id is not specified and no processors are available.
5983
"""
6084

6185
# Check for Google Application Default Credentials and run
@@ -80,32 +104,46 @@ def get_qcs_objects_for_notebook(
80104
print(f"Authentication failed: {exc}")
81105

82106
# Attempt to connect to the Quantum Engine API, and use a simulator if unable to connect.
83-
sampler: Union[PhasedFSimEngineSimulator, ProcessorSampler]
84-
try:
85-
engine = get_engine(project_id)
86-
if processor_id:
87-
processor = engine.get_processor(processor_id)
88-
else:
89-
processors = engine.list_processors()
90-
if not processors:
91-
raise ValueError("No processors available.")
92-
processor = processors[0]
93-
print(f"Available processors: {[p.processor_id for p in processors]}")
94-
print(f"Using processor: {processor.processor_id}")
95-
device = processor.get_device()
96-
sampler = processor.get_sampler()
97-
signed_in = True
98-
except Exception as exc:
99-
print(f"Unable to connect to quantum engine: {exc}")
100-
print("Using a noisy simulator.")
101-
sampler = PhasedFSimEngineSimulator.create_with_random_gaussian_sqrt_iswap(
102-
mean=SQRT_ISWAP_INV_PARAMETERS,
103-
sigma=PhasedFSimCharacterization(theta=0.01, zeta=0.10, chi=0.01, gamma=0.10, phi=0.02),
104-
)
105-
device = Sycamore
107+
if virtual:
108+
engine: AbstractEngine = create_noiseless_virtual_engine_from_latest_templates()
106109
signed_in = False
107-
108-
return QCSObjectsForNotebook(device=device, sampler=sampler, signed_in=signed_in)
109-
110-
111-
# pylint: enable=missing-raises-doc
110+
is_simulator = True
111+
else:
112+
try:
113+
engine = get_engine(project_id)
114+
signed_in = True
115+
is_simulator = False
116+
except Exception as exc:
117+
print(f"Unable to connect to quantum engine: {exc}")
118+
print("Using a noisy simulator.")
119+
engine = create_noiseless_virtual_engine_from_latest_templates()
120+
signed_in = False
121+
is_simulator = True
122+
if processor_id:
123+
processor = engine.get_processor(processor_id)
124+
else:
125+
# All of these are either local processors or engine processors
126+
# Either way, tell mypy they have a processor_id field.
127+
processors = cast(
128+
Sequence[Union[EngineProcessor, AbstractLocalProcessor]], engine.list_processors()
129+
)
130+
if not processors:
131+
raise ValueError("No processors available.")
132+
processor = processors[0]
133+
processor_id = processor.processor_id
134+
print(f"Available processors: {[p.processor_id for p in processors]}")
135+
print(f"Using processor: {processor_id}")
136+
if not project_id:
137+
project_id = getattr(processor, 'project_id', None)
138+
device = processor.get_device()
139+
sampler = processor.get_sampler()
140+
return QCSObjectsForNotebook(
141+
engine=engine,
142+
processor=processor,
143+
device=device,
144+
sampler=sampler,
145+
signed_in=signed_in,
146+
project_id=project_id,
147+
processor_id=processor_id,
148+
is_simulator=is_simulator,
149+
)

cirq-google/cirq_google/engine/qcs_notebook_test.py

+77-7
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,86 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import unittest.mock as mock
16+
import pytest
17+
1518
import cirq_google as cg
16-
from cirq_google.engine.qcs_notebook import get_qcs_objects_for_notebook
19+
from cirq_google.engine.qcs_notebook import get_qcs_objects_for_notebook, QCSObjectsForNotebook
1720

1821

19-
def test_get_device_sampler():
20-
result = get_qcs_objects_for_notebook()
21-
assert result.device is cg.Sycamore
22+
def _assert_correct_types(result: QCSObjectsForNotebook):
23+
assert isinstance(result.device, cg.GridDevice)
24+
assert isinstance(result.sampler, cg.ProcessorSampler)
25+
assert isinstance(result.engine, cg.engine.AbstractEngine)
26+
assert isinstance(result.processor, cg.engine.AbstractProcessor)
27+
28+
29+
def _assert_simulated_values(result: QCSObjectsForNotebook):
2230
assert not result.signed_in
23-
assert isinstance(result.sampler, cg.PhasedFSimEngineSimulator)
2431
assert result.is_simulator
32+
assert result.project_id == 'fake_project'
2533

26-
result = get_qcs_objects_for_notebook("", "")
27-
assert not result.signed_in
34+
35+
def test_get_qcs_objects_for_notebook_virtual():
36+
result = get_qcs_objects_for_notebook(virtual=True)
37+
_assert_correct_types(result)
38+
_assert_simulated_values(result)
39+
assert result.processor_id == 'rainbow'
40+
assert len(result.device.metadata.qubit_set) == 23
41+
42+
result = get_qcs_objects_for_notebook(processor_id='weber', virtual=True)
43+
_assert_correct_types(result)
44+
_assert_simulated_values(result)
45+
assert result.processor_id == 'weber'
46+
assert len(result.device.metadata.qubit_set) == 53
47+
48+
49+
@mock.patch('cirq_google.engine.qcs_notebook.get_engine')
50+
def test_get_qcs_objects_for_notebook_mocked_engine_fails(engine_mock):
51+
"""Tests creating an engine object which fails."""
52+
engine_mock.side_effect = EnvironmentError('This is a mock, not real credentials.')
53+
result = get_qcs_objects_for_notebook()
54+
_assert_correct_types(result)
55+
_assert_simulated_values(result)
56+
57+
58+
@mock.patch('cirq_google.engine.qcs_notebook.get_engine')
59+
def test_get_qcs_objects_for_notebook_mocked_engine_succeeds(engine_mock):
60+
"""Uses a mocked engine call to test a 'prod' Engine."""
61+
fake_processor = cg.engine.SimulatedLocalProcessor(
62+
processor_id='tester', project_name='mock_project', device=cg.Sycamore
63+
)
64+
fake_processor2 = cg.engine.SimulatedLocalProcessor(
65+
processor_id='tester23', project_name='mock_project', device=cg.Sycamore23
66+
)
67+
fake_engine = cg.engine.SimulatedLocalEngine([fake_processor, fake_processor2])
68+
engine_mock.return_value = fake_engine
69+
70+
result = get_qcs_objects_for_notebook()
71+
_assert_correct_types(result)
72+
assert result.signed_in
73+
assert not result.is_simulator
74+
assert result.project_id == 'mock_project'
75+
assert len(result.device.metadata.qubit_set) == 54
76+
77+
result = get_qcs_objects_for_notebook(processor_id='tester')
78+
_assert_correct_types(result)
79+
assert result.signed_in
80+
assert not result.is_simulator
81+
assert result.project_id == 'mock_project'
82+
assert len(result.device.metadata.qubit_set) == 54
83+
84+
result = get_qcs_objects_for_notebook(processor_id='tester23')
85+
_assert_correct_types(result)
86+
assert result.signed_in
87+
assert not result.is_simulator
88+
assert result.project_id == 'mock_project'
89+
assert len(result.device.metadata.qubit_set) == 23
90+
91+
92+
@mock.patch('cirq_google.engine.qcs_notebook.get_engine')
93+
def test_get_qcs_objects_for_notebook_no_processors(engine_mock):
94+
fake_engine = cg.engine.SimulatedLocalEngine([])
95+
engine_mock.return_value = fake_engine
96+
with pytest.raises(ValueError, match='processors'):
97+
_ = get_qcs_objects_for_notebook()

0 commit comments

Comments
 (0)