diff --git a/cirq/google/__init__.py b/cirq/google/__init__.py index f3fdb425f0c..04e321ffec6 100644 --- a/cirq/google/__init__.py +++ b/cirq/google/__init__.py @@ -40,6 +40,7 @@ EngineTimeSlot, ProtoVersion, QuantumEngineSampler, + get_engine, get_engine_sampler, ) diff --git a/cirq/google/engine/__init__.py b/cirq/google/engine/__init__.py index d2a1c21b134..342ae1e9a98 100644 --- a/cirq/google/engine/__init__.py +++ b/cirq/google/engine/__init__.py @@ -20,6 +20,7 @@ from cirq.google.engine.engine import ( Engine, + get_engine, ProtoVersion, ) diff --git a/cirq/google/engine/engine.py b/cirq/google/engine/engine.py index eea5652d6ff..f56a6d5b95d 100644 --- a/cirq/google/engine/engine.py +++ b/cirq/google/engine/engine.py @@ -25,6 +25,7 @@ import datetime import enum +import os import random import string from typing import Dict, List, Optional, Sequence, TypeVar, Union, TYPE_CHECKING @@ -108,7 +109,6 @@ def copy(self) -> 'EngineContext': def _value_equality_values_(self): return self.proto_version, self.client - class Engine: """Runs programs via the Quantum Engine API. @@ -133,8 +133,8 @@ def __init__( proto_version: Optional[ProtoVersion] = None, service_args: Optional[Dict] = None, verbose: Optional[bool] = None, - context: Optional[EngineContext] = None, timeout: Optional[int] = None, + context: Optional[EngineContext] = None, ) -> None: """Supports creating and running programs against the Quantum Engine. @@ -143,7 +143,6 @@ def __init__( API interactions will be attributed to this project and any resources created will be owned by the project. See https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects - context: Engine configuration and context to use. proto_version: The version of cirq protos to use. If None, then ProtoVersion.V2 will be used. service_args: A dictionary of arguments that can be used to @@ -152,11 +151,13 @@ def __init__( true. timeout: Timeout for polling for results, in seconds. Default is to never timeout. + context: Engine configuration and context to use. For most users + this should never be specified. """ if context and (proto_version or service_args or verbose): raise ValueError( - 'either provide context or proto_version, service_args' - ' and verbose') + 'Either provide context or proto_version, service_args' + ' and verbose.') self.project_id = project_id if not context: @@ -166,6 +167,9 @@ def __init__( timeout=timeout) self.context = context + def __str__(self) -> str: + return f'Engine(project_id={self.project_id!r})' + def run( self, program: 'cirq.Circuit', @@ -518,3 +522,34 @@ def sampler(self, processor_id: Union[str, List[str]], return engine_sampler.QuantumEngineSampler(engine=self, processor_id=processor_id, gate_set=gate_set) + + +def get_engine(project_id: Optional[str] = None) -> Engine: + """Get an Engine instance assuming some sensible defaults. + + This uses the environment variable GOOGLE_CLOUD_PROJECT for the Engine + project_id, unless set explicitly. By using an environment variable, + you can avoid hard-coding the project_id in shared code. + + If the environment variables are set, but incorrect, an authentication + failure will occur when attempting to run jobs on the engine. + + Args: + project_id: If set overrides the project id obtained from the + environment variable `GOOGLE_CLOUD_PROJECT`. + + Returns: + The Engine instance. + + Raises: + EnvironmentError: If the environment variable GOOGLE_CLOUD_PROJECT is + not set. + """ + env_project_id = 'GOOGLE_CLOUD_PROJECT' + if not project_id: + project_id = os.environ.get(env_project_id) + if not project_id: + raise EnvironmentError( + f'Environment variable {env_project_id} is not set.') + + return Engine(project_id=project_id) diff --git a/cirq/google/engine/engine_sampler.py b/cirq/google/engine/engine_sampler.py index 70094027cbd..4ec77075086 100644 --- a/cirq/google/engine/engine_sampler.py +++ b/cirq/google/engine/engine_sampler.py @@ -11,7 +11,6 @@ # 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 os from typing import List, TYPE_CHECKING, Union, Optional, cast from cirq import work, circuits @@ -72,7 +71,7 @@ def get_engine_sampler(processor_id: str, gate_set_name: str, -> 'cirq.google.QuantumEngineSampler': """Get an EngineSampler assuming some sensible defaults. - This uses the environment variable GOOGLE_GLOUD_PROJECT for the Engine + This uses the environment variable GOOGLE_CLOUD_PROJECT for the Engine project_id, unless set explicitly. Args: @@ -84,6 +83,14 @@ def get_engine_sampler(processor_id: str, gate_set_name: str, this defaults to the environment variable GOOGLE_CLOUD_PROJECT. By using an environment variable, you can avoid hard-coding personal project IDs in shared code. + + Returns: + A `QuantumEngineSampler` instance. + + Raises: + ValueError: If the supplied gate set is not a supported gate set name. + EnvironmentError: If no project_id is specified and the environment + variable GOOGLE_CLOUD_PROJECT is not set. """ try: gate_set = gate_sets.NAMED_GATESETS[gate_set_name] @@ -91,9 +98,5 @@ def get_engine_sampler(processor_id: str, gate_set_name: str, raise ValueError(f"Please use one of the following gateset names: " f"{sorted(gate_sets.NAMED_GATESETS.keys())}") - if project_id is None: - project_id = os.environ['GOOGLE_CLOUD_PROJECT'] - - return engine.Engine(project_id=project_id, - proto_version=engine.ProtoVersion.V2) \ - .sampler(processor_id=processor_id, gate_set=gate_set) + return engine.get_engine(project_id).sampler(processor_id=processor_id, + gate_set=gate_set) diff --git a/cirq/google/engine/engine_test.py b/cirq/google/engine/engine_test.py index d87f6be1822..e02b0904c95 100644 --- a/cirq/google/engine/engine_test.py +++ b/cirq/google/engine/engine_test.py @@ -13,6 +13,7 @@ # limitations under the License. """Tests for engine.""" +import os from unittest import mock import numpy as np import pytest @@ -258,8 +259,11 @@ def test_create_engine(client): with pytest.raises( ValueError, match='provide context or proto_version, service_args and verbose'): - cg.Engine('proj', mock.Mock(), cg.engine.engine.ProtoVersion.V2, - {'args': 'test'}, True) + cg.Engine('proj', + proto_version=cg.engine.engine.ProtoVersion.V2, + service_args={'args': 'test'}, + verbose=True, + context=mock.Mock()) assert cg.Engine( 'proj', @@ -271,6 +275,14 @@ def test_create_engine(client): assert client.called_with({'args': 'test'}, True) +def test_engine_str(): + engine = cg.Engine('proj', + proto_version=cg.engine.engine.ProtoVersion.V2, + service_args={'args': 'test'}, + verbose=True) + assert str(engine) == 'Engine(project_id=\'proj\')' + + def setup_run_circuit_with_result_(client, result): client().create_program.return_value = ( 'prog', qtypes.QuantumProgram(name='projects/proj/programs/prog')) @@ -692,3 +704,20 @@ def test_sampler(client): assert results[i].params.param_dict == {'a': v} assert results[i].measurements == {'q': np.array([[0]], dtype='uint8')} assert client().create_program.call_args[0][0] == 'proj' + + +@mock.patch('cirq.google.engine.client.quantum.QuantumEngineServiceClient') +def test_get_engine(build): + # Default project id present. + with mock.patch.dict(os.environ, { + 'GOOGLE_CLOUD_PROJECT': 'project!', + }, + clear=True): + eng = cirq.google.get_engine() + assert eng.project_id == 'project!' + + # Nothing present. + with mock.patch.dict(os.environ, {}, clear=True): + with pytest.raises(EnvironmentError, match='GOOGLE_CLOUD_PROJECT'): + _ = cirq.google.get_engine() + _ = cirq.google.get_engine('project!') diff --git a/cirq/google/engine/env_config.py b/cirq/google/engine/env_config.py index 6852f529f76..51e923593e6 100644 --- a/cirq/google/engine/env_config.py +++ b/cirq/google/engine/env_config.py @@ -16,20 +16,27 @@ import os -from cirq.google.engine.engine import Engine +from typing import TYPE_CHECKING + +from cirq.google import engine +from cirq._compat import deprecated + +if TYPE_CHECKING: + import cirq ENV_PROJECT_ID = 'CIRQ_QUANTUM_ENGINE_DEFAULT_PROJECT_ID' -def engine_from_environment() -> Engine: +@deprecated(deadline='v0.10.0', fix='Use cirq.get_engine instead.') +def engine_from_environment() -> 'cirq.google.Engine': """Returns an Engine instance configured using environment variables. If the environment variables are set, but incorrect, an authentication failure will occur when attempting to run jobs on the engine. Required Environment Variables: - QUANTUM_ENGINE_PROJECT: The name of a google cloud project, with the - quantum engine enabled, that you have access to. + CIRQ_QUANTUM_ENGINE_DEFAULT_PROJECT_ID: The name of a google cloud + project, with the quantum engine enabled, that you have access to. Raises: EnvironmentError: The environment variables are not set. @@ -38,5 +45,4 @@ def engine_from_environment() -> Engine: if not project_id: raise EnvironmentError( 'Environment variable {} is not set.'.format(ENV_PROJECT_ID)) - - return Engine(project_id=project_id) + return engine.get_engine(project_id) diff --git a/cirq/google/engine/env_config_test.py b/cirq/google/engine/env_config_test.py index de01366d32b..fdb58f8dbaf 100644 --- a/cirq/google/engine/env_config_test.py +++ b/cirq/google/engine/env_config_test.py @@ -14,6 +14,7 @@ import os from unittest import mock +import warnings import pytest import cirq @@ -21,16 +22,24 @@ @mock.patch('cirq.google.engine.client.quantum.QuantumEngineServiceClient') def test_engine_from_environment(build): - # Default project id present. - with mock.patch.dict(os.environ, { - 'CIRQ_QUANTUM_ENGINE_DEFAULT_PROJECT_ID': 'project!', - }, - clear=True): - eng = cirq.google.engine_from_environment() - assert eng.project_id == 'project!' + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + # Default project id present. + env_dict = {'CIRQ_QUANTUM_ENGINE_DEFAULT_PROJECT_ID': 'project!'} + with mock.patch.dict(os.environ, env_dict, clear=True): + eng = cirq.google.engine_from_environment() + assert eng.project_id == 'project!' - # Nothing present. - with mock.patch.dict(os.environ, {}, clear=True): - with pytest.raises(EnvironmentError, - match='CIRQ_QUANTUM_ENGINE_DEFAULT_PROJECT_ID'): + # Nothing present. + with mock.patch.dict(os.environ, {}, clear=True): + with pytest.raises(EnvironmentError, + match='CIRQ_QUANTUM_ENGINE_DEFAULT_PROJECT_ID'): + _ = cirq.google.engine_from_environment() + + +def test_deprecation(): + with cirq.testing.assert_logs('engine_from_environment', 'get_engine', + 'deprecated'): + env_dict = {'CIRQ_QUANTUM_ENGINE_DEFAULT_PROJECT_ID': 'project!'} + with mock.patch.dict(os.environ, env_dict, clear=True): _ = cirq.google.engine_from_environment() diff --git a/docs/api.rst b/docs/api.rst index 3f7e5839715..21f4ec0027f 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -500,7 +500,7 @@ Functionality specific to quantum hardware and services from Google. cirq.google.SYC cirq.google.SYC_GATESET cirq.google.XMON - cirq.google.engine_from_environment + cirq.google.get_engine cirq.google.get_engine_sampler cirq.google.is_native_xmon_gate cirq.google.is_native_xmon_op @@ -728,3 +728,4 @@ These objects and methods will be removed in a future version of the library. cirq.WaveFunctionSimulatorState cirq.WaveFunctionStepResult cirq.WaveFunctionTrialResult + cirq.google.engine_from_environment