Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add get_engine and deprecate engine_from_environment #3151

Merged
merged 5 commits into from
Jul 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cirq/google/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
EngineTimeSlot,
ProtoVersion,
QuantumEngineSampler,
get_engine,
get_engine_sampler,
)

Expand Down
1 change: 1 addition & 0 deletions cirq/google/engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from cirq.google.engine.engine import (
Engine,
get_engine,
ProtoVersion,
)

Expand Down
45 changes: 40 additions & 5 deletions cirq/google/engine/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand All @@ -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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is technically a breaking change :) any reason for the re-ordering?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users should not be using context directly, so moving it down helps reenforce this (also updated doc string to say this). And yeah this is technically breaking, but hopefully people aren't using context.

) -> None:
"""Supports creating and running programs against the Quantum Engine.

Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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',
Expand Down Expand Up @@ -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)
19 changes: 11 additions & 8 deletions cirq/google/engine/engine_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -84,16 +83,20 @@ 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]
except KeyError:
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)
33 changes: 31 additions & 2 deletions cirq/google/engine/engine_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

"""Tests for engine."""
import os
from unittest import mock
import numpy as np
import pytest
Expand Down Expand Up @@ -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',
Expand All @@ -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'))
Expand Down Expand Up @@ -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!')
18 changes: 12 additions & 6 deletions cirq/google/engine/env_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
31 changes: 20 additions & 11 deletions cirq/google/engine/env_config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,32 @@

import os
from unittest import mock
import warnings
import pytest

import cirq


@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()
3 changes: 2 additions & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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